karatelabs/karate · error · EngineInterruptedException

setTimeout:

Error message

setTimeout: 

What it means

The TimerScheduler threw a RuntimeException (typically RejectedExecutionException) when scheduling the timer's callback, meaning the scheduler's executor was stopped concurrently. setTimeout unwinds the timer record and its AsyncToken and wraps the cause in EngineInterruptedException so nothing leaks.

Solutions

  1. Check the wrapped cause (e.g. RejectedExecutionException) to confirm the executor was stopped, and stop scheduling at that point in your code
  2. Catch EngineInterruptedException and treat it as a benign shutdown signal
  3. Move timer scheduling earlier in the scenario lifecycle
  4. Ensure only the framework shuts the executor down, and never schedule from post-shutdown hooks

Example fix

// before
setTimeout(() => run(), 10); // RuntimeException: scheduler stopped
// after
try {
  setTimeout(() => run(), 10);
} catch (e) {
  if (!String(e).includes('RejectedExecutionException')) throw e;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  setTimeout(fn, delay);
} catch (e) {
  if (String(e).includes('RejectedExecutionException')) { /* scheduler stopped */ }
  else throw e;
}

Prevention

When it happens

Trigger: setTimeout's timer.schedule() call lands after the scheduler's underlying executor has been shut down; any RuntimeException from the scheduling call is rethrown wrapped as 'setTimeout: <cause>'.

Common situations: RejectedExecutionException during engine teardown; executor shutdown racing timer creation from another thread; calling setTimeout from a hook that runs after the runner's executor is closed.

Understand the failure class

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/1a20e3c1289a6c93. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java:782

            retireTimer(record);
            throw new EngineInterruptedException("setTimeout: karate is shutting down");
        }
        ScheduledFuture<?> future;
        try {
            future = timer.schedule(() -> {
                // foreign thread: may only win the CAS and publish, never run JS
                if (record.fire()) {
                    forgetTimer(record);
                    scope.removeTimer(id);
                    scope.publishSuccessor(record.token, AsyncJob.one(new AsyncJob.Timer(id, target, extra)));
                }
            }, delay);
        } catch (RuntimeException e) {
            // the scheduler was stopped concurrently (RejectedExecutionException):
            // unwind the record and its token, or nothing ever releases them
            scope.removeTimer(id);
            retireTimer(record);
            throw new EngineInterruptedException("setTimeout: " + e);
        }
        record.future = future;
        if (!record.isLive()) {
            // retired between the arm and the assignment — the future is ours to drop
            future.cancel(false);
        }
        return id;
    }

    static Object clearTimeout(Context context, Object[] args) {
        Engine engine = context == null ? null : context.getEngine();
        AsyncScope scope = engine == null ? null : engine.currentScope();
        if (scope == null) {
            return Terms.UNDEFINED;
        }
        Object id = arg(args, 0);
        Number n = id instanceof Number number ? number : Terms.objectToNumber(id);
        if (n == null || Double.isNaN(n.doubleValue())) {

View on GitHub (pinned to a22eb90246)