Skip to content

Thread cancellation through 8 object-stats and config-history web reads (#4203) - #4372

Merged
erikdarlingdata merged 2 commits into
devfrom
fix/4203-web-read-cancellation-7
Sep 26, 2026
Merged

erikdarlingdata merged 2 commits into
devfrom
fix/4203-web-read-cancellation-7

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Refs #4203.

Why

/api/read/{tool} requests for 8 object-stats and config-history reads ran their store
query to completion even after the caller abandoned the request. CancellationAllowlist
listed all eight because their MCP tool methods had no CancellationToken parameter to
receive HttpContext.RequestAborted.

What changes

DarlingMcpObjectStatsTools.cs: GetTableIndexSizes, GetIndexUsage, GetObjectLocking,
GetDatabaseSizes each gain a trailing CancellationToken cancellationToken = default,
threaded into every DarlingObjectStatsReader/DarlingServerResolver/DarlingEngineCapability
call in the body, with catch (Exception ex) when (ex is not OperationCanceledException).

DarlingMcpConfigHistoryTools.cs: GetServerConfigChanges, GetDatabaseConfigChanges,
GetTraceFlagChanges, GetDatabaseScopedConfig get the same treatment (GetQueryStoreHealth
was already converted in a prior lane and is unchanged here).

DarlingWebEndpoints.cs: all 8 names removed from CancellationAllowlist; their dispatch
entries now pass cancellationToken: c.RequestAborted.

No shared-helper signature changed (the reader methods already took a trailing
CancellationToken cancellationToken = default); only the tool methods and dispatch entries
were touched.

Test plan

  • dotnet build Darling/Darling.Tests/Darling.Tests.csproj -p:EnableWindowsTargeting=true: 0 warnings, 0 errors.
  • dotnet build Lite.Tests/Lite.Tests.csproj -p:EnableWindowsTargeting=true: 0 warnings, 0 errors.
  • WebReadCancellationPinTests (Windows-only, CI runs it) is the generic ratchet for this
    whole surface, not tool-specific, so no new pin was needed:
    • AConvertedReadEndpoint_ObservesCancellation_InsteadOfDialingTheStore is a [Theory]
      over every dispatch entry not on CancellationAllowlist. Against the pre-fix code these
      8 names were still on the allowlist, so the theory never ran a case for them (RED case:
      if this PR's dispatch-entry edit were dropped for any of the 8 while the name stayed off
      the allowlist, that tool's case would throw NpgsqlException against the dead-port store
      instead of OperationCanceledException, failing the assert).
    • EveryStoreReadingToolMethod_TakesACancellationToken_UnlessAllowlisted reflects every
      [McpServerTool] method on the read surface not on the allowlist and asserts it has a
      CancellationToken parameter. Before this PR all 8 were allowlisted so this test was
      silent about them; after this PR, if any of the 8 methods lost its CancellationToken
      parameter, this test fails naming it.
    • CancellationAllowlist_HasNoStaleEntry asserts no allowlist entry's tool method already
      has a CancellationToken parameter. Before this PR the 8 entries were consistent (no
      token, on the allowlist); this PR's removal keeps that invariant, and if any of the 8
      were left on the allowlist after gaining a token, this test would fail naming it.
    • All three pins are Windows-only (net10.0-windows target) and build clean here; CI decides
      them, listed unchecked in this environment for that reason only.
  • PgTargetMcpSurfaceTests and McpServiceParameterDiSeatCensusTests were checked by
    inspection: neither references any of the 8 tool names or asserts a specific parameter
    list, so neither is affected by this change. EngineCapabilityReadWiringTests and
    RuntimePreconditionReadWiringTests (named in the brief) were not found in the tree under
    those names; the closest matches (EngineCapabilityMissTests, others) do not reference
    these 8 tools either.

For the coordinator

CancellationAllowlist count: down by 8 from this PR (unioned with the sibling #4203 slices
running in parallel on the same file — the tender resolves any textual conflict on
DarlingWebEndpoints.cs, not this lane). No shared-helper signature changed.

CHANGELOG entry

SECTION: Fixed
ENTRY:

pm-pr lane report (dev merge + verification)

New head SHA

f457cccf (merge commit f457cccf... on fix/4203-web-read-cancellation-7, parent PR head 62cb3b7bfa5ed869e8b5840b0af54836af110e15 + origin/dev at e12cdc96). Pushed to origin/fix/4203-web-read-cancellation-7.

Confirmed the PR head was still 62cb3b7b (via gh-pm.sh pr view 4372 --json headRefOid) immediately before merging and before pushing.

Conflicts and resolution

Exactly one conflicted file: Darling/PerformanceMonitor.Darling.Service/DarlingWebEndpoints.cs, one hunk, inside CancellationAllowlist's literal set.

Resolution: dropped both removed groups (this PR's 8 + the siblings' 7), kept every other line as-is (both "keep the other side's stuff" halves are automatic since neither side actually re-added anything — the conflict was purely "each side's own already-removed lines reappearing in the other side's context"). No c.RequestAborted dispatch-table lines conflicted; those merged cleanly (git resolved that half automatically, confirmed no <<<<<<< markers remain anywhere in the file after resolution).

Two other files (DarlingMcpBlockingTools.cs, DarlingServerResolver.cs) auto-merged clean, no manual edits.

Allowlist diff: 94 → 86

Verified by diffing the quoted-name set at origin/dev (94 entries) against the merged working tree (86 entries), against the 3-way base (101 entries at git merge-base 9ba47523).

  • base (101) − this-PR's-own-removals (8) − dev-siblings'-removals (7) = 86 — computed set matched the actual merged-file set byte-for-byte (diff clean).
  • The 8 removed by this PR, confirmed present at origin/dev (94) and absent from the merged result (86): get_database_config_changes, get_database_scoped_config, get_database_sizes, get_index_usage, get_object_locking, get_server_config_changes, get_table_index_sizes, get_trace_flag_changes.
  • Nothing added: merged set ⊆ base set, confirmed empty comm -13 (no name in the merged file that isn't in the 3-way base).

Build

dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug → Build succeeded. 0 Warning(s). 0 Error(s).

Checks

1. Allowlist (94→86, exactly the 8 named tools, nothing added): PASS. See diff above.

2. All 8 tool methods take CancellationToken and pass it to every store call, no CancellationToken.None:
PASS. grep -c CancellationToken.None on both files returned 0. Read every method body in DarlingMcpObjectStatsTools.cs (GetTableIndexSizes, GetIndexUsage, GetObjectLocking, GetDatabaseSizes) and DarlingMcpConfigHistoryTools.cs (GetServerConfigChanges, GetDatabaseConfigChanges, GetTraceFlagChanges, GetDatabaseScopedConfig) — every await (ResolveOrErrorAsync, the reader call, NotCollectedStatusAsync, match-count helpers) passes cancellationToken. No remaining CancellationToken.None anywhere in either path.

3. Catch blocks narrowed to when (ex is not OperationCanceledException): PASS. All 8 methods' single catch block reads catch (Exception ex) when (ex is not OperationCanceledException).

4. Census/pin tests:

  • PgTargetMcpSurfaceTests / McpServiceParameterDiSeatCensusTests: reflection-driven, discover tool classes/registrations generically — no reference to specific tool names in this PR's scope, unaffected by the merge. PASS (by inspection; not name-coupled).
  • EngineCapabilityReadWiringTests (in EngineCapabilityMissTests.cs): regex-scans NotCollectedStatusAsync(...) calls in every MCP source file, tolerant of a trailing cancellationToken argument (comment explicitly cites Web /api/read/* handlers never thread RequestAborted into the underlying store query #4203). All 8 tools' NotCollectedStatusAsync calls end with cancellationToken. PASS.
  • RuntimePreconditionReadWiringTests (in RuntimePreconditionMissTests.cs): regex-scans RuntimePrecondition.StatusAsync(...) calls — none of these 8 tools use that helper (they use NotCollectedStatusAsync), so this test is unaffected either way. PASS (not exercised by this PR's tools).
  • WebReadCancellationPinTests: read in full. Half 1 drives DarlingWebEndpoints.BuildReadDispatch().Keys.Except(CancellationAllowlist) against a dead-port store + already-cancelled context, asserting OperationCanceledException; half 2 reflects every [McpServerTool] method with an NpgsqlDataSource param on the read surface and asserts a CancellationToken param unless allowlisted; a third fact (CancellationAllowlist_HasNoStaleEntry) asserts no allowlist entry already has a converted method. All three are driven off the code, not a fixed list — with the 8 tools removed from the allowlist and each now carrying a CancellationToken parameter wired to RequestAborted in the dispatch table (verified: c.RequestAborted passed for all 8, e.g. get_database_sizes, get_index_usage, etc.), all three should now pass. PASS (by code inspection; could not execute — dotnet test on this macOS box refuses net10.0-windows test target: "Testing with VSTest target is no longer supported... .NET 10 SDK", and the project itself targets net10.0-windows).

5. CHANGELOG entry: PASS-with-note. The ## CHANGELOG entry block is one Fixed bullet, true to the diff, labelled [#4372] linking pull/4372 correctly, listing the same 8 tool names this merge preserved. Refs #4203. is NOT inside the CHANGELOG bullet itself — it's the PR body's opening line (line 1), outside the CHANGELOG section. If the brief means "Refs #4203" must be inside the entry text, it currently isn't; the body was not edited per instructions. Correct text, if it needs to move inside the entry: append Refs #4203. to the ENTRY line before the REF: line.

READY

…ds (#4203)

get_database_sizes, get_index_usage, get_object_locking, get_table_index_sizes
(DarlingMcpObjectStatsTools) and get_database_config_changes, get_database_scoped_config,
get_server_config_changes, get_trace_flag_changes (DarlingMcpConfigHistoryTools) now take a
CancellationToken and pass it to every store call, so an abandoned web request stops its
query instead of running to completion. Removes all eight from CancellationAllowlist.
…ellation-7

# Conflicts:
#	Darling/PerformanceMonitor.Darling.Service/DarlingWebEndpoints.cs
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 26, 2026 01:52
@erikdarlingdata
erikdarlingdata merged commit a0a1883 into dev Sep 26, 2026
16 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4203-web-read-cancellation-7 branch September 26, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant