Repository navigation
Queries-tab web reads stop their store query when the request is abandoned (#4203) - #4350
Merged
Merged
Conversation
… slice) (#4203) Threads CancellationToken from /api/read's RequestAborted down to Npgsql for audit_config and the rest of the web viewer's Configuration tab, plus the shared DarlingServerResolver every tool resolves a server name through. Adds WebReadCancellationPinTests, a code-driven ratchet (dispatch table + tool method reflection) with an allowlist for the tools later lanes still need to convert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
PgTargetMcpSurfaceTests: the audit_config anchor now matches PostgresTargetFactsAsync's new cancellationToken argument. McpServiceParameterDiSeatCensusTests: exclude CancellationToken from the DI-seat census. The MCP SDK binds it to the call's own cancellation, the same way it binds IMcpServer; it is never a DI service and never a client argument. EngineCapabilityMissTests (EngineCapabilityReadWiringTests): WiringCall now allows the collector argument to be followed by the call's cancellation token, so the five NotCollectedStatusAsync calls this PR touched still report their real collector name instead of the literal token identifier. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
get_active_queries, get_plan_corrections, get_query_heatmap and get_query_store_clutter now take a CancellationToken and pass it down to every store call, so an abandoned web request stops its query instead of running to completion. Removes the four from CancellationAllowlist. Also repins two tests the new call shapes broke: QueryStoreClutterTests' anchored-window pin (now expects the token in the fleet-read call text) and DarlingWebEndpointsTests' active-queries preview pin (now expects c.RequestAborted). Widens EngineCapabilityReadWiringTests' WiringCall regex so a NotCollectedStatusAsync call whose collector argument is a type-qualified const (get_query_store_clutter's DarlingQueryStoreClutterReader.CollectorName, which its local consts lookup cannot resolve) doesn't backtrack past the dot and mis-capture the trailing cancellationToken argument as the collector name. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
get_top_queries_by_cpu, get_top_procedures_by_cpu, get_query_store_regressions and get_long_query_completions now take a CancellationToken and pass it down to every store call (including the concurrent CPU-attribution reads in the two top-by-cpu tools), so an abandoned web request stops its query instead of running to completion. Removes the four from CancellationAllowlist. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
RuntimePreconditionReadWiringTests took the LAST argument of RuntimePrecondition.StatusAsync(...) as the collector name. This PR passes the request's cancellation token as a trailing argument, so the census read "cancellationToken" as the collector name instead of the real one, failing EveryWiredRead_NamesARealCollector and BothSkus_WireTheSameSharedReads. PreconditionCall now accepts an optional trailing token argument, positional or named, after the real name, mirroring the same fix EngineCapabilityMissTests already carries for NotCollectedStatusAsync. A new inline-data self-test pins the four call shapes directly against the regex. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…ellation-2 # Conflicts: # Darling/Darling.Tests/EngineCapabilityMissTests.cs # Darling/PerformanceMonitor.Darling.Service/DarlingWebEndpoints.cs
erikdarlingdata
marked this pull request as ready for review
September 25, 2026 23:20
This was referenced Sep 25, 2026
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.
Part of #4203. STACKED on #4347: merge #4347 first.
Why
The web viewer's Queries tab issues
/api/read/*calls that run a store query on Darling's behalf. When thebrowser abandons the request (tab closed, navigated away, filter changed before the first answer came back),
the store query used to keep running to completion with nothing left to read the result — wasted work on the
monitoring store for every one of these reads. C1's PR #4347 built the pattern and the pin
(
WebReadCancellationPinTests) on the Configuration tab's tools; this lane converts 8 of the 12 Queries-tabtools in its slice to the same pattern.
What changes
Each of these 8 tools now takes
CancellationToken cancellationToken = defaultas its last parameter, threadsit to
DarlingServerResolver.ResolveOrErrorAsyncand down to every store call below it (including theconcurrent CPU-attribution reads the two top-by-cpu tools run via
Task.WhenAll), and its catch-all iscatch (Exception ex) when (ex is not OperationCanceledException)so cancellation is not turned into an errorpayload. Each tool's
BuildReadDispatchentry inDarlingWebEndpoints.csnow passesc.RequestAborted, andeach name is removed from
CancellationAllowlist:get_active_queriesget_plan_correctionsget_query_heatmapget_query_store_clutterget_top_queries_by_cpuget_top_procedures_by_cpuget_query_store_regressionsget_long_query_completionsCancellationAllowlistnow holds 114 names (down from 122 at this branch's base). Four tools from this lane'soriginal 12-tool slice are not converted and stay in the allowlist for a follow-up lane:
get_query_duration_trend,get_procedure_duration_trend,get_query_store_duration_trend,get_query_trend(all in
DarlingMcpTrendTools.cs).get_query_store_topalso stays, per the brief: PR #4341 is rewriting itsreader, and it converts after that merges.
Two test fixes came from merging
origin/fix/4203-web-read-cancellationpartway through this lane (commit2cdce525, from the coordinator's correction after #4347's CI found three pin classes that break when a toolgains the
cancellationTokenargument):PgTargetMcpSurfaceTests,McpServiceParameterDiSeatCensusTestsandEngineCapabilityMissTests(EngineCapabilityReadWiringTests) all came in via that merge, not from this lane.Three more test fixes are this lane's own, all the same class of defect (a pin that matches a call's exact
source text, which the new
cancellationTokenargument changes) surfacing in tools this lane converted:QueryStoreClutterTests.TheToolBody_ReadsTheFleetOverTheSameAnchoredWindowpinned the fleet-read call textliterally; updated to expect
cancellationTokenin it. The window-anchoring behavior the test actuallychecks (the fleet reads use the same
requestedStart/nowas the main reads) is unchanged.DarlingWebEndpointsTests.ReadEndpoints_ActiveQueries_KeepsTheTwoThousandCharacterWebPreviewpinned the wholeget_active_queriesdispatch line; updated to expect the trailingc.RequestAborted. The 2000-character webpreview budget the test actually checks is unchanged.
EngineCapabilityReadWiringTests'WiringCallregex (already widened by the merge above to allow atrailing
cancellationTokenargument) still mis-parsedget_query_store_clutter'sNotCollectedStatusAsync(..., DarlingQueryStoreClutterReader.CollectorName, cancellationToken)call: itscollector argument is a type-qualified const the regex's local
constslookup can't resolve (that lookuponly sees
private const string X = "...";declared in the same file), so before this fix the regexbacktracked past the dot and captured the literal word
cancellationTokenas if it were the collector name,which then failed the "is a real
CollectorCatalogname" assertion. This exact call, unchanged apart from thenew trailing argument, was simply invisible to this census before (the regex found no match at all for a
dotted const with no argument after it) — a latent gap, not a behavior change. Widened the regex's
bare-identifier branch with a negative lookahead so it never captures
cancellationToken/ct/CancellationToken.Noneas the primary collector-name group, which restores the prior silent-skip for thisone dotted-const call rather than asserting a wrong name, while every properly-resolvable call (everything
else in both SKUs) is still fully validated.
CHANGELOG entry
SECTION: Fixed
ENTRY:
REF:
[Queries-tab web reads stop their store query when the request is abandoned (#4203) #4350]: Queries-tab web reads stop their store query when the request is abandoned (#4203) #4350
Test plan
WebReadCancellationPinTests(its dispatch theory covers every tool outside the allowlist, including all 8 this lane converted)DarlingMcpPlanCorrectionToolsTests,PlanCorrectionsWebDefaultTests,DarlingQueryHeatmapSurfaceAndSqlTests,QueryStoreClutterTests,QueryStoreClutterViewerSurfacesTests,ViewerQueryTrendsSqlTests,ViewerQueryHeatmapSqlTests,ViewerQuerySnapshotsSqlTests,ViewerActiveQueriesDisplayTests,McpToolGuideHeadsSqlCoreActiveQueriesTests,DarlingMcpDataToolsTests,DarlingQueryStoreRegressionsTests,McpToolGuideHeads.LongQueryMcpPayloadContractCensusTests,PgTargetMcpSurfaceTests,McpServiceParameterDiSeatCensusTests,EngineCapabilityReadWiringTests(the three the coordinator flagged, plus the census)*WebEndpoint*,*ReadDispatch*,StorageCommandTimeoutTests,AlertReadFailureSurfaceTests,DocCommentHygiene*dotnet build Darling/Darling.Tests/Darling.Tests.csproj: 0 Warning(s), 0 Error(s) (checked after each batch)git merge origin/dev— not run. This lane's watchdog fired past 200k tokens partway through wiring up the second batch of 4 tools; per the token guardrails ("past 200k... don't start another build-test cycle"), I committed and pushed what was done and stopped rather than opening a merge that could need its own build-fix cycle.-classruns against the MTP executable (Darling.Tests.exe), no rig started (per the brief, none needed for this slice).No PostgreSQL rig was started (per the brief: the pin uses a closed local port).
What's left in #4203
get_query_duration_trend,get_procedure_duration_trend,get_query_store_duration_trend,get_query_trend(
DarlingMcpTrendTools.cs) — this lane's original slice included these 4; not converted, stopped by the200k-token watchdog.
get_query_duration_trendandget_procedure_duration_trendeach have a publicMCP-facing overload plus an internal budget-taking overload
BuildReadDispatchcalls directly (the samesplit Trend tools return every raw point: get_file_io_trend sends 12,451 points / 1.4 MB for one server at defaults, more than an LLM client's context #3897's trend tools use elsewhere) — both overloads need the token threaded, and the public wrapper
needs to pass it to the internal call.
get_query_store_top— intentionally left, per the brief: PR Query Store reads get a wide interval table (#3953) #4341 is rewriting its reader.CancellationAllowlistfor later lanes.Coordinator: please double-check
git merge origin/devand the full suite run clean before this leaves draft (neither ran in this lane).EngineCapabilityReadWiringTestsregex widening (an exemption oncancellationToken/ct/CancellationToken.Noneas a bare collector-name match) — this is shared test infrastructure, not scoped toone tool, so worth a second look even though it's the same class of fix as the coordinator's own merged
commit.
#4350) — I don't know this PR's actual number untilgh pr createreturns it; if it differs, the entry's
[#...]label and REF line need the real number.C2b (census fix)
CI run 36187700714 failed two tests on both Darling jobs:
RuntimePreconditionReadWiringTests.EveryWiredRead_NamesARealCollectorandRuntimePreconditionReadWiringTests.BothSkus_WireTheSameSharedReads.Cause.
RuntimePreconditionMissTests.cs'sPreconditionCallregex took the LAST argument ofRuntimePrecondition.StatusAsync(...)as the collector name. This slice passes the request'scancellationTokenas a trailing argument onget_long_query_completions's andget_query_store_clutter_report's calls, so the census read"cancellationToken"as the collector nameinstead of the real one.
Fix.
PreconditionCallnow accepts an optional trailing token argument after the real name, positional(
cancellationToken) or named (cancellationToken: ct), and still requires the real name to come before it.The bare-identifier name branch keeps the negative lookahead that already protects
EngineCapabilityMissTests's sibling regex (WiringCall) forNotCollectedStatusAsync, which this same PRalready carries and which CI did not flag, so this brings
RuntimePreconditionMissTests.csin line with thesame fix rather than inventing a new shape. Added a
[Theory]self-test,PreconditionCall_ReadsTheNameAcrossTrailingTokenShapes, that runs the regex directly againstStatusAsync(a, b, "x"),StatusAsync(a, b, "x", cancellationToken),StatusAsync(a, b, "x", cancellationToken: ct)andStatusAsync(a, b, name, cancellationToken), assertingeach reads the name, not the token.
I grepped
RuntimePreconditionMissTests.csand every*Census*.cs/*Wiring*.csfile underDarling/Darling.Testsfor other regexes assuming the collector name is the last argument before). Theother two
RuntimePrecondition-related regexes in the same file (CapabilityCall,QueryStoreCall) onlycheck that a call exists — they never capture a trailing argument as a name — and
EngineCapabilityMissTests'sWiringCallalready carries the equivalent fix.McpPayloadContractCensusTests'sSharedPassThroughProducermatches only up through the call's opening paren, never a captured argument.
PgTargetMcpSurfaceTests.csandMcpServiceParameterDiSeatCensusTests.csbuild no ad hoc regexes over these calls at all. No other instance ofthe defect found.
Tests. Built
Darling/Darling.Tests/Darling.Tests.csproj: 0 Warning(s), 0 Error(s).CollectorRuntimePreconditionTests+RuntimePreconditionReadWiringTests+RuntimePreconditionMissLivePostgresTests(every class inRuntimePreconditionMissTests.cs): 43 total, 0 failed, 3 skipped (live-Postgres, no rig started per this lane's brief).PgTargetMcpSurfaceTests+McpServiceParameterDiSeatCensusTests+EngineCapabilityReadWiringTests+McpPayloadContractCensusTests: 77 total, 0 failed, 0 skipped.pm-pr lane report
Resolved this PR's conflict with
devafter #4347 (the first #4203 slice) squash-merged, since dev now held #4347's changes as one commit while this branch still carried the original commits.01ae1fc2624a7bd94dc9dc6e1034347aefceddd5(merge oforigin/devintofix/4203-web-read-cancellation-2, fast-forward push, no force).Darling/PerformanceMonitor.Darling.Service/DarlingWebEndpoints.cs: 4 conflict blocks, all in theCancellationAllowlistratchet and theget_long_query_completionshandler row. Resolved by taking dev's full (post-Web reads stop their store query when the request is abandoned (first slice) (#4203) #4347) allowlist and removing this PR's own 8 tools from it, so the merged set holds every Web reads stop their store query when the request is abandoned (first slice) (#4203) #4347 removal plus this PR's own. Theget_long_query_completionshandler kept this PR's version (withcancellationToken: c.RequestAbortedthreaded), since dev's copy of that row predates this PR's own conversion of that tool.Darling/Darling.Tests/EngineCapabilityMissTests.cs: one conflict in theWiringCallregex used byEngineCapabilityReadWiringTests. Kept this PR's version, which adds a negative lookahead so aNotCollectedStatusAsynccall's cancellation-token argument (added by Web /api/read/* handlers never thread RequestAborted into the underlying store query #4203) isn't mis-captured as the collector name — a strict superset of dev's regex needed once cancellation tokens started appearing in those calls.git diff origin/dev -- Darling/PerformanceMonitor.Darling.Service/DarlingWebEndpoints.cs | grep -E '^[-+]\s*"'): exactly 8 removals, no additions —get_active_queries,get_plan_corrections,get_query_heatmap,get_query_store_regressions,get_query_store_clutter,get_long_query_completions,get_top_procedures_by_cpu,get_top_queries_by_cpu. Matches this PR's own slice; the ratchet only shrank.dotnet build Darling/Darling.Tests/Darling.Tests.csproj -p:EnableWindowsTargeting=true→ Build succeeded, 0 Warning(s), 0 Error(s). Could not runWebReadCancellationPinTests,PgTargetMcpSurfaceTests,McpServiceParameterDiSeatCensusTests,EngineCapabilityReadWiringTests, orRuntimePreconditionReadWiringTestson this Mac host — the built test binary requiresMicrosoft.WindowsDesktop.App10.0.0, which isn't installed on this machine (onlyMicrosoft.NETCore.AppandMicrosoft.AspNetCore.App). CI decides on those.