karatelabs/karate · warning

timed out stopping after ms

Error message

timed out stopping {} after {}ms

What it means

stopBounded() submits the Stoppable's stop logic to a thread pool and waits future.get(budgetNanos). When the stoppable does not finish within the budget, a TimeoutException is caught, the future is cancelled (interrupting the stop thread), and this warning is logged with Outcome.TIMED_OUT. The resource may still be in the process of shutting down or may be left partially stopped.

Solutions

  1. Increase the shutdown budget/timeout so the stoppable can finish gracefully
  2. Find the resource named in the warning and fix its stop() path (close connections, drain queues) so it stops quickly
  3. Reduce open work (in-flight requests, long polling) before shutdown begins
  4. If the resource is known-safe to abandon, suppress or ignore the warning and ensure the JVM exits (e.g. System.exit or daemon threads)

Example fix

// before
KarateLifecycle.budgetMillis = 500; // server.stop() takes ~2s
// after
KarateLifecycle.budgetMillis = 5000; // allow graceful stop
Defensive patterns

Strategy: try-catch

Try / catch

try { lifecycle.stop(); } catch (Exception e) { logger.warn('shutdown incomplete: {}', e.getMessage()); /* force cleanup */ }

Prevention

When it happens

Trigger: Any Stoppable whose stop() implementation takes longer than the remaining shutdown budget: a stuck HTTP server waiting on open connections, a mock with long in-flight requests, a background process ignoring interrupts, or a stop() that blocks on I/O or locks.

Common situations: Embedded servers holding keep-alive connections; CI pipelines with tight shutdown timeouts; background JVM processes that don't respond to Thread.interrupt(); resources registered but never given a chance to drain.

Understand the failure class

Related errors


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

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/common/KarateLifecycle.java:376

            return new StopResult(name, kind, Outcome.TIMED_OUT, Duration.ZERO);
        }
        logger.debug("stopping {} [{}]", name, kind);
        Future<?> future = pool().submit(() -> {
            SHUTDOWN_THREAD.set(true);
            try {
                stoppable.stop();
            } finally {
                SHUTDOWN_THREAD.remove();
            }
        });
        Outcome outcome;
        try {
            future.get(budgetNanos, TimeUnit.NANOSECONDS);
            outcome = Outcome.STOPPED;
        } catch (TimeoutException e) {
            future.cancel(true);
            outcome = Outcome.TIMED_OUT;
            logger.warn("timed out stopping {} after {}ms", name, TimeUnit.NANOSECONDS.toMillis(budgetNanos));
        } catch (ExecutionException e) {
            Throwable cause = e.getCause() == null ? e : e.getCause();
            outcome = Outcome.FAILED;
            logger.warn("failed to stop {}: {}", name, cause.getMessage());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            future.cancel(true);
            outcome = Outcome.TIMED_OUT;
            logger.warn("interrupted while stopping {}", name);
        }
        return new StopResult(name, kind, outcome, Duration.ofNanos(System.nanoTime() - start));
    }

    private static ExecutorService pool() {
        synchronized (LOCK) {
            if (stopPool == null) {
                // cached, so a worker stuck on a stop() that ignores interrupts never blocks the next
                stopPool = Executors.newCachedThreadPool(ThreadUtils.daemonFactory("karate-lifecycle-"));

View on GitHub (pinned to a22eb90246)