karatelabs/karate · error · io.karatelabs.js.EngineInterruptedException

<interrupted>

Error message

<interrupted>

What it means

JsPromise.join blocks the calling thread on the promise's future; if that thread is interrupted while waiting, the library restores the interrupt flag and throws EngineInterruptedException ("<interrupted>"). This is the engine's typed wrapper so await-style callers unwind uniformly on cancellation.

Solutions

  1. Catch EngineInterruptedException and propagate/abort — do not swallow; the interrupt was intentional.
  2. Ensure awaited promises always settle (resolve/reject) so joins don't linger until interruption.
  3. Check for deadlock in the promise producer (unresolved inner promise, missing resolve callback).
  4. If timeouts are expected, pass an explicit timeout instead of relying on interruption.

Example fix

// before
var v = await neverSettling(); // hangs until thread interrupted
// after
var v = await Promise.race([p, Promise.timeout(5000)]); // settles on its own
Defensive patterns

Strategy: try-catch

Validate before calling

// before awaiting, ensure the producer can settle:
if (p == null || typeof p.then !== 'function') throw new TypeError('not a promise');

Type guard

boolean isAwaitable(Object v) { return v != null && tryResolve(v) != null; } // Thenable check per spec

Try / catch

try { return p.join(timeout); } catch (EngineInterruptedException e) { Thread.currentThread().interrupt(); return null; /* or rethrow after cleanup */ }

Prevention

When it happens

Trigger: Awaiting (join) a promise whose future never completes while the host thread gets Thread.interrupt() — e.g. Karate step timeout, executor shutdown, or explicit cancellation.

Common situations: Test-suite shutdown while awaiting a never-resolving promise; watchdog timers interrupting the worker thread; await on a promise whose JS producer deadlocked or was cancelled (related to generator HOST_CANCELLED).

Related errors


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

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/JsPromise.java:240

     * Block until settled and return the fulfillment value, throwing
     * {@link JsRejectionException} on rejection. For Java callers only — calling
     * this from the engine thread would wait for a queue that nothing is
     * pumping.
     */
    public Object await() {
        return join(null);
    }

    /** {@link #await()} with a deadline; throws {@link EngineTimeoutException}
     *  if the promise has not settled by then. */
    public Object join(Duration timeout) {
        try {
            return timeout == null
                    ? toFuture().get()
                    : toFuture().get(Math.max(timeout.toMillis(), 0), TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new EngineInterruptedException();
        } catch (TimeoutException e) {
            throw new EngineTimeoutException("timed out waiting for promise");
        } catch (ExecutionException e) {
            Throwable cause = e.getCause() == null ? e : e.getCause();
            if (cause instanceof RuntimeException re) {
                throw re;
            }
            throw new EngineException(cause.getMessage(), cause);
        }
    }

    /**
     * The reverse association that makes a round trip lossless: handing a
     * promise's own {@link #toFuture()} back into JS recovers the original
     * {@code JsPromise}, independent of the scope's stage cache.
     */
    static final class PromiseView extends CompletableFuture<Object> {

View on GitHub (pinned to a22eb90246)