Repository navigation
fix(mcp): thread CancellationToken through device tools - #770
Conversation
Client cancel now aborts registry-gate waits and device exclusive-lock waits on disconnect, configure_*, digital/PWM/DAC writes, and set_sample_rate, including analog configure's capability re-read. Co-authored-by: Tyler Kron <tylerkron@gmail.com>
PR Summary by QodoPropagate MCP cancellation through device operations
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1.
|
…change A cancel that landed during configure_analog_channels' capability re-read threw after the new channel mask had gone out but before the live rate was checked against the new cap, leaving a widened channel set at a rate the firmware will not stream at, and abandoning the text exchange mid-reply. The re-read is no longer handed the client token. Configure honors a cancel only before it touches the device; once the mask goes out, the re-read and rate check land with it. The re-read is a bounded exchange. Tests: the refresh test now cancels mid-re-read and asserts the rate is still brought under the new cap. A theory drives every lock-taking device tool through the MCP layer, cancels while it is parked on the device lock, and checks it throws, sends nothing and leaves the lock free. The disconnect test also checks the gate is free afterwards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 516de98 |
|
Qodo-clean, CI green — ready for review |
…l-cancellation-6970 # Conflicts: # src/Daqifi.Mcp/DaqifiAgent.cs # src/Daqifi.Mcp/Tools/DaqifiTools.cs
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 4b05965 |
|
Qodo-clean, CI green — ready for review |
…l-cancellation-6970
What was wrong
When a client stopped a device tool call, the call kept waiting anyway.
disconnect_deviceignored cancellation.DisconnectAsyncwaited on_gatewith theWaitAsync()overload that can't be cancelled, so a client that gave up still sat there until whoever held the registry lock finished.configure_*, digital/PWM/DAC writes, andset_sample_ratetook no token at all. They calledRunExclusiveAsync(_ => …)without one, so a cancel could not stop the wait for the device operation lock.Discover and version-check cancellation is handled in #765. This PR covers the remaining device tools.
How it was fixed
_gatenow takes aCancellationTokenand passes it on. The sync list/status tools are unchanged.DisconnectAsyncuses_gate.WaitAsync(token).RunExclusiveAsync, and each exclusive body checks it once before touching the device.configure_analog_channelschecks the token before it sends the new channel mask. It deliberately does not pass the token to the capability re-read that follows. Cancelling there would leave a wider channel set running at a rate the firmware won't stream at (the rate check comes after the re-read), and it would drop the text exchange while the device's reply is still arriving. The re-read is bounded (3 s response + 250 ms completion).ShutdownAsyncstill can't be cancelled: it is process teardown, not a tool.Tests
Full local suite green on net9.0 and net10.0 after merging current main (
Daqifi.Mcp.Tests293,Daqifi.Core.Tests4356 + 3 skipped).DaqifiToolsso the tool → agent → lock path is covered): the test cancels while the call is parked on the held device lock (it asserts the call hasn't completed before cancelling), then checks the call throwsOperationCanceledExceptionrather thanMcpException, sends no SCPI, skips the capability re-read, and leaves the lock free afterwards.DisconnectAsynccancelled while parked on the held registry gate throws within the bound, does not disconnect, and leaves the gate free: a later disconnect still succeeds.configure_analog_channelscancelled during the capability re-read still finishes: widening 1 → 4 channels at 12000 Hz reportsSampleRateAdjustedFromHz12000 and a live rate of 5000.Does not reopen discover / version-check from #765. Out of scope: Idempotent/Title/[Range] contract, download progress,
SampleRateAdjustedFromHz, comment diet,CreateDefaultdiscover path.Merge order
Re-merged onto current main (
68f15cb), which now includes #750 (typed structured results) and #765 (discover / version-check cancellation). The merge was textually clean and the change is still needed: on main none of these device tools take aCancellationToken, andDisconnectAsyncstill uses the uncancellable_gate.WaitAsync().Daqifi.Mcp.Tests332/332 on net9.0 and net10.0 after the merge, including #750's structured-content contract tests.