Repository navigation
Fix the BRIN manual-VACUUM live test race (#5938) - #5939
Merged
Merged
Conversation
…gress row and keep autovacuum off the table (#5938)
erikdarlingdata
marked this pull request as ready for review
October 16, 2026 11:29
There was a problem hiding this comment.
Verdict: LGTM
Claims
- VERIFIED —
SlowVacuum.StartAsyncnow waits for thepg_stat_progress_vacuumrow whosepidis the manual VACUUM's own backend —Darling/Darling.Tests/QueryStoreIntervalBrinLiveTests.cs:387(AND pid = {connection.ProcessID}) - VERIFIED — new
NoAutovacuumAsyncsetsautovacuum_enabled = falseon the scratch legacy table before the rows are inserted, in both manual-VACUUM tests —QueryStoreIntervalBrinLiveTests.cs:297-298(definition), called at:422and:525, both before theirInsertManyAsynccalls - VERIFIED — "No product code changed" —
gh pr diff 5939 --name-onlylists onlyDarling/Darling.Tests/QueryStoreIntervalBrinLiveTests.cs - VERIFIED —
TheTurnOff_...watches the index so the VACUUM's own queueing would not trip it, whileTheSummarize_...watches the table —WaiterWatch(watcher, LegacyBrin, ct)at:430vsWaiterWatch(watcher, LegacyTable, ct)at:532 - VERIFIED — "no class named
*Ratchet*in Darling.Tests" —grep -rl "class.*Ratchet" Darling/Darling.Tests/returns nothing - UNVERIFIED — the rig reproduction numbers (6/6 old-shape passes, 1/8 failure under aggressive autovacuum, 15/15 new-shape passes, Census/Hygiene/Cleanup run counts, 0 build warnings/errors) — these are live PostgreSQL rig runs that cannot be checked from a read-only tree
Riskiest lines
QueryStoreIntervalBrinLiveTests.cs:387— the whole fix hinges onconnection.ProcessIDmatching the manual VACUUM backend'spidinpg_stat_progress_vacuum, not an autovacuum worker's — pinned byTheTurnOff_WhileAManualVacuumRunsOnTheTable_LeavesTheIndexOn_AndNeverAsksForTheLockandTheSummarize_WhileAManualVacuumRunsOnTheTable_SkipsTheIndex_AndNeverAsksForTheLockQueryStoreIntervalBrinLiveTests.cs:422,525—NoAutovacuumAsyncruns afterCREATE INDEXbut beforeInsertManyAsync, relying on the table still being empty (so no worker can already be on it) — pinned by the same two tests, each on a freshScratchPostgresdatabaseQueryStoreIntervalBrinLiveTests.cs:430vs:532—WaiterWatchtargets the index in one test and the table in the other; swapping these would silently reopen the flake in just one test — pinned by both tests'Assert.False(waiterSeen, ...)
Traps near this diff
- "A test that reads the clock twice measures the CI scheduler" — does not apply — each polling loop (
:169-179,:311-325, unchanged by this diff) tracks a singleStopwatchinstance per wait, not two independent clock reads - "Lite and Darling ship in parity... a fix to one SKU's reader... without the counterpart's is a finding" — does not apply —
QueryStoreIntervalBrinis a Postgres-only BRIN autosummarize feature with no Lite/DuckDB counterpart (grep forQueryStoreIntervalBrinoutside Darling returns nothing) - "Ruling (#5833): tests are load-bearing... never answer a trap with a test that reads source text" — does not apply — the changed tests already assert runtime behavior (
StepOutcome,waiterSeen), and this diff adds no new text-reading test
Divergence (blind read vs PR body)
None — the body's account matches the diff on every claim graded above.
Reviewer checklist
- Run
Darling.Tests.QueryStoreIntervalBrinLiveTestsseveral times against a live PostgreSQL instance withautovacuum_naptimeandautovacuum_vacuum_insert_thresholdlowered (as the PR body describes) to confirm the flake is actually gone, since this cannot be exercised from a read-only tree
This was referenced Oct 16, 2026
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.
Claude posting for Erik Darling.
Fixes #5938.
The failure
PR #5930 (four PNG baselines only), run 38888262525, job
Darling PG tests (1)116934424635, 2026-10-16 10:55Z:Line 527 is
Assert.False(waiterSeen, ...). The assertion on line 525 (skipped 1 being vacuumed) passed, so the summarize did skip the index. The only thing that failed was the test's watcher, which saw SOMEONE ungranted on the table.The race
The code is right:
SummarizeTargetsSqlreadspg_stat_progress_vacuumfor the table andSummarizeNewRangesAsyncskips a being-vacuumed index before it opens a transaction or asks for any lock. The bug is in the test's two helpers.SlowVacuum.StartAsyncdecided "the manual VACUUM is running" when ANYpg_stat_progress_vacuumrow existed for the table. The table is brand new with many fresh rows, which is what the autovacuum launcher picks first. A worker on it satisfies that poll while the manual VACUUM has not started.deadlock_timeout, 1 s). That ungranted request sits inpg_lockson the table, andWaiterWatch(any ungranted lock on the relation) reads it as the summarize's.TheTurnOff_WhileAManualVacuumRunsOnTheTable_...uses the same helper and has the same exposure (it watches the index, so the VACUUM's own queueing would not trip it, but the "VACUUM is running" poll and the second pass would).What changed (test only)
Darling/Darling.Tests/QueryStoreIntervalBrinLiveTests.cs:SlowVacuum.StartAsyncnow waits for thepg_stat_progress_vacuumrow whosepidis the manual VACUUM's own backend (connection.ProcessID). The test starts its step only once that backend is progressing, so the summarize is checked against the lock the test controls. A state observed, not a sleep.NoAutovacuumAsyncsetsautovacuum_enabled = falseon the scratch legacy table, before the rows are inserted, in both manual-VACUUM tests, so no worker can be on it.No product code changed.
Reproduction and verification
Rig: throwaway PG from
pg-runtime.zip, port 55938, in my scratch folder; stopped bypg_ctl stopat the end.autovacuum_naptime = 1sandautovacuum_vacuum_insert_threshold = 50to make workers frequent: 1 failure in 8 class runs, inTheSummarize_WhileAManualVacuumRunsOnTheTable_SkipsTheIndex_AndNeverAsksForTheLock(I did not capture which assertion; the CI line is the one quoted above).QueryStoreIntervalBrinLiveTestspassed (31 tests each, 0 failed).*Census*classes: Total 499, Failed 0.*LivePostgresCollectionHygiene*: 10, Failed 0.*LiveCleanup*: 6, Failed 0. Build: 0 warnings, 0 errors.*Ratchet*in Darling.Tests (LiveCleanupConversionRatchetTestsdoes not exist on this base), so none ran.The old-shape local repro is a single failure; I did not prove the autovacuum worker as the waiter with a pg_locks dump. The mechanism is the only reading consistent with the passed line-525 assertion and the failure on a code-free PR, and the new shape removes both ways it can interfere.
CHANGELOG entry
SECTION: none
Test-only change.