{"record":{"id":"daa586f27a796278","repo":"apple/pkl","slug":"externalreaderalreadyterminated","errorCode":"externalReaderAlreadyTerminated","errorMessage":"externalReaderAlreadyTerminated","messagePattern":"externalReaderAlreadyTerminated","errorType":"exception","errorClass":"ExternalReaderProcessException","httpStatus":null,"severity":"error","filePath":"pkl-core/src/main/java/org/pkl/core/externalreader/ExternalReaderProcessImpl.java","lineNumber":93,"sourceCode":"  public ExternalModuleResolver getModuleResolver(long evaluatorId)\n      throws ExternalReaderProcessException {\n    return ExternalModuleResolver.of(getTransport(), evaluatorId);\n  }\n\n  @Override\n  public ExternalResourceResolver getResourceResolver(long evaluatorId)\n      throws ExternalReaderProcessException {\n    return ExternalResourceResolver.of(getTransport(), evaluatorId);\n  }\n\n  private MessageTransport getTransport() throws ExternalReaderProcessException {\n    synchronized (lock) {\n      if (closed) {\n        throw new IllegalStateException(\"External reader process has already been closed.\");\n      }\n      if (process != null) {\n        if (!process.isAlive()) {\n          throw new ExternalReaderProcessException(\n              ErrorMessages.create(\"externalReaderAlreadyTerminated\"));\n        }\n\n        assert transport != null;\n        return transport;\n      }\n\n      // This relies on Java/OS behavior around PATH resolution, absolute/relative paths, etc.\n      var command = new ArrayList<String>();\n      command.add(spec.executable());\n      if (spec.arguments() != null) {\n        command.addAll(spec.arguments());\n      }\n\n      var builder = new ProcessBuilder(command);\n      var workingDir = spec.workingDir();\n      if (workingDir != null) {\n        builder.directory(new File(workingDir));","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-core/src/main/java/org/pkl/core/externalreader/ExternalReaderProcessImpl.java#L75-L111","documentation":"When the external reader process is not closed but the underlying subprocess has already died, getTransport throws ExternalReaderProcessException with code 'externalReaderAlreadyTerminated'. The transport only exists once the process is alive and started, so a dead child means the reader can no longer serve requests.","triggerScenarios":"The external reader subprocess crashed or was killed (OOM, signal, non-zero exit) and then the evaluator calls getTransport via getModuleResolver/getResourceResolver or spec accessors.","commonSituations":"Reader binary path wrong or crashing on startup, system OOM-killer terminating the child, timeout/kill of a hung reader, container shutting down the process.","solutions":["Check the reader process's stderr/exit code to find why it died and fix the root cause","Verify the external reader binary/launcher is present, executable, and compatible","Increase memory limits or fix signals that kill the subprocess","Recreate the external reader process and retry the evaluation once the crash cause is fixed"],"exampleFix":"// ensure reader is alive before resolving\nif (!processHandle.isAlive()) {\n  throw new IllegalStateException(\"reader crashed; check stderr logs before retrying\");\n}\nvar resolvers = ExternalResourceResolver.of(reader.getTransport(), evaluatorId);","handlingStrategy":"try-catch","validationCode":"// before using the reader, confirm the child process is alive\nif (readerProcessHandle != null && !readerProcessHandle.isAlive()) {\n  throw new IllegalStateException(\"External reader died; check its stderr and exit code\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  var transport = reader.getTransport(); // via resolver accessors\n} catch (ExternalReaderProcessException e) {\n  if (e.getMessage().contains(\"externalReaderAlreadyTerminated\")) {\n    // inspect reader stderr/exit code, fix crash cause, then recreate the reader\n  }\n}","preventionTips":["Capture and log the reader process's stderr to diagnose crashes","Set adequate memory limits so the child is not OOM-killed","Verify the reader binary version matches your pkl-core version","Wrap long evaluations with monitoring that detects early child-process death"],"tags":["pkl","process","subprocess-crash","reader"],"backgroundTag":"external-reader-process-terminated","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}