karatelabs/karate · error · RuntimeException

Interrupted while shutting down HTTP server

Error message

Interrupted while shutting down HTTP server

What it means

HttpServer.stopAndWait() shuts down the Netty boss/worker event loops and waits on their termination futures. If the waiting thread is interrupted, the method restores the interrupt flag and rethrows this RuntimeException wrapping the InterruptedException, since a silent partial shutdown could leave the port bound.

Solutions

  1. Ensure stop() is called on a thread that will not be interrupted, or finish shutdown in a dedicated non-interrupted thread.
  2. Remove overly aggressive timeouts (JUnit @Timeout, executor shutdownNow) around server stop.
  3. Retry/complete the shutdown in a fresh thread; the server close is idempotent per channel closeFuture semantics.
  4. Investigate why the interrupt occurred — usually a stuck earlier phase of the test.
  5. ­

Example fix

// before
someExecutor.shutdownNow(); // interrupts in-flight stop()
// after
someExecutor.shutdown();
someExecutor.awaitTermination(30, TimeUnit.SECONDS);
Defensive patterns

Strategy: try-catch

Validate before calling

if (!Thread.currentThread().isInterrupted()) server.stopAndWait();

Try / catch

try { server.stopAndWait(); } catch (RuntimeException e) { if (e.getCause() instanceof InterruptedException) { /* retry or log; interrupt already re-set */ } else throw e; }

Prevention

When it happens

Trigger: The thread calling server.stop() (or stopAndWait()) is interrupted while blocked on channel close/termination futures — e.g. test framework or executor shutdown interrupts the thread.

Common situations: JUnit timeout rules or TestNG timeouts interrupting a hung stop; application shutdown hooks cancelled mid-stop; a test failing elsewhere and the runner tearing down with interrupt.

Related errors


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

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/http/HttpServer.java:167

    /** Graceful, blocking, idempotent — {@link #stopAndWait()} by another name, for
     *  {@link KarateLifecycle}. Servers that wrap one of these (mock server, OAuth2 callback
     *  server) inherit registration from here and must not register themselves as well. */
    @Override
    public void stop() {
        stopAndWait();
    }

    public void stopAndWait() {
        stopAsync();
        try {
            // the closeFuture, not group termination, is what guarantees the port is free on return
            channel.closeFuture().sync();
            bossGroup.terminationFuture().sync();
            workerGroup.terminationFuture().sync();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Interrupted while shutting down HTTP server", e);
        }
        logger.debug("stop: shutdown complete");
    }

    public void stopAsync() {
        KarateLifecycle.unregister(this);
        logger.debug("stop: shutting down");
        // close the listen socket first — group termination closes registered channels only as a
        // side effect, and (without a quiet period) can complete before the fd is released
        channel.close();
        // no quiet period: a server being torn down accepts nothing more, so waiting for one to
        // pass only delays the (blocking) stop — the timeout still bounds in-flight work
        bossGroup.shutdownGracefully(0, 15, TimeUnit.SECONDS);
        workerGroup.shutdownGracefully(0, 15, TimeUnit.SECONDS);
    }

    private HttpServer(String host, int requestedPort, SslContext sslContext, Function<HttpRequest, HttpResponse> handler, SseHandler sseHandler, WsHandler wsHandler) {
        this.handler = handler;

View on GitHub (pinned to a22eb90246)