# A row that is being deleted by a transaction still in progress when the # copy reads it is copied with that transaction's xmax (0008). Once the # transaction commits and its DELETE is replayed, find_target_tuple() does # not find the row, because HeapTupleSatisfiesNewHeap() takes any valid xmax # as committed. Expected: REPACK succeeds and 999 rows remain. setup { CREATE EXTENSION injection_points; CREATE TABLE t(i int PRIMARY KEY, j text) WITH (autovacuum_enabled = off); -- 8 blocks; with repack_snapshot_after = 4 there are exactly two ranges, -- and row 1000 is in the second one. INSERT INTO t SELECT x, repeat('x', 20) FROM generate_series(1, 1000) x; } teardown { DROP TABLE t; DROP EXTENSION injection_points; } session s1 setup { SET repack_snapshot_after = 4; SELECT injection_points_set_local(); SELECT injection_points_attach('repack-concurrently-new-range', 'wait'); SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); } step repack { REPACK (CONCURRENTLY) t; } step check { SELECT count(*) FROM t; } session s2 step begin { BEGIN; } step delete { DELETE FROM t WHERE i = 1000; } step commit { COMMIT; } session s3 # REPACK is at the end of the first range; the next snapshot sees the DELETE # in progress. step wakeup_range { SELECT injection_points_detach('repack-concurrently-new-range'); SELECT injection_points_wakeup('repack-concurrently-new-range'); } # Wait until the copy is done and REPACK waits before taking the lock. step wait_lock { DO $$ BEGIN WHILE NOT EXISTS (SELECT 1 FROM pg_stat_activity WHERE wait_event = 'repack-concurrently-before-lock') LOOP PERFORM pg_sleep(0.1); PERFORM pg_stat_clear_snapshot(); END LOOP; END $$; } # The DELETE has committed by now; let REPACK replay it. step wakeup_lock { SELECT injection_points_detach('repack-concurrently-before-lock'); SELECT injection_points_wakeup('repack-concurrently-before-lock'); } permutation repack begin delete wakeup_range wait_lock commit wakeup_lock check