Skip to content

The deadlock pattern only offers a report whose ERROR: is the line's own label, and the managed prefix's gap can't slide past the real pid (#4014) - #4042

Merged
erikdarlingdata merged 1 commit into
devfrom
fix/4014-deadlock-forged-statement
Sep 23, 2026
Merged

erikdarlingdata merged 1 commit into
devfrom
fix/4014-deadlock-forged-statement

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Closes #4014.

Why

#4014 reported that a backend's STATEMENT companion echoing ERROR: deadlock detected, with a tab-continued DETAIL:, matched the deadlock pattern through the line's real pid bracket. That would attribute a forged deadlock to a real backend. The managed-prefix branch also kept the lazy [^\n]*? gap that #4008 removed from plan capture.

Measured end to end, the forgery never stores anything. Since #4005 every candidate goes through PgLogEntryAssembler (FromReport), whose label is the line's own, and it refuses both shapes. The new parser tests pin that. The pattern itself was still wider than that rule: on PostgreSQL 18.6, the old SQL pattern really did offer the STATEMENT echo as a candidate.

What changes

The C# route (PgDeadlockLogParser.s_deadlockBlock) and the self-hosted SQL route (PgDeadlocksCollector) change together, as defence in depth that matches the assembler's rule:

Not here, filed as #4041: the assembler, and therefore log events and deadlocks, drops every line under a prefix with fields between the zone and the pid. Plan capture has accepted that shape since #4016. Fixing it changes the #3996-hardened reader shared by all log families, so it gets its own PR and review round.

Test plan

  • PgDeadlockLogParserTests: new AStatementCompanionEchoingAReport_IsNotReadAsOne (Deadlock capture's regexp_matches may accept a forged STATEMENT echo, same class as #4008 #4014's exact shape, plus a real report after it still reading) and AForgedHeaderBehindARealColonPrefixedBracket_IsNotReadAsOne.
  • New PgDeadlockCandidatePatternLiveTests runs the shipped regexp_matches pattern, lifted from BuildQuery, through PostgreSQL's own regex engine:
  • Red-watch: with dev's pattern the live test fails (Expected: 0, Actual: 1, the echo offered).
  • Byte pin TheTailerExtraction_LeftBothSiblingsSqlByteIdentical updated for the deadlock SQL.
  • Lite: 106/106 (deadlock parser, gate, overlap pin, locale facet). Darling: 117 plus the live class and 26 hygiene gates, green. Builds: 0 warnings.
  • Test-only from the user's side: no stored deadlock changes, since the assembler already refused these. No CHANGELOG entry.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv

…own label, and the managed prefix's gap can't slide past the real pid (#4014)

#4014 reported that a STATEMENT companion echoing "ERROR:  deadlock detected"
plus a tab-continued DETAIL matched the deadlock pattern through the line's
real pid bracket. Measured end to end, the forgery never stores anything:
since #4005 every candidate goes through PgLogEntryAssembler, whose label is
the line's own. New tests pin that for the STATEMENT echo and for a forged
header behind a real managed-prefix bracket.

The pattern was still wider than the rule behind it, so both copies are
narrowed to match it (defense in depth; fewer false candidates):
- the gaps before ERROR: and before DETAIL: may not cross a field label's
  ":  ", so the ERROR: matched is the line's first label; the %Q query id
  glued to the label still fits, pinned by the captured fixture;
- the managed family's lazy gap to the pid bracket becomes [^\[\n]*, #4008's
  fix for plan capture, so it can't backtrack past the real bracket.

Measured on PostgreSQL 18.6: the old SQL pattern offered the STATEMENT echo
as a candidate; the new one offers only real reports.
PgDeadlockCandidatePatternLiveTests runs the shipped pattern through
PostgreSQL's regex engine and pins exactly that. Red-watched: with dev's
pattern it fails (Expected 0, Actual 1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 23, 2026 13:43
@erikdarlingdata
erikdarlingdata merged commit 4f87345 into dev Sep 23, 2026
15 of 16 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4014-deadlock-forged-statement branch September 23, 2026 13:50
erikdarlingdata added a commit that referenced this pull request Sep 26, 2026
…4438)

Adds the missing [Unreleased] CHANGELOG entries for nine merged PRs. Two more need none.

- Fixed: #3588, #3886, #3889, #3900, #3911, #4016, #4032 and #4042.
- Changed: #4157. llms.txt and CITATION.cff now match the shipped product.
- None:
  - #4332 adds RawChunkIntervalPlanner without wiring it in; the later PR that wires it carries its own entry.
  - #4398 changes tests only.
- Each entry was written from its PR's diff, with a [#N] label linking the pull request.
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