feat(mcp): preemptive task cancellation via duckdb_interrupt (#111) - #113
Merged
Conversation
… task cancel (#111) First increment toward interruptible tasks: QueryExecutor already owns its duckdb_connection for its lifetime, so expose interrupt() which calls duckdb_interrupt(conn) from another thread. Additive and REST-safe (the REST path never calls it); a running query returns an error, surfaced as an exception by execute(). Verified by a unit test that starts a huge range-count on one thread and interrupts it from another — it stops in ~1.4s (vs many seconds unbudged) and throws. Next: thread this through a cancellable async-tool execution path and wire tasks/cancel + shutdown to it (see the plan on #111).
- Add a thread-id registry so a QueryExecutor self-registers for the duration of its DuckDB query; another thread can then interrupt it by thread id without threading an executor handle through the whole executeTool -> DatabaseManager -> QueryExecutor pipeline. - MCPTaskManager gains a per-task interrupt hook: the worker publishes an interrupt closure while running, and tasks/cancel and shutdown fire it to stop the in-flight query preemptively instead of waiting for cooperative polling. Cancelled-flag still wins over the resulting error, so an interrupted task lands in 'cancelled', not 'failed'. - shutdown() now interrupts running tasks before joining, so a graceful stop no longer blocks on a multi-minute query finishing on its own. - Tests: unit test that cancel fires the interrupt hook; integration test that a long (100B-row) async query is cancelled in ~1s instead of ~32s.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes MCP task cancellation preemptive:
tasks/canceland graceful shutdown now interrupt the in-flight DuckDB query instead of waiting for the work to notice a cooperative cancel flag. A 100-billion-row async query that would run ~32s is cancelled in ~1s.How it works
QueryExecutor::interrupt()callsduckdb_interrupton the executor's connection; the runningduckdb_queryreturns an error that surfaces as a thrown exception.QueryExecutor*registry lets the executor self-register (RAIIActiveExecGuard) for the duration of each query, so another thread can interrupt whatever query runs on a given worker thread — without threading an executor handle throughexecuteTool→DatabaseManager→QueryExecutor.MCPTaskManagergains a per-task interrupt hook. The worker publishes[tid]{ interruptActiveExecutor(tid); }while running;cancel()andshutdown()fire it under the manager mutex. The cancelled flag still wins over the resulting error, so an interrupted task lands incancelled, notfailed.shutdown()interrupts running tasks before joining, so a graceful stop no longer blocks on a multi-minute query.Test plan
QueryExecutor::interruptstops an in-flight querytasks/cancelfires the preemptive interrupt hookcancelledCloses #111