t8y2/dbx · error · DuckDbWorkerError
duckdb_worker_busy
duckdb_worker_busy
Error message
DuckDB worker is already executing a query
What it means
The DuckDB worker's Execute method rejects incoming queries while an interrupt is flagged, i.e. the worker is still executing a previous query. DuckDB operations are not reentrant in this worker, so a second Execute arriving mid-run returns a duckdb_worker_busy error response instead of being processed.
Source
Thrown at agents/drivers/duckdb/src/runtime.rs:288
let params = request.parse_params()?;
Ok(DuckDbWorkerResponse::ok(request.id, session.get_table_ddl(params)?))
}),
DuckDbWorkerMethod::CompletionAssistant => self.handle_session_request(request, |session, request| {
let params = request.parse_params()?;
Ok(DuckDbWorkerResponse::ok(request.id, session.completion_assistant(params)?))
}),
DuckDbWorkerMethod::GetObjectSource => self.handle_session_request(request, |session, request| {
let params = request.parse_params()?;
Ok(DuckDbWorkerResponse::ok(request.id, session.get_object_source(params)?))
}),
DuckDbWorkerMethod::AttachDatabase => self.handle_session_request(request, |session, request| {
session.attach_database(request.parse_params()?)?;
Ok(DuckDbWorkerResponse::ok_empty(request.id))
}),
DuckDbWorkerMethod::Execute => {
if self.active_interrupt.lock().unwrap_or_else(|e| e.into_inner()).is_some() {
return WorkerHandleResult {
response: Some(DuckDbWorkerResponse::err(
request.id,
DuckDbWorkerError::new("duckdb_worker_busy", "DuckDB worker is already executing a query"),
)),
shutdown: false,
};
}
let interrupt_handle = match self.session.lock().unwrap_or_else(|e| e.into_inner()).interrupt_handle() {
Ok(handle) => handle,
Err(err) => {
return WorkerHandleResult {
response: Some(DuckDbWorkerResponse::err(
request.id,
DuckDbWorkerError::from_message("duckdb_not_connected", err),
)),
shutdown: false,
}
}View on GitHub (pinned to c0390bff16)
Solutions
- Serialize queries client-side: await the response (or the resolved promise) before sending the next Execute request.
- Wait for or clear the active interrupt state before issuing the next query; if a previous cancel wedged, recycle the worker process.
- Queue requests in a driver-level mutex/semaphore so concurrency never reaches the worker while busy.
Example fix
// before void runQuery(sql); runQuery(sql2); // after const busy = await isWorkerBusy(); if (busy) await waitUntilIdle(); await runQuery(sql); await runQuery(sql2);
Defensive patterns
Strategy: retry
Validate before calling
// TS: check busy state before dispatching
async function canExecute(worker: DuckDbWorker): Promise<boolean> {
return !(await worker.getInterruptFlag());
} Type guard
function isBusyError(e: unknown): e is { code: "duckdb_worker_busy" } {
return typeof e === "object" && e !== null && (e as any).code === "duckdb_worker_busy";
} Try / catch
try {
await worker.execute(sql);
} catch (e) {
if (isBusyError(e)) {
await worker.waitForIdle();
return worker.execute(sql);
}
throw e;
} Prevention
- Await each query's response before sending the next Execute.
- Route all queries through a single-flight queue/mutex per worker.
- After a cancel, wait for the interrupt flag to clear before new requests.
When it happens
Trigger: Sending a DuckDbWorkerMethod::Execute request (runtime.rs:288) while active_interrupt is Some — a prior query is still running or its interrupt/cancel flag was not cleared, often when the client pipelines queries without awaiting responses.
Common situations: Frontend issuing a new query while a long-running one executes; a cancelled query leaving active_interrupt set; race between interrupt handling and the next request.
Related errors
- driver operation lock table poisoned
- JRE install lock table poisoned
- duckdb_worker_exited
- JDBC Session was quarantined while waiting for a connection
- Interrupted while waiting for a JDBC workload lease
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/d41a6e9b239d1370.
Report an issue: GitHub.