karatelabs/karate · warning

Pool full, closing excess driver

Error message

Pool full, closing excess driver

What it means

On release(), the driver is offered back to the ArrayBlockingQueue of available drivers. If the queue is already full (offered == false), the excess driver is closed quietly and createdCount is decremented. The code notes this 'shouldn't happen with correct sizing', so it usually indicates a bookkeeping bug or duplicate release.

Solutions

  1. Ensure each acquired driver is released exactly once (check for duplicate release in error/teardown paths)
  2. Keep poolSize stable for the JVM lifetime; do not shrink queue capacity after drivers are created
  3. Check getStats()/createdCount logs to spot leaked or double-released drivers
Defensive patterns

Strategy: validation

Validate before calling

// track acquire/release pairing in your harness
Set<Driver> outstanding = ConcurrentHashMap.newKeySet();
// on acquire: outstanding.add(d); on release: if (!outstanding.remove(d)) log.warn("double release");

Prevention

When it happens

Trigger: Calling release() (or acquire/release lifecycle) more times than the pool expects, or pool size shrunk at runtime so queue capacity < outstanding drivers.

Common situations: A scenario runtime released twice on error paths; misconfigured poolSize smaller than number of drivers created earlier in the JVM; mixed use of pooled and non-pooled providers.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/driver/PooledDriverProvider.java:284

        // scenario that acquires this driver.
        //
        // Why this matters (context for future maintainers): intercept.feature's
        // wildcard-pattern scenario set Fetch.enable via driver.intercept() and never
        // called stopIntercept. The driver went back to the pool with Fetch.enable
        // still active and a stale JS InterceptHandler from a destroyed scenario.
        // Every subsequent Page.navigate on that driver paused ALL subresource
        // requests through our onRequestPaused event handler, which runs synchronously
        // on the CDP websocket dispatch thread. A heavy page (HTML + CSS + JS + etc)
        // could saturate that thread, queueing the Page.navigate command response
        // behind the Fetch event backlog. The 30s CDP wall-clock timeout then fired
        // before the response was dequeued → "CDP timeout for: Page.navigate".
        cleanScenarioState(driver);

        // Return to pool
        boolean offered = availableDrivers.offer(driver);
        if (!offered) {
            // Pool full (shouldn't happen with correct sizing)
            logger.warn("Pool full, closing excess driver");
            closeDriverQuietly(driver);
            createdCount.decrementAndGet();
        } else {
            logger.debug("Returned driver to pool for scenario: {}", runtime.getScenario().getName());
        }
    }

    /**
     * Tear down scenario-scoped driver state at owner-scenario exit. Called from
     * {@link #release} before the driver is offered back to the pool.
     * <p>
     * Only state that belongs to the scenario's scope should be cleaned here —
     * NOT cookies or page state, those belong in {@link #resetDriver} at acquire
     * time (per-scenario setup vs per-scenario teardown).
     * </p>
     * <p>
     * Subclasses overriding this should call {@code super.cleanScenarioState(driver)}
     * to preserve intercept/dialog teardown.

View on GitHub (pinned to a22eb90246)