Skip to content

tests: the driver/postgres Query_1col_1row allocation guard no longer counts its fake backend's allocations or depends on how its reply is segmented (v1.6.0, Refs #890 #1104) - #1162

Merged
FumingPower3925 merged 3 commits into
mainfrom
flake-pg-alloc-guard-segments
Oct 28, 2026

Conversation

@FumingPower3925

Copy link
Copy Markdown
Contributor

Flakes lane. driver/postgres TestAllocBudgets/Query_1col_1row failed allocs/op=15 (budget 14) in the step "celeris#890 allocation guards and alloc-count tests, without -race" on #1154 (run 40732286713, job 122971490732) and on #1150 (head 323fe30). Tests only, no driver change. Refs #890, Refs #1104, Refs #1154, Refs #1150

What the 15 is

AllocsPerOp floors (an integer division), so a 15 against 14 means almost every query of the final benchmark round allocated one more, not one stray allocation. The extra allocation is the colsCh of the sync fast path's fall-through (simpleQuery, "Partial or EAGAIN"): when the poll on the caller's goroutine reads only part of the reply it falls through to the async wait and allocates the channel there. That is the same +1 that #1144 measured for a platform without a sync poll (syncLoop == nil: 15).

Why the poll can read only part: the guard's fake backend wrote its reply as FOUR writes (RowDescription, DataRow, CommandComplete, ReadyForQuery), so whether the poll found all four depended on the backend's writes against the poll's phases (one read, 16 x poll(0)+Gosched, poll(1ms)). A postgres backend flushes at ReadyForQuery, one segment.

A second finding: the 14 was not the driver's cost. testing.Benchmark counts the whole process, and about 10 of the 14 were the fake backend's (writeMsg x4, buildRowDescription x2, readMsg; heap profile at rate 1, in the evidence). With a prebuilt reply, one write and a reused read buffer the driver costs 4 allocs/op on the sync path (the "aspirational 4" this row held before #890 item 91 set it to 14) and 5 without the sync poll or with a reply in pieces.

Reproduction (what is and is not shown)

  • Reproduced deterministically by injecting the split: a gap of 300 us between the backend's writes, with the guard's own bench and budget: 15 against 14, 20 of 20 (commit 1, red). linux/amd64 and arm64, docker golang:1.27.3, --cpus 2.
  • Not reproduced naturally. Main, strict, 0 failures in about 540 runs: -count=60 (one process), 100 fresh processes with the CI selection, GOMAXPROCS 1, 2, 4, 8 x --cpus 0.3, 0.5, 1, 2 (40 runs each). So the claim that CI's 15 was a reply read in pieces is an inference from the mechanism (the arithmetic of the floor, the allocation site, the fake's four writes); I did not observe a split on a failing run. If the cause on that runner is another persistent +1, the new guard (4) fails there too and says so.

Commits

  1. tests: ... counts 15, not 14, when the backend's writes of a reply are apart (red on main): a seam (fakePGWriteGap) and TestQuery1col1rowGuardDoesNotDependOnTheReplyArrivingInPieces1104: gap 0 passes (14), gap 300 us fails (15 against 14), 20 of 20.
  2. tests: the Query_1col_1row guard's fake backend answers in one segment from a prebuilt reply ...: the backend builds the reply once and writes it once; the budget is the driver's 4 (5 without the sync poll); a second row Query_1col_1row_reply_in_pieces (budget 5) prices the fall-through (2 ms between the writes, so a whole read needs a 6 ms stall). Both are rows of TestAllocBudgets, so the ci.yml entry list and the count of 15 do not change (the discovery check in that step names top-level tests of !race files). The commit-1 test is replaced by these rows.

Numbers

main this PR
Query_1col_1row, arm64 docker --cpus 2, strict, -count=30 14 (budget 14), 30 of 30 4 (budget 4), 30 of 30
reply in pieces (2 ms apart) n/a 5 (budget 5), 30 of 30
linux/amd64 (-count=3) 14 4; in pieces 5
darwin/arm64 native, strict, no sync poll 15 against 15 (#1144) 5 (budget 5); in pieces 5
commit 1 alone, gap 300 us 15 against 14, 20 of 20 red n/a
mutant: colsCh allocated on the sync path too 14 stays green (hides a +1) red: 5 against 4

The mutant matters: the old 14 had ten allocations of slack for the driver (the backend's), so a driver regression of +1 was invisible. With the driver's own 4 it is caught.

go vet linux, darwin, windows; golangci-lint 0 new findings (rowch_failall_887_test.go:311 errcheck is on main). The windows Portable fallback job is unmeasured here: its path is the no-sync-poll one, 5, as on darwin.

Release note

None (tests only).

…he backend's writes of a reply are apart (red on main; Refs #890, Refs #1104)
…t from a prebuilt reply, so the guard counts the driver's 4 allocs/op (5 without the sync poll), not 14 with the backend's own (Refs #890, Refs #1104)
@FumingPower3925 FumingPower3925 added this to the v1.6.0 milestone Oct 28, 2026
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@FumingPower3925
FumingPower3925 marked this pull request as ready for review October 28, 2026 22:15
@FumingPower3925
FumingPower3925 merged commit 3f8d83f into main Oct 28, 2026
54 checks passed
@FumingPower3925
FumingPower3925 deleted the flake-pg-alloc-guard-segments branch October 28, 2026 22:15
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