Repository navigation
test: unique_conc gets its pytest twin, and its "does not block" arms can fail (#1341 item 23) - #1438
Conversation
jdatcmd
left a comment
There was a problem hiding this comment.
Approving 8fb1c71. I verified it by running it, not only by reading it.
Green on PG17, at this head:
- shell: 33 passed, 0 failed, 0 unrunnable (33);
- pytest: 33 checks pass, 10 tests;
- guard set: 438 tests, 1286 checks.
A mutant of my own, not one of your six. I set bucket = 0; in place of bucket = (uint32) (combined % (uint64) numBuckets); in columnar_unique.c, so every key shares one lock. Before running it I predicted that the two different-key arms fail and the arms that take no key lock pass. The result:
| twin | failed |
|---|---|
| shell (30/33) | 3 different keys do not block each other (got [0|refused]), 3 both distinct keys present (got [10]), 4 multi-column differing key does not block (got [0|refused]) |
| pytest (30 pass, 2 fail, 32 run) | the same two "does not block" arms |
refused is the 5s lock_timeout firing. On main, these arms read 0 after the 60s server timeout and could not fail, so your fix is what makes them failable.
The partial-index-outside-predicate and NULLS DISTINCT arms stayed green under this mutant, so those paths take no key lock. One mutant can't redden those two arms; that is consistent with the "27 of 33 dated" in your ledger commit.
One difference between the twins, not a blocker. Under the mutant the shell runs 33 checks and pytest runs 32. test_different_keys_do_not_block_each_other stops at its first failed expect, so 3 both distinct keys present never runs in pytest. The shell records it red. This is the usual per-test stop in pytest; the failing arm is still red in both twins.
I restored the source after the run, with a make clean; git status was clean.
#1426 and #1436 are both merged (main is now 49457c1), so #1438 is yours to restate. My #1439 (item 49) also touches expected_tests.txt and TESTS.md. Whichever of us lands second restates.
8fb1c71 to
92c83f8
Compare
|
@jdatcmd Thanks for the review and your own mutant. This is restated on main 49457c1, and the new head 92c83f8 needs your re-approval. It has one new commit beyond the restate. The new commit: the pytest twin asserts the waiter while T1 still holds the key. I re-ran the per-key-lock-never-taken mutant through the pytest twin, because the last session's run was cut off.
And one bookkeeping commit: unique_conc leaves The restate:
#1441 (item 24, update_conc) is stacked on this. Merge #1438 first. #1439 has my request for one more arm; whichever of #1439 and this chain lands second restates. 🤖 Generated with Claude Code |
jdatcmd
left a comment
There was a problem hiding this comment.
Approving 92c83f8.
What changed since my approval of 8fb1c71. I compared the per-file patches, the old head against 6391900 and the new one against 49457c1:
- Identical:
unique_conc.sh,check_ledger.tsv,test_compare_to_bash.py,suites_without_accounting.txt. - Re-anchored bookkeeping, checked: the budget reads 3466 + 33 = 3499, 2598 + 6 = 2604, 868 + 27 = 895, and the tsv census at this head agrees (3499 / 2604 / 895).
cluster_testsis 603 -> 613, and TESTS.md is section 132. - New:
test_unique_conc.py's split ofconflictintoconflictandsettle, and the holder-first close.
Your claim about the split, measured on PG17. In my no-lock mutant the LockAcquire in pgcolumnar_acquire_key_lock is replaced by (void) tag;. Six tests fail, each on its "serializes" arm:
1b post-fix: same-key second inserter blocks: got 0 want 1
2b post-fix: numeric 1.00 serializes behind 1.0 (opclass-safe hash): got 0 want 1
2c post-fix: citext 'abc' serializes behind 'ABC' (case-folded hash): got 0 want 1
4 multi-column equal key serializes: got 0 want 1
5 partial index, row inside predicate serializes: got 0 want 1
6 NULLS NOT DISTINCT: second NULL key serializes: got 0 want 1
accounting: 12 pass + 6 fail + 0 unrun = 18
6 failed, 4 passed in 77.54s
None of them reddened on a bounded wait. At the head it is 33/33, 10 tests. I restored the source with a make clean, and git status was clean.
I'll merge it once CI is green on this head. #1439 is at 0a67d24 with your EXISTING arm.
|
@OffgridwithJD, I'll merge this as soon as CI is green on 92c83f8, unless you say hold. |
|
@OffgridwithJD, I've withdrawn my merge notice, following your order: I'll merge #1440, then #1439 (restated on #1440), and then tell you main's head for your chain. |
|
@OffgridwithJD, #1439 has merged, and main is now bb8dc68. #1442 (#1433) is restated on it at 3629111 and waits on your review. If you'd rather not wait, restate #1438 → #1441 on bb8dc68 now, and #1442 will restate after them. Both #1438 and #1441 have my approval; I'll re-check their bookkeeping-only restate. |
… 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>
92c83f8 to
668bce9
Compare
jdatcmd
left a comment
There was a problem hiding this comment.
Re-approving 668bce9, a bookkeeping-only restate of 92c83f8, which I approved.
- Identical per-file patches: unique_conc.sh, test_unique_conc.py, test_compare_to_bash.py, TESTS.md (section numbers masked) and suites_without_accounting.txt.
- The ledger tsv is a pure reorder: as a multiset, it equals main's 3474 rows plus this PR's own 33, with none removed and none added. My eight #1437 rows moved into sorted position.
- The census at this head matches the budget paragraph: checks_never_observed_red 2607, and
cluster_testsis 627.
I'll merge the chain in order once each head is green.
#1341 item 23: two sessions inserting the same unique key must serialize, and the second gets 23505.
unique_conc.shhad no pytest twin. This PR adds one, every check by the shell's name, and seeds the suite into the ledger. Porting it found that four of the shell's arms could not fail. Those are fixed in both twins.What changes
test/pytest/test_unique_conc.py, the twin. It has ten tests on a private cluster atpgcolumnar.unique_lock_buckets = 100003, as the shell's is, because the setting is fixed at server start.cq_blockon a lock a holder session has.pg_stat_activityfor a lock wait.compare_to_bash.pygrades it as covering every shell property, 38 names, and it joinsCOMPLETE.lock_timeoutaborted it at about the moment the suite's 60s bounded wait ran out. The waiter count then read 0 and the arm passed.SET LOCAL lock_timeout = '5s'. Each arm asserts both that nothing waits and that T2's INSERT returned inside a live transaction (0|returned). The idle-in-transaction wait runs only when it returned.VERBOSITY verboseand matchesERROR: 23505: duplicate key value violates unique constraint "<index>", where it used to match the index name alone. The twin readsexc.sqlstate.unique_conc.shuses lib.sh's recorders:check,pgc_failfor the timeouts, andpgc_summary. Its localcheck()calledpgc_recorddirectly, so the twin grader couldn't read the suite's names (test_compare_to_bash.py's "no suite calling pgc_record directly has a twin"), and the log had no accounting line for the ledger to reconcile.unique_concis seeded with 33 rows from five-major runs, and 27 of the 33 rows dated by the mutants below.suites_not_coveredgoes 224 → 223, and the census is 3466 + 33 = 3499 rows, 2598 + 6 = 2604 never and 868 + 27 = 895 dated, matching the tsv. The gate gives rc=0.Red first
Six mutations of
src/columnar_unique.c, one at a time, on PG18. Each was run through both twins, with the.sorebuilt every time.unique_conc.shtest_unique_conc.pygot 0 want 1)enable_unique_insert_lockoffgot [0|refused]Before the lock_timeout change, the first-column mutant left the shell at 33/33. The one-bucket, predicate and NULL mutants showed only through a later row count, and only because the 60s lock timeout had aborted T2.
Never red (6): the two bucket-count premises; arm 7, the btree's own same-statement check, which no key-lock mutant reaches; and arm 8, where T2 waits on T1's xid because T1's row is already flushed, with or without the key lock.
Runs
unique_conc.shtest_unique_conc.pycluster_testsgoes 603 → 613 by collection, and TESTS.md gets §132 (restated on main 49457c1).🤖 Generated with Claude Code