karatelabs/karate · error · RuntimeException

Timeout waiting for available driver for scenario

Error message

Timeout waiting for available driver for scenario '{scenarioName}' [{stats}]

What it means

When all drivers in the pool are busy, waitForDriver blocks on availableDrivers.poll(30s). If no driver is returned within the 30-second timeout, this RuntimeException is thrown with the scenario name and pool statistics. It means the pool is smaller than concurrent demand or drivers are stuck/leaked.

Solutions

  1. Reduce the number of parallel Karate threads to be <= the driver pool size
  2. Increase the pool size (poolSize / auto-detected size) to match or exceed runner concurrency
  3. Find and fix driver leaks: ensure every scenario releases its driver even on failure (proper teardown hooks)
  4. Investigate hung scenarios/drivers that hold a driver indefinitely; add scenario timeouts
  5. Check getStats() in the message to confirm whether drivers are all assigned or the pool never initialized

Example fix

// before: 10 parallel threads, pool of 2
mvn test -Dtest=Runner -Dkarate.options="-t ~@wip" // threads=10, poolSize=2
// after: match concurrency to pool
System.setProperty("karate.env", "chrome");
runner.path("classpath:features").parallel(2); // threads == pool size
// or size the pool explicitly for 10 threads
Defensive patterns

Strategy: validation

Validate before calling

// before running, ensure concurrency <= pool size
int threads = runnerThreads;
if (threads > driverPoolSize) {
    throw new IllegalStateException("threads " + threads + " > poolSize " + driverPoolSize);
}

Type guard

null

Try / catch

try { karate.run(scenarios); }
catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Timeout waiting for available driver")) {
        logger.error("Pool stats: {} — reduce threads or increase pool", e.getMessage());
    } else throw e;
}

Prevention

When it happens

Trigger: More concurrent scenarios than poolSize drivers; a driver never released (missing release() for a ScenarioRuntime); drivers permanently busy/hung so the queue stays empty for >30 seconds while acquire() waits.

Common situations: karate.options/threads set higher than the auto-detected pool size; a previous scenario crashed without releasing its driver (leak); browser hangs (network stalls, unresponsive page) keeping drivers checked out; heavy parallel CI with limited machine resources.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

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

                poolSize = runtime.getFeatureRuntime().getSuite().threadCount;
                logger.info("Auto-detected pool size from Suite: {}", poolSize);
            }
            if (poolSize < 1) {
                poolSize = 1;  // Fallback
                logger.warn("Could not detect pool size, defaulting to 1");
            }
            availableDrivers = new ArrayBlockingQueue<>(poolSize);
        }
    }

    private Driver waitForDriver(Map<String, Object> config, ScenarioRuntime runtime) {
        String scenarioName = runtime.getScenario().getName();
        logger.warn("Pool exhausted for scenario '{}', waiting for available driver... [{}]",
                scenarioName, getStats());
        try {
            Driver driver = availableDrivers.poll(30, TimeUnit.SECONDS);
            if (driver == null) {
                throw new RuntimeException("Timeout waiting for available driver for scenario '"
                        + scenarioName + "' [" + getStats() + "]");
            }
            // If the driver is terminated OR fails reset/liveness, replace it.
            // Same rationale as takeHealthyFromPool — a poisoned driver must not be
            // handed out to the next scenario.
            if (driver.isTerminated()) {
                createdCount.decrementAndGet();
                driver = createDriver(config);
                createdCount.incrementAndGet();
                logger.info("Replaced terminated driver for scenario: {}", scenarioName);
            } else if (!resetDriver(driver)) {
                closeDriverQuietly(driver);
                createdCount.decrementAndGet();
                driver = createDriver(config);
                createdCount.incrementAndGet();
                logger.info("Replaced unhealthy driver for scenario: {}", scenarioName);
            } else {
                logger.info("Acquired pooled driver after wait for scenario: {}", scenarioName);

View on GitHub (pinned to a22eb90246)