karatelabs/karate · error · EngineInterruptedException

setTimeout: karate is shutting down

Error message

setTimeout: karate is shutting down

What it means

karate-js's setTimeout refuses to schedule a new async timer once the Karate lifecycle has left the RUNNING phase. Throwing EngineInterruptedException here prevents an unbacked AsyncToken from being created that could never be drained during shutdown. It is a deliberate fail-fast guard, not a bug.

Solutions

  1. Move the setTimeout call into running scenario/feature code so the lifecycle is in RUNNING phase
  2. Guard the call: check KarateLifecycle.phase() == RUNNING before scheduling
  3. Replace fire-and-forget timers with synchronous logic or Karate's built-in async/delay mechanisms
  4. If shutdown is unexpected, check for earlier failures (cancellation, thread interruption) that moved the lifecycle out of RUNNING

Example fix

// before
setTimeout(() => doWork(), 100); // may throw during teardown
// after
if (KarateLifecycle.phase() === 'RUNNING') {
  setTimeout(() => doWork(), 100);
}
Defensive patterns

Strategy: validation

Validate before calling

if (KarateLifecycle.phase() !== KarateLifecycle.Phase.RUNNING) {
  // skip or defer the timer
  return;
}
setTimeout(fn, delay);

Prevention

When it happens

Trigger: Calling setTimeout (e.g. from JS `setTimeout(fn, delay)` or Java host code via hostAsyncCall) while KarateLifecycle.phase() is not RUNNING — i.e. before a run starts, during teardown, or after a suite has been aborted/cancelled.

Common situations: Background timers kicked off from after-scenario hooks or cleanup code; a JS callback that schedules a follow-up timer while the engine is being torn down; test-parallel worker shutdown racing a pending setTimeout call.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

        return engine;
    }

    static Object setTimeout(Context context, Object[] args) {
        Engine engine = timerEngine(context);
        AsyncScope scope = engine.currentScope();
        if (scope == null) {
            throw JsErrorException.typeError("setTimeout requires an active evaluation");
        }
        Object callback = arg(args, 0);
        if (!(callback instanceof JsCallable target)) {
            throw JsErrorException.typeError("setTimeout: callback is not a function");
        }
        long delay = coerceDelay(arg(args, 1));
        Object[] extra = args.length > 2 ? Arrays.copyOfRange(args, 2, args.length) : EMPTY_ARGS;
        if (KarateLifecycle.phase() != KarateLifecycle.Phase.RUNNING) {
            // refuse before a token is acquired — scheduling is about to be
            // rejected anyway, and an unbacked token would strand the drain
            throw new EngineInterruptedException("setTimeout: karate is shutting down");
        }
        AsyncToken token = scope.acquire("timer");
        if (token == null) {
            throw new EngineInterruptedException();
        }
        AsyncActivation.anyAsync = true;
        int id = scope.nextTimerId();
        AsyncScope.TimerRecord record = new AsyncScope.TimerRecord(scope, id, token);
        scope.addTimer(record);
        TimerScheduler timer = scheduler();
        // the closed-check and the registration are one atomic step under the
        // scheduler's gate — see TimerScheduler for why ordering alone would not do
        if (!timer.register(record)) {
            scope.removeTimer(id);
            retireTimer(record);
            throw new EngineInterruptedException("setTimeout: karate is shutting down");
        }
        ScheduledFuture<?> future;

View on GitHub (pinned to a22eb90246)