{"record":{"id":"37e857341959477d","repo":"karatelabs/karate","slug":"async-drain-timed-out","errorCode":null,"errorMessage":"async drain timed out","messagePattern":"async drain timed out","errorType":"exception","errorClass":"EngineTimeoutException","httpStatus":null,"severity":"error","filePath":"karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java","lineNumber":335,"sourceCode":"        }\n        reportUnhandledRejections(engine, scope);\n        return result;\n    }\n\n    static void drain(Engine engine, AsyncScope scope) {\n        long deadlineNanos = drainDeadline(engine);\n        while (true) {\n            if (Thread.currentThread().isInterrupted()) {\n                throw new EngineInterruptedException();\n            }\n            if (scope.isCancelRequested()) {\n                throw new EngineInterruptedException();\n            }\n            if (scope.isQuiescent()) {\n                return;\n            }\n            if (deadlineNanos != 0 && System.nanoTime() > deadlineNanos) {\n                throw new EngineTimeoutException(\"async drain timed out\");\n            }\n            AsyncJob job = scope.takeJob(POLL_MILLIS);\n            if (job instanceof AsyncJob.Control control) {\n                // teardown is the pump owner's job, never the requesting\n                // activation's — it would be joining its own thread\n                job.token.release();\n                throw new EngineInterruptedException(control.reason);\n            }\n            if (job != null) {\n                runJob(engine, job);\n            }\n        }\n    }\n\n    private static long drainDeadline(Engine engine) {\n        Duration cap = engine.asyncDrainTimeout();\n        return cap == null ? 0 : System.nanoTime() + cap.toNanos();\n    }","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java#L317-L353","documentation":"AsyncSupport.drain pumps the event loop of a JS scope until it is quiescent, bounded by a deadline; if the deadline (deadlineNanos) elapses while async jobs are still pending, it throws EngineTimeoutException 'async drain timed out'.","triggerScenarios":"Calling drain (directly or via hostAsyncCall/finishScope) on a scope whose async jobs never finish within the configured timeout — long-blocking jobs, jobs that reschedule themselves, or callbacks that never complete.","commonSituations":"JS that awaits network calls slower than the drain deadline; an async job that swallows its completion callback on error; deadlines configured too aggressively for CI machines under load.","solutions":["Increase the drain timeout/deadline configured for the async scope to cover the slowest expected job","Inspect pending jobs for callbacks that are never invoked (missing resolve/reject on error paths)","Add error handling inside async jobs so failures settle the scope instead of hanging it","Log timing of each job to identify which job is exceeding the deadline"],"exampleFix":"// before\nscope.drain(500); // ms deadline too tight for remote calls\n// after\nscope.drain(30_000); // generous deadline matching worst-case job latency","handlingStrategy":"try-catch","validationCode":"// before draining, ensure async work is bounded\nlong started = System.nanoTime();\n// drain(deadlineMillis) must exceed the slowest scheduled job's worst case","typeGuard":null,"tryCatchPattern":"try {\n    scope.drain(deadlineMillis);\n} catch (EngineTimeoutException e) {\n    logger.warn(\"async jobs exceeded {}ms, pending jobs: {}\", deadlineMillis, scope.pendingJobCount());\n    scope.cancel();\n    throw e;\n}","preventionTips":["Size deadlines to the slowest expected remote call, not the average","Always resolve/reject in every branch of async job callbacks","Monitor pending job counts to detect leaking/self-rescheduling jobs","Cancel the scope on timeout to avoid orphaned jobs"],"tags":["async","timeout","javascript-engine"],"backgroundTag":"async-drain-timeout","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"}