karatelabs/karate · error · EngineTimeoutException
async drain timed out
Error message
async drain timed out
What it means
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'.
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
Example fix
// before scope.drain(500); // ms deadline too tight for remote calls // after scope.drain(30_000); // generous deadline matching worst-case job latency
Defensive patterns
Strategy: try-catch
Validate before calling
// before draining, ensure async work is bounded long started = System.nanoTime(); // drain(deadlineMillis) must exceed the slowest scheduled job's worst case
Try / catch
try {
scope.drain(deadlineMillis);
} catch (EngineTimeoutException e) {
logger.warn("async jobs exceeded {}ms, pending jobs: {}", deadlineMillis, scope.pendingJobCount());
scope.cancel();
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- listen timed out after
- await on a promise that can never settle
- HTTP endpoint not available
- Port : not available within timeout
- retry failed after attempts:
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/37e857341959477d.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java:335
}
reportUnhandledRejections(engine, scope);
return result;
}
static void drain(Engine engine, AsyncScope scope) {
long deadlineNanos = drainDeadline(engine);
while (true) {
if (Thread.currentThread().isInterrupted()) {
throw new EngineInterruptedException();
}
if (scope.isCancelRequested()) {
throw new EngineInterruptedException();
}
if (scope.isQuiescent()) {
return;
}
if (deadlineNanos != 0 && System.nanoTime() > deadlineNanos) {
throw new EngineTimeoutException("async drain timed out");
}
AsyncJob job = scope.takeJob(POLL_MILLIS);
if (job instanceof AsyncJob.Control control) {
// teardown is the pump owner's job, never the requesting
// activation's — it would be joining its own thread
job.token.release();
throw new EngineInterruptedException(control.reason);
}
if (job != null) {
runJob(engine, job);
}
}
}
private static long drainDeadline(Engine engine) {
Duration cap = engine.asyncDrainTimeout();
return cap == null ? 0 : System.nanoTime() + cap.toNanos();
}View on GitHub (pinned to a22eb90246)