Repository navigation
test: update_conc gets its pytest twin, and a savepoint arm that reaches the subtransaction discard (#1341 item 24) - #1441
Conversation
|
The four other majors after the restate onto 49457c1, at this head: PG15, 16, 17 and 19 are each 28/28 in both twins. With PG18 from the PR body, all five majors are re-measured on the restated tree. 🤖 Generated with Claude Code |
jdatcmd
left a comment
There was a problem hiding this comment.
Approving 3ac87e5.
At the head, PG17: update_conc.sh 28/28; test_update_conc.py 28 checks, 11 tests. The census at this head (3527 / 2618 / 909) matches the budget paragraph, and suites_not_covered is 222.
Your central claim, with two mutants of my own in pgcolumnar_subxact_callback's SUBXACT_EVENT_ABORT_SUB branch:
| mutant | shell | pytest |
|---|---|---|
PgColumnarWriteStateDiscardSubXact dropped |
26/28: both "savepoint, failed statement" arms, got [2/10,99|3] want [1/10|2] |
its oracle arm, got '2/10,99|3' |
PgColumnarDeleteVectorDiscardSubXact dropped |
26/28: the same two arms, got [0/|1] want [1/10|2] |
its oracle arm, got '0/|1' |
Each mutant reddens for its own reason. Without the row discard, the failed UPDATE's new row survives as a duplicate (three rows, id=1 twice). Without the mark discard, its delete mark survives and the row is lost. The two older savepoint arms stay green under both, which confirms that they could not reach the discard. I restored the source with a make clean, and git status was clean.
Not a blocker: as in #1438, the pytest test stops at its first failed expect, so it runs 27 checks under a mutant where the shell runs 28.
#1439 and #1442 go first, as agreed. After that this chain restates once, and I'll re-approve its bookkeeping.
… can fail (#1341 item 23) test_unique_conc.py is the twin of unique_conc.sh, every check by the shell's name, on a private cluster at unique_lock_buckets 100003 (fixed at server start). T1 is held mid-INSERT by a second row waiting in cq_block; T2 runs in a thread; every barrier polls pg_stat_activity for a lock wait. Porting it found four shell arms that could not fail: 3, 4 (differing key), 5 (outside the predicate) and 6 (NULLS DISTINCT). When T2 did block, the server's 60s lock_timeout aborted it as the 60s bounded wait ran out, and the waiter count read 0: a mutant that hashes only a key's first column left the shell at 33/33. In both twins T2 now runs those arms and the lock-off controls with a 5s lock_timeout, and each asserts nothing waits and its INSERT returned in a live transaction. A wait the next check asserts no longer records a second, fixed-name failure. The 23505 arms assert the SQLSTATE (psql VERBOSITY verbose), not the index name alone. The suite uses lib.sh's check, pgc_fail and pgc_summary instead of a local recorder, so the twin grader can read it and the log has an accounting line. The PG < 15 skip of NULLS NOT DISTINCT is gone: no supported major takes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ons of columnar_unique.c (#1341) 33 rows from five-major runs (33/33 on each of 15-19). Dated on PG18 by: the per-key lock never taken (18), the lock taken with the setting off (2), every key in one bucket (3), only the first key column hashed (1, shared), the partial predicate ignored (2) and a NULL key locked under NULLS DISTINCT (2). suites_not_covered 224 -> 223; the census is 3507 rows, 2607 never, 900 dated, restated on main 45eaa19. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th pgc_summary (#1341) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…key (#1341) conflict() ran the whole race before the test asserted anything. With no key lock taken, T2 did not wait, T1 then stalled behind T2's uncommitted duplicate, and each test's first red was "a bounded wait for a standalone sentinel completed", not the arm the mutant breaks. conflict() now returns the waiter count with T1 held, the test asserts it, and settle() finishes the race. Measured on PG18: under the no-lock mutant the six tests now go red on their "serializes" arm (got 0 want 1), in 62s rather than 422s; on the head, 33/33. The sessions close holder first, so a T1 still paused when a test stops is released before its own connection closes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hes the subtransaction discard (#1341 item 24) test_update_conc.py asserts every shell check by name; compare_to_bash grades it as covering all 31 (28 checks and the three bounded-wait names), and it joins COMPLETE. update_conc.sh moves onto lib.sh's check, pgc_fail and pgc_summary, so it leaves suites_without_accounting. Its savepoint arm could not reach the discard it claims: the inner UPDATE completes, executor-end flushes it under the inner subtransaction, and with either discard in pgcolumnar_subxact_callback removed it stays green. A new arm fails a statement partway inside the savepoint, with the scan order asserted as its premise, and goes red under each (0/|1 and 2/10,99|3). cluster_tests 627 -> 638 by collection; TESTS.md gets section 135. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tions (#1341) 28 rows from five-major runs (28/28 on each of 15-19). Dated on PG18 by, in columnar_row_lock.c: the flush-time serialization skipped, the committed-delete conflict never raised, and the re-read from the statement snapshot (10 arms each, the same 10); the row lock skipped during a unique-index UPDATE (1); the self-modify guard removed (1); and in pgcolumnar_subxact_callback, either discard skipped (the 2 failed-statement savepoint arms). suites_not_covered 223 -> 222; the census is 3535 rows, 2621 never, 914 dated, restated on main 45eaa19. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3ac87e5 to
af72e6a
Compare
jdatcmd
left a comment
There was a problem hiding this comment.
Re-approving af72e6a, a bookkeeping-only restate of 3ac87e5, which I approved.
- Identical per-file patches: update_conc.sh, test_update_conc.py, the ledger rows, test_compare_to_bash.py, TESTS.md (masked) and suites_without_accounting.txt.
- The census at this head matches the budget paragraph: checks_never_observed_red 2621, and
cluster_testsis 638.
I'll merge the chain in order once each head is green.
… a savepoint leaves nothing (#1341) test/phase3.sh's savepoint arm rolled back only completed statements. Executor end had already flushed their buffers under the subtransaction, so the abort's xmin verdict hid them and the savepoint's own discard was never reached (#1441). With the subxact abort discarding neither pending writes nor pending deletes, all 32 checks passed. An INSERT whose 2001st row divides by zero, and a DELETE that fails at a = 9, are now each rolled back to a savepoint and must leave nothing, in the transaction and after it. Measured: the mutants leave 2000 rows visible, and rows 2..8 deleted. The delete arm said its range straddled a vector boundary and nothing read one: with 16384-row vectors all 32 passed. The zone maps' maxima are now read. The suite moves onto lib.sh's check, as smoke.sh did: its own check called pgc_record directly, which the twin's grader cannot read. test/pytest/test_phase3.py is the twin, one check per test by the shell's name, on a private database. Census 3883 -> 3914 never, 2282 -> 2287 dated, 6165 -> 6201 rows; suites_not_covered 102 -> 101; cluster_tests 2748 -> 2784; TESTS.md section 268. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a savepoint leaves nothing (#1341) test/phase3.sh's savepoint arm rolled back only completed statements. Executor end had already flushed their buffers under the subtransaction, so the abort's xmin verdict hid them and the savepoint's own discard was never reached (#1441). With the subxact abort discarding neither pending writes nor pending deletes, all 32 checks passed. An INSERT whose 2001st row divides by zero, and a DELETE that fails at a = 9, are now each rolled back to a savepoint and must leave nothing, in the transaction and after it. Measured: the mutants leave 2000 rows visible, and rows 2..8 deleted. The delete arm said its range straddled a vector boundary and nothing read one: with 16384-row vectors all 32 passed. The zone maps' maxima are now read. A DELETE whose RETURNING reads the table in a block that commits into one that aborts must see the rows deleted so far, as heap does. With the savepoint commit not handing its delete buffers and written marks to the parent, the abort left the read's marks attributed to a finished block, no later read wrote them again, and the reads summed 19999 against heap's 14950. The statement's end still wrote every mark, so only the reads showed it. The suite moves onto lib.sh's check, as smoke.sh did: its own check called pgc_record directly, which the twin's grader cannot read. test/pytest/test_phase3.py is the twin, one check per test by the shell's name, on a private database. Census 3883 -> 3914 never, 2282 -> 2288 dated, 6165 -> 6202 rows; suites_not_covered 102 -> 101; cluster_tests 2748 -> 2785; TESTS.md section 268. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a savepoint leaves nothing (#1341) test/phase3.sh's savepoint arm rolled back only completed statements. Executor end had already flushed their buffers under the subtransaction, so the abort's xmin verdict hid them and the savepoint's own discard was never reached (#1441). With the subxact abort discarding neither pending writes nor pending deletes, all 32 checks passed. An INSERT whose 2001st row divides by zero, and a DELETE that fails at a = 9, are now each rolled back to a savepoint and must leave nothing, in the transaction and after it. Measured: the mutants leave 2000 rows visible, and rows 2..8 deleted. The delete arm said its range straddled a vector boundary and nothing read one: with 16384-row vectors all 32 passed. The zone maps' maxima are now read. A DELETE whose RETURNING reads the table in a block that commits into one that aborts must see the rows deleted so far, as heap does. With the savepoint commit not handing its delete buffers and written marks to the parent, the abort left the read's marks attributed to a finished block, no later read wrote them again, and the reads summed 19999 against heap's 14950. The statement's end still wrote every mark, so only the reads showed it. The suite moves onto lib.sh's check, as smoke.sh did: its own check called pgc_record directly, which the twin's grader cannot read. test/pytest/test_phase3.py is the twin, one check per test by the shell's name, on a private database. Census 4397 -> 4428 never, 3150 -> 3156 dated, 7547 -> 7584 rows; suites_not_covered 41 -> 40; cluster_tests 3834 -> 3871; TESTS.md section 330. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a savepoint leaves nothing (#1341) test/phase3.sh's savepoint arm rolled back only completed statements. Executor end had already flushed their buffers under the subtransaction, so the abort's xmin verdict hid them and the savepoint's own discard was never reached (#1441). With the subxact abort discarding neither pending writes nor pending deletes, all 32 checks passed. An INSERT whose 2001st row divides by zero, and a DELETE that fails at a = 9, are now each rolled back to a savepoint and must leave nothing, in the transaction and after it. Measured: the mutants leave 2000 rows visible, and rows 2..8 deleted. The delete arm said its range straddled a vector boundary and nothing read one: with 16384-row vectors all 32 passed. The zone maps' maxima are now read. A DELETE whose RETURNING reads the table in a block that commits into one that aborts must see the rows deleted so far, as heap does. With the savepoint commit not handing its delete buffers and written marks to the parent, the abort left the read's marks attributed to a finished block, no later read wrote them again, and the reads summed 19999 against heap's 14950. The statement's end still wrote every mark, so only the reads showed it. The suite moves onto lib.sh's check, as smoke.sh did: its own check called pgc_record directly, which the twin's grader cannot read. test/pytest/test_phase3.py is the twin, one check per test by the shell's name, on a private database. Census 4379 -> 4410 never, 3175 -> 3181 dated, 7554 -> 7591 rows; suites_not_covered 41 -> 40; cluster_tests 3841 -> 3878; TESTS.md section 330. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a savepoint leaves nothing (#1341) test/phase3.sh's savepoint arm rolled back only completed statements. Executor end had already flushed their buffers under the subtransaction, so the abort's xmin verdict hid them and the savepoint's own discard was never reached (#1441). With the subxact abort discarding neither pending writes nor pending deletes, all 32 checks passed. An INSERT whose 2001st row divides by zero, and a DELETE that fails at a = 9, are now each rolled back to a savepoint and must leave nothing, in the transaction and after it. Measured: the mutants leave 2000 rows visible, and rows 2..8 deleted. The delete arm said its range straddled a vector boundary and nothing read one: with 16384-row vectors all 32 passed. The zone maps' maxima are now read. A DELETE whose RETURNING reads the table in a block that commits into one that aborts must see the rows deleted so far, as heap does. With the savepoint commit not handing its delete buffers and written marks to the parent, the abort left the read's marks attributed to a finished block, no later read wrote them again, and the reads summed 19999 against heap's 14950. The statement's end still wrote every mark, so only the reads showed it. The suite moves onto lib.sh's check, as smoke.sh did: its own check called pgc_record directly, which the twin's grader cannot read. test/pytest/test_phase3.py is the twin, one check per test by the shell's name, on a private database. Census 4434 -> 4465 never, 3329 -> 3335 dated, 7763 -> 7800 rows; suites_not_covered 35 -> 34; cluster_tests 4035 -> 4072; TESTS.md section 335. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1341 item 24: two transactions UPDATE or DELETE the same row, the loser gets 40001, and no duplicate survives.
update_conc.shhad no pytest twin. This PR adds one, every check by the shell's name, and seeds the suite into the ledger. The port found that the suite's savepoint arm cannot reach the code it describes, so a new arm covers that path, red first.Stacked on #1438. Merge #1438 first; this branch is its head plus three commits.
What changes
test/pytest/test_update_conc.py, the twin. It has eleven tests, with each concurrent case running on a heap oracle and on a columnar table.pg_stat_activity(this database only) confirms.lock_timeout, as the shell's cluster does.compare_to_bash.pygrades it as covering all 31 shell names (28 checks plus the three bounded-wait names), and it joinsCOMPLETE.update_conc.shuses lib.sh's recorders (check,pgc_failfor the timeouts,pgc_summary), so it leavessuites_without_accounting.txt, as unique_conc does in test: unique_conc gets its pytest twin, and its "does not block" arms can fail (#1341 item 23) #1438.pgcolumnar_subxact_callback's discards never run.1/(id-3)). No executor-end runs, so its mark and row are still buffered at ROLLBACK TO. Its premise is the scan order: id=1 is read before id=3.update_concis seeded with 28 rows from five-major runs, and 14 of the 28 rows dated by the mutants below.suites_not_coveredgoes 223 → 222, and the census is 3499 + 28 = 3527 rows, 2604 + 14 = 2618 never and 895 + 14 = 909 dated, matching the tsv. The gate gives rc=0 against origin/main.Red first
Seven mutations, one at a time, on PG18. Each was run through both twins, with the
.sorebuilt and its md5 printed every time.update_conc.shtest_update_conc.pyPgColumnarSerializeFlushRowsreturns at oncegot [2] want [1],got [241] want [200],got [00000] want [40001])GetActiveSnapshot(), notGetLatestSnapshot()PgColumnarSerializeRowNowreturns at oncegot [23505|1] want [40001|1], the #1421 buggot [2] want [1]PgColumnarWriteStateDiscardSubXactnot called on subxact abortgot [2/10,99|3]PgColumnarDeleteVectorDiscardSubXactnot calledgot [0/|1]Never red (14):
Runs
update_conc.shtest_update_conc.pyThe mutants and the five-major runs were measured on main 6391900. After the restate onto 49457c1, whose only
srcchange iscolumnar_vector.c(#1436), PG18 is 28/28 in both twins again. The other four majors are re-running and I'll post them here.Also measured: the guard half (438 tests), harness_selftest 1245/1245 and docs_style 58/58.
cluster_testsgoes 613 → 624 by collection, and TESTS.md gets §133.🤖 Generated with Claude Code