Repository navigation
A cancelled page read gets the cancellation when the store connect times out first (#5678) - #5679
Conversation
The fix is per-site, but the thing that is type-gated is the backstopThe PR body's own "Backstop check" paragraph states the mechanism precisely:
That is the whole bug, in one filter — catch (Exception ex) when ((ex is OperationCanceledException or IOException)
&& context.RequestAborted.IsCancellationRequested)Every route that does not have a local catch therefore still hands a gone caller's non-
Plus three sites that do hand a sentence or an exception to a failure log with the request token in reach, and are absent from the table:
None of this says the six converted sites are wrong — the helper and its placement after the latency sample read correctly, and the table's reasoning about mute rules, the write slot, the admin read, catch (Exception ex) when (context.RequestAborted.IsCancellationRequested
&& ex is not BadHttpRequestException)That is a design call, not a one-line demand — but as it stands, the issue's symptom (a false failure line for a browser that left while the store connect timed out) is still reachable on Not blocking, for the recordThe two parts of #5678 are also asymmetric about where the guard sits. Part 1 puts |
There was a problem hiding this comment.
Verdict: CHANGES REQUESTED
The helper is right and its placement after the latency sample is right; the table's reasoning about what it left alone checks out. What does not hold is the completeness the new census asserts in its name: the decision "a gone caller is not a failure" lives in the observer's TYPE-gated filter (DarlingWebFailureObserver.cs:55-56), so routes without a local catch still write an Error line and a 500 body for exactly the #5678 shape, and three sentence/exception sites are not in the table. Detail in the top-level note and four inline comments.
Claims
- VERIFIED — a tool-caught sentence went on to the error answer and logged a line —
DarlingMcpFleetTools.cs:186-189returnsFormatError;ToHttpResult's ServerError arm (DarlingWebEndpoints.cs:6052-6056) callsServerErrorResult, which callsReport(:6085); the old catch at:412never saw it. - VERIFIED — "The latency sample was already right" —
ReadOutcomeClassifier.cs:66-71returnsCancelledon a fired token, and the helper runs after both recorders (DarlingWebEndpoints.cs:449-454). - VERIFIED — "
ServerTagToolResult… All 5 callers pass it" —DarlingWebEndpoints.cs:2018, 2041, 2060, 2079, 2098. - VERIFIED — mute-rule routes unchanged, the cores take no token —
:960, :977, :1012, :1028. - VERIFIED — server add/edit/admin read take no request token —
:1097(CancellationToken.None),:1455-1456. - VERIFIED —
/api/alert-templatesand/api/alerts/testtake no token —:833,:886. - VERIFIED — the Backstop check paragraph —
DarlingWebFailureObserver.cs:55-62(quiet arm),:75-105(the onlyReportcalls),:111-118(tail check gated on>= 500). - VERIFIED — part 1's two catches —
DarlingServerResolver.cs:140andWebDataStartNote.cs:749, the latter insideAddNoteAsync, whichAddAsync:609awaits outside its owntry. - VERIFIED — four new cases, the helper's rule tested on its own —
DarlingWebFailureHandlingTests.cs:466, 505, 528, 544;:528covers all three branches ofThrowIfCallerGone(DarlingWebEndpoints.cs:6072-6078). - UNVERIFIED — the suite counts (113 / 1059 / 593, 0 warnings) and the RED-plant — needs a build; this tree has no restored packages (
Darling/Darling.Tests/objabsent). - UNVERIFIED — "the driver, given a token that is already cancelled, can throw a connect timeout instead of a cancellation" — the premise of the change; a property of the pinned Npgsql version's connect path, provable only by running
ASpentConnectBudget_ThrowsATimeout_NotACancellation_UnderACancelledToken. - UNVERIFIED — "The store spends its 1 second connect budget deterministically" — the mechanism is
Thread.Sleep(1200)in an Npgsql logger racingTimeout=1(DarlingWebFailureHandlingTests.cs:456-462); determinism on a loaded runner needs repeated live runs. - REFUTED — the census's summary, "every web handler that hands a tool's string, or a store fault, to a failure log with the request token in reach ends a gone caller's request first" —
DarlingTriageEndpoint.cs:822(and705/708),AlertNotebookEndpoint.cs:162-164and:187-189, andDarlingWebEndpoints.cs:1837-1839all threadcontext.RequestAbortedinto the read, catchwhen (ex is not OperationCanceledException), and callReport. The pin checks fiveIndexOfwindows and sees none of them.
Riskiest lines
Darling/PerformanceMonitor.Darling.Service/DarlingWebEndpoints.cs:454— every/api/read/*answer now ends in a throw rather than a body when the token fired and the result is a ServerError sentence, a genuine 57014 formed before the user navigated away included — pinned byAToolCaughtFailure_ForABrowserThatLeft_WritesNoFailureLine, withAToolCaughtFailure_ForALiveBrowser_StillAnswersServerError_AndWritesOneLineas the control.Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingServerResolver.cs:140— a shared seam:ResolveOrErrorAsyncis the resolve-and-bail call in ~190 MCP tools and inDiagnosticsBundleRunner.cs:255, all of which now get a throw where they got a sentence — pinned for the seam byTheRegistryRead_SurfacesCancellation_WhenTheConnectBudgetIsSpent; UNPINNED for any caller.Darling/PerformanceMonitor.Darling.Service/DarlingTriageEndpoint.cs:806— makesRunSectionAsyncthrow against its own doc at:763("Never throws: a broken section is one card on the page, not a dead page") and leaves the sibling catch at:822logging the PR's own exception shape — UNPINNED behaviourally; only the source pin atDarlingWebFailureHandlingTests.cs:561-563covers it.
Traps near this diff
- "A roster-based census does not see the file it does not roster:
EveryPagedTool_ObservesTruncation_…walked ten tools and missed three in another file (#3699). An anti-pattern census sweeps the class (every tool body); rosters are for the positive contract only." — applies —DarlingWebFailureHandlingTests.cs:541-564is five lookups over three files under a universal name; the misses are under Claims. - "A pin that encodes the lie … the
>= limitinference as its own proof (#3699). Ask what a pin would fail ON." — does not apply — the control atDarlingWebFailureHandlingTests.cs:505asserts the status and the one line a live token still gets, andWebDataStartNoteEventReadsTests.cs:76-85pins the premise separately from the behaviour. - "A test that reads the clock twice measures the CI scheduler … One anchor owns both ends of an interval (#3676)." — does not apply — nothing measures or asserts an interval; the assertions are exception types (
WebDataStartNoteEventReadsTests.cs:84). The wall-clock dependence sits in the setup; see the checklist. - "Lite and Darling ship in parity (CONTRIBUTING's two-store rule): a fix to one SKU's reader, tool, alert or description without the counterpart's is a finding" — does not apply — Lite ships no HTTP surface (
grep -rl RequestAborted Lite/returns nothing) and every changed file is underDarling/PerformanceMonitor.Darling.Service/. - "An expression-bodied member holding a
{ }property pattern is read SHORT by the T-SQL convention guard's member walk (#3667)" — does not apply — the new member is block-bodied with no property pattern (DarlingWebEndpoints.cs:6072-6078) and the changed signature stayed a block body (:2292-2296). - The SQL traps (ORDER BY ordinals, integer-division rates, page denominators,
>= limittruncation) — do not apply — no SQL changed;DarlingServerResolver.LoadEnabledServersSql(:74-78) is untouched and no collector query is in scope.
Divergence (blind read vs PR body)
- The table enumerates what was judged safe, not what logs. Routes with no local catch are absent, yet by the body's own Backstop reasoning they are the largest hole:
/api/ag/count(DarlingWebEndpoints.cs:375-380→DarlingAgReader.cs:114→DarlingAgStatesReader.cs:331, no catch in the chain),/api/fleet,/api/ag,/api/views/*,/api/alerts/*. Three sites of the table's own shape are also absent: the triage binding catch, all ofAlertNotebookEndpoint, and/api/alert-history/dismiss. - "
/api/compose/run… when the outcome is a store fault or a server error" —ComposeRunFailureResulthas three logging arms. The author-SQLSTATE Warning at:3291-3299is reachable withFault == nullandIsServerError == false(FromRunException's remap arm,:2581-2585), so the guard at:684lets it log for a gone caller. - "the data-start note read logged or answered a fault for a caller who was gone" — that probe only ever logged at Debug (
WebDataStartNote.cs:751-753), so part 1 there is about not answering, not a false line. The registry half lands only where the token is threaded:DarlingTriageEndpoint.cs:618andAlertNotebookEndpoint.cs:102pass none, so a gone caller's registry fault still reachesReportat:629/:650and:110.
Reviewer checklist
- Run the four new cases and
WebDataStartNoteEventReadsTestsrepeatedly on a loaded runner: everything rests on aThread.Sleep(1200)in Npgsql's logger outlasting aTimeout=1budget, and on this Npgsql version not checking the token before the connect; the sleep also blocks a pool thread under xUnit parallelism. - On a live host, abort an
/api/ag/countor/api/fleetread against a store slow to accept connections and see whether an Error line and a 500 body still appear — the residual case this shape cannot reach. - On a live host, abort a
POST /api/server-tagswhose core then fails and confirm an operator can still tell the write failed (LogServerTagWritereturns early for a non-2xx envelope at:2145-2148, and theServerErrorResultline is now suppressed). -
UnreachableStore()/ConnectBudgetSpent()build anNpgsqlDataSourcethe web tests never dispose (only theTestServeris in ausing).
… failure log, by the request token (#5678) The backstop and DarlingWebFailureLog.Report now key on the request token, not the exception type. Report takes a required token and writes one Debug line for a caller who left. The untokened resolver calls get the token, the triage section runner no longer throws, compose ends any gone caller's failed run, and the server-tag writes lose their guard. Guard pins are deterministic seams, not a timing race.
Claude posting for Erik Darling. Stale: every finding is fixed in 95c9e36 and each thread has a reply. The round-2 review on that head reached its turn limit without a verdict.
|
Claude posting for Erik Darling. This answers item 2 under "Riskiest lines" in review round 1, about the resolver change that reaches every MCP tool and the diagnostics bundle. No caller gets a new kind of outcome. |
Round 2 reads as the right shape; two of the body's sentences do not holdRound 1's finding is genuinely fixed, and fixed in the place that makes the per-site conversions redundant rather than load-bearing: I walked the site-list table row by row against Two sentences in the body are not true of this tree. 1. "Triage 2. "The changelog line now says no failure is logged. It adds that a store timeout arriving after the browser left leaves one Debug line." No changelog file is in this PR ( Three non-blocking notes are inline: the Everything else I checked held: the |
There was a problem hiding this comment.
Verdict: CHANGES REQUESTED
Round 1's finding is fixed, in the one place that covers the routes it named. Item 4 does not hold: RunSectionAsync still propagates a gone caller's cancellation against the doc the body says it restored, and the pin named for that property cannot fail on it. Detail in the top-level note and three inline comments.
Claims
- VERIFIED — the quiet arm is now the token, not the exception type —
DarlingWebFailureObserver.cs:53-54, 64; pinned byIsCallerGone_IsTheTokenNotTheType_ExceptForABadHttpRequest(DarlingWebFailureHandlingTests.cs:540-550) andTheBackstop_WritesNoLineAndNoBody_ForAStoreTimeoutUnderACancelledToken(:218-236) with the live-token Theory control at:244-257. - VERIFIED — both
Reportoverloads take a required token, write one Debug line, and still mark the request reported —DarlingWebFailureLog.cs:223-237, 261-273(MarkReported()precedes the check on both);Report_UnderACancelledToken_WritesOneDebugLine_AndNoWarningOrError(:556-575). - VERIFIED — the two untokened
ResolveOrErrorAsynccalls now get the request token —DarlingTriageEndpoint.cs:618,AlertNotebookEndpoint.cs:102. - VERIFIED — item 5 covers the compose author-error Warning — the guard at
DarlingWebEndpoints.cs:686-689sits on the only path tologger.LogWarningat:3297. - VERIFIED —
ThrowIfCallerGoneremoved fromServerTagToolResult, its optional parameter now a required token —DarlingWebEndpoints.cs:2295-2304, pinned atDarlingWebFailureHandlingTests.cs:604-607. - VERIFIED — the site-list table, row by row — every
Report(call and all seven helper call sites match the stated token, counts included (server databases 3, triage 6, notebook 9); bothCancellationToken.Nonerows hold (DarlingFleetSweepEndpoints.cs:265, 272pass a null logger;DarlingWebEndpoints.cs:1442forbids theHttpContext). - VERIFIED — "two direct
LogWarnings remain" — a sweep of the five web-surface files returns exactlyDarlingWebEndpoints.cs:1493and:3297. - VERIFIED — the
Thread.Sleep(1200)tests are gone and the test server's data source is disposed — noThread.Sleep(1200)/ConnectBudgetSpent/AToolCaughtFailure_ForABrowserThatLeftsurvives;BuiltServerowns it (DarlingWebFailureHandlingTests.cs:173-200, 293). - VERIFIED —
ThrowIfCallerGone's doc claim that the latency sample is already right —ReadOutcomeClassifier.cs:66-71returnsCancelledon a fired token; the call sits after both recorders (DarlingWebEndpoints.cs:449-454). - UNVERIFIED — every suite count, "0 warnings", and both RED-plants — needs a restored tree and a .NET SDK, neither reachable here.
- UNVERIFIED — "the driver can throw a connect timeout instead of a cancellation under an already-cancelled token" — still the premise; the tests now hand-build that exception, so nothing in the tree demonstrates it on the pinned Npgsql version.
- REFUTED — "the throw added in part 2 is removed, so its doc (
Never throws) is true again" — read tools resolve outside their owntry(DarlingMcpDataTools.cs:79, 116), soFaultOrCancel's new throw (DarlingServerResolver.cs:149) leaves the tool, andRunSectionAsync's filter excludesOperationCanceledException(DarlingTriageEndpoint.cs:820). A gone caller whose section hits a connect timeout ends the whole triage page, not one card. - REFUTED — "The changelog line now says no failure is logged…" — no changelog file is in the diff, and
5678/caller_gone/browser left/Debug lineappear nowhere inCHANGELOG.mdordocs/changelog/3.10.md(newest entry #5637); the #4281 line referenced is in the releaseddocs/changelog/3.9.md:485. The splice lane (build.yml:777-783) may own it, so a wrong sentence rather than a missing deliverable.
Riskiest lines
Darling/PerformanceMonitor.Darling.Service/Hosting/DarlingWebFailureObserver.cs:53— widens a silent swallow from two exception types to every type but one, so a server-side bug coinciding with a fired token leaves no line at any level on the no-local-catch routes — pinned byIsCallerGone_IsTheTokenNotTheType_ExceptForABadHttpRequestandTheBackstop_WritesNoLineAndNoBody_…; the cancelled-token-plus-BadHttpRequestExceptioncase is UNPINNED.Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingServerResolver.cs:149— a fault sentence becomes a throw at the seam ~130 MCP and web sites share; I re-read all of them and each sits under awhen (ex is not OperationCanceledException)catch or routes to the observer — pinned for the seam byAFailedRegistryRead_SurfacesCancellation_ForACancelledCaller_AndTheFaultSentenceForALiveOne, UNPINNED for any caller, and the mechanism behind the REFUTED triage claim.Darling/PerformanceMonitor.Darling.Service/DarlingWebEndpoints.cs:454— an/api/read/*answer now ends in a throw rather than a body whenever the token fired and the result is a ServerError sentence, a genuine 57014 formed before the caller left included — pinned byThrowIfCallerGone_OnlyEndsAServerErrorSentence_ForACancelledToken(:579-589) and the source pin at:597-607; UNPINNED end-to-end.
Traps near this diff
- "A pin that encodes the lie … the
>= limitinference as its own proof (#3699). Ask what a pin would fail ON." — applies —TheTriageSectionRunner_DoesNotThrowForAGoneCaller(DarlingWebFailureHandlingTests.cs:624-634) is oneAssert.DoesNotContainover the method's own source span, named for a behaviour that arrives fromDarlingServerResolver.cs:149one frame down, which the pin cannot see. - "A roster-based census does not see the file it does not roster … rosters are for the positive contract only." — does not apply — the roster census round 1 faulted is gone; the positive contract is the compiler (
DarlingWebFailureLog.cs:223, 261) and the four survivors each name one wiring spot. The compiler enforces that a token is passed, not the request's. - "Lite and Darling ship in parity (CONTRIBUTING's two-store rule): a fix to one SKU's reader, tool, alert or description without the counterpart's is a finding." — does not apply — Lite ships no web dashboard and no counterpart to
DarlingWebFailureLog(a grep for it overLite/andLite.Tests/is empty); its only HTTP surface is the MCP transport atLite/Mcp/McpHostService.cs:121, with no request-scoped failure log. - "An expression-bodied member holding a
{ }property pattern is read SHORT by the T-SQL convention guard's member walk (#3667)." — does not apply — the one new expression-bodied member holds a type pattern (DarlingWebFailureObserver.cs:53-54); the other three new methods are block-bodied. - "A test that reads the clock twice measures the CI scheduler (#3676)." — does not apply, and this round removed it — the new seams take a hand-built exception and token (
DarlingServerResolver.cs:147,WebDataStartNote.cs:755) and nothing in the diff measures an interval. - The SQL traps (
ORDER BYordinals, integer-division rates, page denominators,>= limittruncation) — do not apply — no SQL changed;DarlingServerResolver.LoadEnabledServersSql:74-78is untouched.
Divergence (blind read vs PR body)
- Item 4 claims a restored contract that is not restored, and its test cannot fail on it — the blocking item.
- The final changelog sentence describes an edit nowhere in the tree.
- Item 1 is presented as preserving the old arms exactly. It also changes
BadHttpRequestException: that type derives fromIOExceptionand the old swallow arm ran first, so under a fired token a malformed request was swallowed with its status never set; the carve-out now routes it to:73-84, which answersbad.StatusCode. An improvement framed as a no-op, untested on that path. - Item 3 reads as logging-only, but passing the token to
ResolveOrErrorAsyncatDarlingTriageEndpoint.cs:618andAlertNotebookEndpoint.cs:102also makes those registry reads cancellable for the first time — the mechanism behind divergence 1. - "The write routes' only trace for a gone caller is the Debug line" holds for server-tag and mute-rule routes, not for the write routes with no local catch (
DarlingWebEndpoints.cs:572, 613, 755), which reach the observer's quiet arm and leave nothing.
Reviewer checklist
- Build and run
Darling.Tests: every count, the "0 warnings" claim and both RED-plants are unverifiable here, and four source pins'IndexOfwindows shift with the new hunks. - On a live host, abort an
/api/triageread while the store is slow to accept connections and confirm the log is quiet and the page simply ends — nothing in the tree exercisesRunSectionAsyncunder a fired token. - On a live host, send a malformed or oversized body and abort it in the same breath; confirm the response carries the 413/400 rather than the old silent 200.
- Confirm the premise once on the pinned Npgsql version: a connect under an already-cancelled token throwing
NpgsqlException(TimeoutException), notOperationCanceledException. - Decide whether #5678 wants a
docs/changelog/3.10.mdentry here or in the splice lane, and correct the body's last sentence either way.
…he old meaning for a malformed request (#5678) RunSectionAsync catches anything a section's tool throws under a fired request token, so its Never throws doc holds (the read tools resolve the server outside their own try). The source pin is replaced by behavior tests. The backstop's rule is the token alone again, so a BadHttpRequestException under a fired token is swallowed as before. The unreachable registry guard in the server databases route is removed.
Claude posting for Erik Darling. Stale: every round-2 item is fixed or answered in 0a81b54, each inline thread has a reply and is resolved, and the PR body answers the summary items. The follow-up review on that head reached its turn limit without a verdict.
Claude posting for Erik Darling.
Fixes #5678.
Why
When a browser leaves a page mid-read, its request token is cancelled. The store driver, given a token that is already cancelled, can throw a connect timeout instead of a cancellation. Code that only skips real cancellations then treats the person who left as a failure. It writes an Error line and a 500 for a request nobody is waiting on.
The first version of this fix patched one site at a time and tested the exception type before the token. Routes with no local catch were still affected (
/api/fleet,/api/ag,/api/ag/count,/api/views/*,/api/alerts/*). They wrote a failure line for a gone caller. The sites that callDarlingWebFailureLog.Reportdirectly were unguarded too. This round moves the rule to the two places every request passes through.What changes
Part 1 (earlier commit)
DarlingServerResolver.LoadEnabledOrFaultAsyncand the data-start note probe (WebDataStartNote) no longer turn a cancelled caller's connect timeout into a fault sentence or an unannotated grid. This round, both catches call a small method that takes the exception and the token. These areDarlingServerResolver.FaultOrCancelandWebDataStartNote.ProbeFailed. The rule is tested on a hand-built exception, with no connection attempt to time.Part 2 (earlier commit)
DarlingWebEndpoints.ThrowIfCallerGone(result, token)ends a gone caller's tool-caught "Error during ..." sentence with a cancellation, in the/api/read/*loop. It was also called in the server-databases route. Round 2 removes that call.This round (review round 1)
DarlingWebFailureObserver): the quiet arm is nowwhen (IsCallerGone(RequestAborted)). That is justRequestAborted.IsCancellationRequested. Round 2 dropped aBadHttpRequestExceptioncarve-out that round 1 had added. The token decides, not the exception type. Every exception the oldOperationCanceledException or IOExceptionfilter swallowed is still swallowed. That includesBadHttpRequestException, which derives fromIOExceptionand was swallowed before its own arm. The one addition is theNpgsqlExceptiona connect timeout throws. It writes no line and no body, as before.DarlingWebFailureLog.Report(both overloads) takes a REQUIRED request-token parameter, so a missed site is a compile error. When the token fired, it writes ONE Debug line instead of the Warning or Error. The line holds the route, the elapsed time and the kindcaller_gone. It also holds the exception type and SQLSTATE, or the same scrubbed sentence. It still marks the request as reported. A caller with no request in reach passesCancellationToken.Noneexplicitly.ResolveOrErrorAsynccalls (triage,DarlingTriageEndpoint.cs:618, and alert notebook,AlertNotebookEndpoint.cs:102) now get the request token. This does more than quiet a log line: those registry reads are now cancellable, so a gone caller's lookup stops. That is intended.RunSectionAsync: the throw added in part 2 is removed. Its twoReportcalls carry the token. Round 1 said this made its "Never throws" doc true again. The review showed that was wrong, and round 2 fixes it (see below)./api/compose/runends the request whenoutcome.Payload is null && !outcome.IsNotFound. That covers the panel author's own query error, whichComposeRunFailureResultlogs as a Warning.ThrowIfCallerGonestays in the/api/read/*loop (reads). It is removed fromServerTagToolResult, and its optional parameter is now a required token. The write routes' only trace for a gone caller is the Debug line from item 2.The functions that reach
Reportnow take the token too (ToHttpResult,ServerErrorResult,MuteRuleToolResult,ServerTagToolResult,ComposeRunFailureResult,DarlingWebStatementSweep.RefusalandJsonText).Site list
Every
Reportcall, and every helper that reaches it, with the token it passes:DarlingWebFailureObserver(bothReportcalls)context.RequestAborted/api/read/*loop catch andToHttpResultcontext.RequestAborted/api/compose/runcontext.RequestAborted/api/alert-templates(now takes theHttpContext),/api/alerts/testcontext.RequestAbortedcontext.RequestAborted/api/servers,/api/servers/{id}, admin read)context.RequestAborted/api/alert-history/dismiss)context.RequestAbortedRunInServerWriteSlotAsync, 2 sites)CancellationToken.None: the write core takes no request token on purpose, and the slot runs after the request is overcontext.RequestAbortedcontext.RequestAbortedcontext.RequestAbortedCancellationToken.None: no logger, no requestSearched the web handlers and the host for any direct
LogErrororLogWarningin a catch with the request token in reach. Two remain: the compose author-error Warning (covered by item 5) and the server-write-slot timeout Warning (about a held slot, not a caller).Test plan
NpgsqlExceptionunder a cancelled token writes no failure line and no body, and the test shows the route ran. The control, with a live token, writes one Warning and answers 503 (timeout), or one Error and answers 500 (refused connect).Reportunder a cancelled token writes Debug only, on both overloads, and marks the request as reported.IsCallerGoneand the two seams (FaultOrCancel,ProbeFailed) are tested directly on a hand-built exception and token. No timing: the oldThread.Sleep(1200)logger racingTimeout=1is gone, with the three tests that rested on it.ServerTagToolResulthas no guard. The compiler now enforcesReport. The triage runner is covered by behavior tests (round 2), not a source pin.TheBackstop_WritesNoLineAndNoBody_ForAStoreTimeoutUnderACancelledTokenfailed, nothing else did. Restored.Report).Report_UnderACancelledToken_WritesOneDebugLine_AndNoWarningOrErrorfailed, nothing else did. Restored.DarlingWebFailureHandlingTests,DocCommentHygieneTests: 116 passed, 0 failed.*Census*and*Ratchet*class and the web exception text census. It also covered theWebDataStartNote*, triage, alert notebook, compose, server tag and statement filter classes. The endpoint, mute rule, fleet sweep and server databases classes ran too.DarlingWebFailureLog.csclosed an old unmatched/*in a doc line. A parameter name in the registry seam missed the census allow-list entry.Round 2 (review round 2)
try(DarlingMcpDataTools.cs:79, 116). So a cancellation escaped the tool and thenRunSectionAsync. So did a connect timeout thatFaultOrCancel(DarlingServerResolver.cs:149) turned into one.RunSectionAsync(DarlingTriageEndpoint.cs:821) has a new first catch,when (requestContext.RequestAborted.IsCancellationRequested). It writes one Debug line throughReportand no Warning or Error. It adds no error text and returns the section.internalso tests can call it.TheTriageSectionRunner_ReturnsQuietly_WhenASectionsToolThrowsForAGoneCallerhas two cases, a tool throwingOperationCanceledExceptionand one throwing the connect-timeoutNpgsqlException. Both throw outside any try of their own, under a cancelled request token. The control isTheTriageSectionRunner_StillLogsAndDegradesTheCard_ForTheSameThrowUnderALiveToken.internaladded): the two gone-caller cases failed, the first withSystem.OperationCanceledException : The operation was canceled., and 1 passed. GREEN with the fix: 3 of 3 pass.BadHttpRequestExceptionunder a fired token. Restored the old behavior in code.IsCallerGonetakes only the token, so the malformed-request type is swallowed again, as the old first arm did. The new testTheBackstop_StaysQuiet_ForAMalformedRequestUnderACancelledTokenpins it (status 200, no Warning, no Error). The live-token testBadHttpRequestException_AnswersItsOwnStatus_WritesNoBody_LogsAtDebugNeverErrorstill pins its own arm.ServerError-shaped stringResolveOrErrorAsyncreturns is the registry-fault sentence. Under a fired tokenFaultOrCancelthrows before that sentence is built (DarlingServerResolver.cs:149). I removed theThrowIfCallerGonecall and its comment inDarlingServerDatabasesEndpoint.cs. A comment on the store read's catch says why the lookup above needs none. The direct store read'sThrowIfCancellationRequestedstays, because that is the live connect-timeout case. The two latercontext.RequestAbortedarguments are left alone. They are harmless and keep the call shape the same as the other routes.DarlingWebFailureObserver.cs:53): kept on purpose. ASP.NET Core's own exception handler makes the same call for a client abort. The old arms were equally silent forOperationCanceledExceptionandIOException. The addition is the connect-timeoutNpgsqlException, whose cause here is the caller leaving./api/__test/abort,abort-ioand the store-timeout test.DarlingServerResolver.cs:149): the review re-read every caller. It found each one under awhen (ex is not OperationCanceledException)catch, or routed to the observer. The one that was not safe, the triage section runner, is fixed above with a behavior test. The seam itself is pinned byAFailedRegistryRead_SurfacesCancellation_ForACancelledCaller_AndTheFaultSentenceForALiveOne./api/read/*throw (DarlingWebEndpoints.cs:454): with a fired token nobody receives the body. Ending in a throw instead of a body changes nothing a caller can see. A 57014 formed before the caller left also ends there. Its latency sample is recorded first (:449-453), andReadOutcomeClassifier.ClassifySentencesays Cancelled for a fired token. So the sample is still right. It is pinned byThrowIfCallerGone_OnlyEndsAServerErrorSentence_ForACancelledTokenand the source pin on the loop.Round 2 tests:
DarlingWebFailureHandlingTestsandWebDataStartNoteEventReadsTests: 55 passed, 0 failed.Round 2 handoff: done, items 1 to 6, pushed as 0a81b54. Not done: nothing.