{"record":{"id":"b9527aaf821d3d82","repo":"apple/pkl","slug":"cannot-receive-one-way-messages-before-transport-s","errorCode":null,"errorMessage":"Cannot receive one-way messages before transport start.","messagePattern":"Cannot receive one-way messages before transport start\\.","errorType":"exception","errorClass":"ProtocolException","httpStatus":null,"severity":"error","filePath":"pkl-core/src/main/java/org/pkl/core/messaging/MessageTransports.java","lineNumber":131,"sourceCode":"\n    @Override\n    protected void doSend(Message message) throws ProtocolException, IOException {\n      var other = this.other;\n      assert other != null;\n      other.accept(message);\n    }\n\n    public void setOther(DirectMessageTransport other) {\n      this.other = other;\n    }\n  }\n\n  public abstract static class AbstractMessageTransport implements MessageTransport {\n\n    private final Logger logger;\n    private MessageTransport.OneWayHandler oneWayHandler =\n        (msg) -> {\n          throw new ProtocolException(\"Cannot receive one-way messages before transport start.\");\n        };\n    private MessageTransport.RequestHandler requestHandler =\n        (msg) -> {\n          throw new ProtocolException(\"Cannot receive request messages before transport start.\");\n        };\n    private final Map<Long, ResponseHandler> responseHandlers = new ConcurrentHashMap<>();\n\n    protected AbstractMessageTransport(Logger logger) {\n      this.logger = logger;\n    }\n\n    protected void log(String message, Object... args) {\n      var formatter = new MessageFormat(message);\n      logger.log(formatter.format(args));\n    }\n\n    protected abstract void doStart() throws ProtocolException, IOException;\n","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-core/src/main/java/org/pkl/core/messaging/MessageTransports.java#L113-L149","documentation":"(Same family as 121.) The default handlers of AbstractMessageTransport throw ProtocolException for any incoming message before start() runs. This entry covers the one-way message path: a OneWay message was delivered while the transport was still in its pre-start state.","triggerScenarios":"oneWayHandler.handleOneWay(msg) is invoked in accept() with the default handler still installed, i.e. start() was never called before messages flowed.","commonSituations":"Transport wiring races in evaluator hosts; external readers sending notifications immediately after handshake; missing start() call in custom MessageTransport implementations.","solutions":["Ensure start() is invoked immediately after constructing the transport and before connecting","Synchronize transport startup with peer connection establishment","Log transport lifecycle to confirm handler registration happens before first inbound message"],"exampleFix":"// before\nMessageTransports.AbstractMessageTransport t = new MyTransport(logger);\nt.accept(inbound); // throws\n// after\nMyTransport t = new MyTransport(logger);\nt.start();\nt.accept(inbound);","handlingStrategy":"validation","validationCode":"if (!started) { LOG.error(\"one-way message dropped: transport not started\"); return; }","typeGuard":"null","tryCatchPattern":"try { accept(msg); } catch (ProtocolException e) { if (e.getMessage().contains(\"before transport start\")) { transport.start(); } else throw e; }","preventionTips":["Enforce start()-before-connect ordering in host code","Log transport lifecycle events","Avoid constructing transports in one thread and starting them in another without synchronization"],"tags":["java","messaging","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}