{"record":{"id":"25a57549c0bc7497","repo":"karatelabs/karate","slug":"engine-is-poisoned","errorCode":null,"errorMessage":"engine is poisoned: ","messagePattern":"engine is poisoned: ","errorType":"exception","errorClass":"EngineException","httpStatus":null,"severity":"critical","filePath":"karate-js/src/main/java/io/karatelabs/js/Engine.java","lineNumber":218,"sourceCode":"\n    /** True once this engine has been poisoned — see {@link #poisoned}. */\n    public boolean isPoisoned() {\n        return poisoned != null;\n    }\n\n    void poison(String detail) {\n        if (poisoned == null) {\n            poisoned = detail;\n        }\n    }\n\n    /** Checked before waiting for scope ownership or touching jsLock, so a\n     *  poisoned engine fails fast rather than blocking on a lock its stuck\n     *  activation may still hold. */\n    void checkPoisoned() {\n        String detail = poisoned;\n        if (detail != null) {\n            throw new EngineException(\"engine is poisoned: \" + detail, null);\n        }\n    }\n\n    /** Fully release jsLock regardless of re-entrant hold count, returning the\n     *  count so the caller can restore it exactly. */\n    int releaseJsLock() {\n        int holds = jsLock.getHoldCount();\n        for (int i = 0; i < holds; i++) {\n            jsLock.unlock();\n        }\n        return holds;\n    }\n\n    void reacquireJsLock(int holds) {\n        for (int i = 0; i < holds; i++) {\n            jsLock.lock();\n        }\n    }","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-js/src/main/java/io/karatelabs/js/Engine.java#L200-L236","documentation":"The JS engine has been 'poisoned': a previous activation failed so badly (e.g. a stuck async activation) that the engine records a detail string and refuses all further work. checkPoisoned is called before waiting for scope ownership or jsLock so further evals fail fast instead of deadlocking behind the poisoned activation's locks.","triggerScenarios":"Any evalRaw/evalInternal/hostAsyncCall after a prior activation poisoned the engine — typically an async callback that never completed, a hung wait, or an unrecoverable internal error recorded in `poisoned`.","commonSituations":"A stuck async step (e.g. a timer or HTTP wait that never resolved) earlier in the run; subsequent evaluate calls then fail with 'engine is poisoned: <detail>'; the useful root cause is in the detail text, not in this throw site.","solutions":["Read the detail suffix after 'engine is poisoned:' — it names the original failure; fix that root cause","Audit async operations for completion paths that can be skipped (timeouts, cancellation)","Recreate/restart the engine instead of reusing a poisoned instance","Check for callbacks that capture the engine and never return (deadlocks, missing resolves)"],"exampleFix":"// before\nengine.eval(\"someStep()\"); // throws 'engine is poisoned: ...'\n// after\ntry {\n  engine.eval(\"someStep()\");\n} catch (EngineException e) {\n  // inspect e.getMessage() after 'engine is poisoned:' for the root cause\n  log.error(e.getMessage());\n}","handlingStrategy":"try-catch","validationCode":"if (engine.isPoisoned()) { /* recreate engine instead of eval */ }","typeGuard":null,"tryCatchPattern":"try {\n  engine.eval(js);\n} catch (EngineException e) {\n  String detail = e.getMessage().replaceFirst(\"^engine is poisoned: \", \"\");\n  // handle the root cause recorded in detail\n}","preventionTips":["Always complete or time-bound async activations","Fix the first failure — every later eval will report poisoned","Restart the engine instead of reusing a poisoned instance"],"tags":["javascript","engine","async","deadlock","fail-fast"],"backgroundTag":"invalid-state-transition","analyzedSha":"a22eb90246d958d15a47bf436693d0121ad2812d","analyzedAt":"2026-09-12T09:01:00.220Z","contentChangedAt":"2026-09-12T09:01:00.220Z","schemaVersion":2},"datasetVersion":"2026-09-16T19:17:19.609Z"}