Repository navigation
RAISE-shaped deadlock and plan-capture records are not stored (#4058) - #4153
Merged
Merged
Conversation
Owner
Author
Code review round 1 (automated reviewer): #4153Verdict: changes requested. Critical
Warnings
Confirmed
Fixes are in progress on this branch. A live test is being added in parallel. |
Adds Darling/Darling.Tests/PgRaiseShapedRecordsLiveTests.cs, gated on DARLING_TEST_PG_CSVLOG, proving against the collectors' own csv statement plus ReadAsync: 1. A PL/pgSQL RAISE EXCEPTION shaped as a deadlock (message, DETAIL, ERRCODE 40P01) is skipped by PgDeadlocksCollector and counted under raise_shaped_deadlocks_skipped. 2. A REAL deadlock inside a function frame still comes out, matched to the victim's own pg_backend_pid(). 3. A PL/pgSQL RAISE LOG shaped as an auto_explain plan capture is skipped by PgPlanCaptureCollector and counted under forged_captures_skipped.
…ql innermost-frame parse - CollectorMeasurementSeamTests: add PgDeadlocksCollector.RaiseShapedDeadlocksSkippedMeasurement to the fixed label list (review round 1, critical item). - PgPlanCaptureCollector.PlanRowsFromCsvEntries: move the RAISE-shaped provenance guard BELOW the no-plan-text (msIndex < 0) skip, so a genuine log_min_duration_statement/log_duration record under verbose logging is skipped uncounted rather than counted as forged (review round 1 of #4137's ruling). Add a unit test for that ordering. - ForgedCaptureNote: name RAISE-shaped captures explicitly. - PgLogEntryProvenance.RaisedByPlpgsql: replace the first-physical-line " at RAISE" suffix test with a quote-aware parse of the innermost Context frame (function signature or inline_code_block, then " line N at KIND"), closing the bypass where a quoted function name containing a newline or " line 1 at SQL statement"-shaped text could defeat the old first-line split. Part of #4058.
…t a DO block (its RAISE failed open); the innermost-frame parser's unit tests
…loses both signals, and that a translated lc_messages fails open; a genuine duration record's Location is exec_simple_query/exec_execute_message
erikdarlingdata
marked this pull request as ready for review
September 24, 2026 11:11
erikdarlingdata
enabled auto-merge (squash)
September 24, 2026 11:11
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.
#4058 r3: tests for RAISE-shaped record detection
Tests only, added on top of r2's product code (head
03e734242). No product code changed except the one allowedInternalsVisibleToline.What was added
PgLogEntryProvenanceTests(8 tests, all pass) —RaisedByPlpgsql/ReportedByOtherover hand-builtPgLogEntryvalues: first-line " at RAISE" suffix true, later-line-only false, CRLF-terminated first line true, null/empty context false; empty Location false,Locationmatching the expected function false,Locationnamingexec_stmt_raisetrue.PgDeadlockRaiseShapedUnitTests(5 tests, all pass) —FromEntry/IsRaiseShapedover hand-built entries: a real deadlock raised inside a PL/pgSQL function (Context ends " SQL statement") is kept; a Context ending " at RAISE" nulls it; aLocationofexec_stmt_raisenulls it. PlusPgDeadlocksCollector.ReadAsync's csvlog branch over a body with one RAISE-shaped deadlock record and one real one: one row survives, andraise_shaped_deadlocks_skipped= 1; a separate test proves an unrelated RAISE ERROR (not deadlock-shaped) is not counted.PgPlanCaptureRaiseShapedUnitTests(2 tests, all pass) —PlanRowsFromCsvEntriesover a LOG plan-shaped entry with Context ending " at RAISE": no row,forgedCaptures= 1; a genuine capture with empty Location and no Context: one row, 0 forged.RdsDeadlockIngestorTests.ARaiseShapedDeadlock_IsCountedAndSkipped_WithNoStoreWriteAttempted(added to the existing suite, 23 tests total in that class, all pass) — a RAISE-shaped csv record over the FakeRds csvlog route yieldsoutcome.Rows == 0andoutcome.RaiseShapedSkipped == 1, against the dead store (so a store write would surface as a thrown exception, not a silent pass).All new csvlog fixtures were verified to parse to the correct 26-field shape before being trusted in an assertion (built and cross-checked column-by-column against
PgDeadlocksCsvlogTests' ownDeadlockRecord/CsvDeadlockRecordfixtures, not hand-guessed).Not done — live tests (item 5 of the brief), deferred
I ran out of budget before writing
PgRaiseShapedRecordsLiveTests(theDARLING_TEST_PG_CSVLOG-gated live suite: a RAISE EXCEPTION imitating a deadlock, a real two-session PL/pgSQL deadlock, and a RAISE LOG imitating an auto_explain capture, against a live pg18 rig). No rig was started for this session. This is real coverage the brief calls for and should be picked up as a fast follow — the unit-level coverage above proves the logic but not the live shape assumptions (real csvlog column layout, realLocation/Contexttext PostgreSQL actually writes).What I ran
dotnet build Darling/Darling.Tests/Darling.Tests.csproj -p:EnableWindowsTargeting=true— green after each edit.PgLogEntryProvenanceTests: Total 8, Failed 0PgDeadlockRaiseShapedUnitTests: Total 5, Failed 0PgPlanCaptureRaiseShapedUnitTests: Total 2, Failed 0RdsDeadlockIngestorTests: Total 23, Failed 0*PgDeadlock*/*PgPlanCapture*/*RdsPlan*/*PgServerLog*/CollectorMeasurementSeamTests/CollectionLogDrainForensicsStoreTests/DocCommentHygieneTestsas a batch, nor the live suite (no rig started) — hit the context-wall notice partway through and am stopping to commit/push/report per instructions.Coordinator verification (branch head d53f133)
inline_code_blocktook the DO-block branch and failed open. It's fixed, and a mutation check proved the test catches it;lc_messagesfailing open, and the genuine duration record's Location.PgRaiseShapedRecordsLiveTestspasses (3/3). A forged deadlock is skipped and counted, a real deadlock through a PL/pgSQL function is kept, and a forged plan capture is skipped and counted as forged.DARLING_TEST_PG_CSVLOG, soPgRaiseShapedRecordsLiveTestsskips in CI (and so do the other csvlog live classes). The coordinator's rig run above is its only live proof. Re-run it on a csvlog rig after any change to these routes.*PgDeadlock*(47),*RdsDeadlock*(24),*RdsPlan*(18),*PgServerLog*(32),*PgLogEvents*(70),*PgPlanCapture*,*PgLogEntryProvenance*(21),*RaiseShaped*, CollectorMeasurementSeamTests (35), CollectionLogDrainForensicsStoreTests, LiveCleanupConversionRatchetTests and DocCommentHygieneTests. The full Darling suite equals dev's baseline exactly, and the branch merges cleanly with dev.terseverbosity, and a non-Englishlc_messagesfor the context check.CHANGELOG entry
PgLogEntryProvenance,PgDeadlockLogParser.FromEntry/IsRaiseShaped,PgPlanCaptureCollector.PlanRowsFromCsvEntries,RdsDeadlockIngestor's csvlog route).Part of #4058.
Live proof
Darling/Darling.Tests/PgRaiseShapedRecordsLiveTests.cs(new file), gated onDARLING_TEST_PG_CSVLOG, against atimescale/timescaledb:2.28.1-pg18rig withlogging_collector=on,log_destination=csvlog,deadlock_timeout=100ms,compute_query_id=on. All three cases run against the collectors' own csv statement plusReadAsync, withPgReadBinaryFileGrantedcovered bothfalseandtrue:AFakeDeadlockRaisedByPlpgsql_IsSkippedNotStored— a PL/pgSQLRAISE EXCEPTIONshaped as a deadlock report(message, DETAIL, ERRCODE
40P01) is skipped byPgDeadlocksCollector, counted underraise_shaped_deadlocks_skipped.ARealDeadlockInsideAFunctionFrame_StillComesOut— two sessions calling a PL/pgSQL function in reversed roworder hit PostgreSQL's own deadlock detector; the real report still comes out and matches the victim's own
pg_backend_pid(), proving the check does not over-fire on a genuine deadlock reached through a function frame.AFakePlanCaptureRaisedByPlpgsql_IsSkippedNotStored— a PL/pgSQLRAISE LOGshaped as anauto_explainplan-capture line is skipped by
PgPlanCaptureCollector, counted underforged_captures_skipped.Ran the new class 3 times clean (
Total: 3, Failed: 0each run), thenLiveCleanupConversionRatchetTests(
Total: 15, Failed: 0) andDocCommentHygieneTests(Total: 77, Failed: 0). Container removed after the run.Not verified: the full suite (left to the coordinator per this lane's brief) and the RDS log-API route (this class
covers only the csvlog route, matching the existing
PgDeadlocksCsvlogLiveTests/PgPlanCaptureCsvLiveTestspattern it was modeled on).
One decision worth noting: the rig in the lane brief set
log_min_messages=log, which (severity ordering) filtersout ERROR-level rows including deadlock reports, so no deadlock ever reached the csv file under that setting. I ran
the rig with
log_min_messages=noticeinstead so ERROR rows are captured; this only affects the local rigconfiguration used to prove the test, not any product code.
Review round 1 fixes
Implements the review-round-1 items from the coordinator's brief on top of the existing r2/r3 commits
(head
c462a969c), merged with the parallel lane'sPgRaiseShapedRecordsLiveTests.csaddition.1. (Critical) Seam list
Added
PgDeadlocksCollector.RaiseShapedDeadlocksSkippedMeasurementtoCollectorMeasurementSeamTests' fixed label list. It was wired and measured already; only the gate'sexpected-label list was missing it.
2. (Warning) Real slow-statement records counted as forged under verbose logging
Moved the provenance guard (
PgLogEntryProvenance.RaisedByPlpgsql/ReportedByOther) inPgPlanCaptureCollector.PlanRowsFromCsvEntriesBELOW the "no plan text" (msIndex < 0) skip, per#4137's round-1 ruling: a
log_min_duration_statement/log_durationrecord carries the duration markerbut no plan text, and under verbose logging its
Locationisn'texplain_ExecutorEnd— running theguard first would have counted it as forged on every slow statement. Added
PlanRowsFromCsvEntries_ARealDurationRecord_WithNoPlanText_YieldsNoRow_AndDoesNotCountAsForged(a LOGduration record, no plan text,
Location: "exec_simple_query, postgres.c:1") proving no row andforgedCaptures == 0.3. (Warning) The context bypass: a newline inside a quoted function name
Replaced
PgLogEntryProvenance.RaisedByPlpgsql's first-physical-line test with a quote-aware parse ofthe innermost
Contextframe:"PL/pgSQL function "literal (or"inline_code_block"for a DO block); anythingelse returns false (fail open, same as before).
find the first unquoted
(and its depth-matched unquoted), so a quoted type name containing(or), or containing a literal newline or text shaped like theline N attoken, can no longer bemistaken for the frame boundary.
"inline_code_block"), requires" line ", then digits, then" at ",then reads the statement kind to the end of that physical line (CRLF-trimmed). Raise-shaped only when
the kind is exactly
"RAISE".errhidecontextremain uncaught — the remark still says "partial").New unit tests in
PgLogEntryProvenanceTestscover: a plain RAISE frame, a DO-block RAISE frame, thenewline-in-quoted-name bypass case, a quoted name containing
" line 1 at SQL statement"followed by areal
at RAISEframe, a quoted type argument containing), a real deadlock-in-function context whoseinnermost frame is an SPI
SQL statementframe, a RAISE-in-subquery case whose innermost frame is an SPISQL expressionframe, null/empty, and garbage — all per the brief's list.4. The plan note
Added one sentence to
PgPlanCaptureCollector.ForgedCaptureNote: a capture raised by a PL/pgSQL RAISErather than auto_explain is also counted here (#4058). No test pins the note's literal text (callers
reference the const, not a copy), so nothing else needed updating.
5. The stderr deadlock routes
No code change needed here —
PgDeadlockLogParser.cs(lines ~325-337, already on this branch from r2)already runs the RAISE-shaped check on the self-hosted stderr route's own candidate entry, ahead of
BuildFromEntry, with a comment stating it fails open when a HINT companion pushes the CONTEXT line outof the block match. The stale "not covered" claim was in an issue-thread ruling comment
(issue #4058, comment id 5811745193), not in code or in this PR's body — nothing in this PR's own text or
in the collector source claims the self-hosted stderr route is uncovered, so there was nothing left here
to fix beyond confirming the comment at
PgDeadlockLogParser.cs:326-331already says what the briefwants said: it IS covered, and its skips there are silent (no measurement is added on either stderr
route, matching the brief's "don't add counting on the stderr routes").
What I ran
dotnet build Darling/Darling.Tests -p:EnableWindowsTargeting=true— green after every edit, and aftermerging the parallel lane's live-test file.
restored after):
PgLogEntryProvenanceTests,PgPlanCaptureRaiseShapedUnitTests,CollectorMeasurementSeamTeststogether:
Total: 46, Errors: 0, Failed: 0, Skipped: 0Not verified
*PgDeadlock*,*RaiseShaped*,*RdsDeadlock*,*RdsPlan*,CollectionLogDrainForensicsStoreTests,DocCommentHygieneTests, or the full Darling suite as thebrief's Verify section asks — hit the context-wall notice partway through item 5's investigation and am
stopping to commit/push/report per instructions. The merged live-test file
(
PgRaiseShapedRecordsLiveTests.cs, added by the parallel lane, already reported green there) was notre-run by me.
*RaiseShaped*/*RdsDeadlock*pass after this round'sRaisedByPlpgsqlrewrite — the rewritten logic is exercised only by the three targeted classes aboveplus the new tests in them;
PgDeadlockRaiseShapedUnitTestsandRdsDeadlockIngestorTests(which alsocall into
RaisedByPlpgsqltransitively throughIsRaiseShaped) were not re-run this round.CHANGELOG entry
in the deadlock/plan-capture provenance check, fixed a verbose-logging false-positive on real
slow-statement records, and added the missing measurement-label seam entry.
Part of #4058.