t8y2/dbx · critical · DuckDbWorkerError

duckdb_worker_exited

duckdb_worker_exited

Error message

DuckDB worker process exited

What it means

When the DuckDB worker process dies unexpectedly, spawn_stdout_reader (triggered from ensure_started_locked) fails over by resolving every pending request of the dead generation with a duckdb_worker_exited error, so callers' promises reject instead of hanging. The error indicates the worker subprocess terminated (crash, OOM kill, signal) before responding.

Source

Thrown at crates/dbx-core/src/db/duckdb_worker_process.rs:789

                Ok(None) => break,
                Err(err) => {
                    log::warn!("[duckdb-worker:stdout-error] error={err}");
                    break;
                }
            }
        }

        let pending = {
            let mut pending = pending.lock().await;
            let ids = pending
                .iter()
                .filter(|&(_id, request)| request.generation == generation)
                .map(|(id, _request)| id.clone())
                .collect::<Vec<_>>();
            ids.into_iter().filter_map(|id| pending.remove(&id).map(|request| (id, request.sender))).collect::<Vec<_>>()
        };
        for (id, sender) in pending {
            let _ = sender.send(DuckDbWorkerResponse::err(
                id,
                DuckDbWorkerError::new("duckdb_worker_exited", "DuckDB worker process exited"),
            ));
        }
    });
}

View on GitHub (pinned to c0390bff16)

Solutions

  1. Retry the failed request: implement restart-on-demand by re-calling the ensure/start path, which respawns a fresh worker and re-runs the query.
  2. Check why the child died: inspect worker stderr/core logs for panics, and cap query memory (memory_limit pragma) to avoid OOM kills.
  3. Handle the duckdb_worker_exited rejection in callers with a bounded retry + backoff and surface a user-facing reconnect message if retries exhaust.

Example fix

// before
const res = await worker.request(q); // rejects with duckdb_worker_exited and gives up
// after
try {
  const res = await worker.request(q);
} catch (e) {
  if (e.code === "duckdb_worker_exited") {
    await worker.restart();
    return worker.request(q);
  }
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// TS: health-check the worker before dispatching
async function ensureAlive(worker: DuckDbWorkerProcess) {
  if (!(await worker.ping())) await worker.ensureStarted();
}

Type guard

function isExitedError(e: unknown): e is { code: "duckdb_worker_exited" } {
  return typeof e === "object" && e !== null && (e as any).code === "duckdb_worker_exited";
}

Try / catch

try {
  return await worker.request(req);
} catch (e) {
  if (isExitedError(e)) {
    await worker.restart();
    return worker.request(req);
  }
  throw e;
}

Prevention

When it happens

Trigger: A request sent through duckdb_worker_process (crates/dbx-core/src/db/duckdb_worker_process.rs:789) whose worker process exited while the request was pending — child crash (panic/segfault), SIGKILL from the OOM killer on a huge query, or explicit kill on shutdown/generation change.

Common situations: Out-of-memory on large DuckDB aggregations; worker panic on malformed input; system shutdown or supervisor restart killing the child; generation bump from a previous restart invalidating in-flight requests.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/e0be7a8a0cf5eb78. Report an issue: GitHub.