karatelabs/karate · warning

executor did not terminate

Error message

executor did not terminate: {}

What it means

KarateLifecycle.stop() shuts down its internal executor and calls awaitTermination(DRAIN_MILLIS). If the executor's threads still have not terminated after the drain window, shutdownNow() has already interrupted them and this warning logs that termination did not complete. Non-daemon worker threads may keep the JVM alive or tasks may be abandoned mid-run.

Solutions

  1. Ensure all registered Stoppables stop promptly (fix their stop() implementations)
  2. Investigate which task blocks the executor — take a thread dump at shutdown to identify the stuck thread
  3. Allow more drain time if work legitimately needs it
  4. Use daemon threads for pooled work so a non-terminated executor cannot keep the JVM alive

Example fix

// before: task blocks on socket read, ignores interrupt
socket.getInputStream().read();
// after
socket.setSoTimeout(1000); // read becomes interruptible, stop completes
Defensive patterns

Strategy: validation

Validate before calling

// verify executor health before shutdown
if (!executor.isTerminated()) { executor.shutdownNow(); }

Prevention

When it happens

Trigger: Executor tasks that ignore interruption or block on non-interruptible I/O/locks during shutdown; very short DRAIN_MILLIS relative to work in flight; a submitted stoppable task hung inside stop().

Common situations: Hung background servers during suite teardown; CI agents where the process lingers after tests; leaked threads from user code registered as Stoppables.

Related errors


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

Appendix: source

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

        @Override
        public String lifecycleKind() {
            return kind;
        }

        @Override
        public ExecutorService lifecycleExecutor() {
            return executor;
        }

        @Override
        public void stop() {
            if (executor == null || executor.isTerminated()) {
                return;
            }
            executor.shutdownNow();
            try {
                if (!executor.awaitTermination(DRAIN_MILLIS, TimeUnit.MILLISECONDS)) {
                    logger.warn("executor did not terminate: {}", name);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        @Override
        public String toString() {
            return name;
        }

    }

}

View on GitHub (pinned to a22eb90246)