mirror of https://github.com/postgres/postgres
This path previously was not reliably covered. There was some heuristic coverage via insert-conflict-toast.spec, but that test is not deterministic, and only tested for a somewhat specific bug. Backpatch, as this is a complicated and otherwise untested code path. Unfortunately 9.5 cannot handle two waiting sessions, and thus cannot execute this test. Triggered by a conversion with Melanie Plageman. Author: Andres Freund Discussion: https://postgr.es/m/CAAKRu_a7hbyrk=wveHYhr4LbcRnRCG=yPUVoQYB9YO1CdUBE9Q@mail.gmail.com Backpatch: 9.5-pull/41/head
parent
6d2fba3189
commit
08e2edc076
@ -0,0 +1,179 @@ |
||||
Parsed test spec with 3 sessions |
||||
|
||||
starting permutation: controller_locks controller_show s1_upsert s2_upsert controller_show controller_unlock_1_1 controller_unlock_2_1 controller_unlock_1_3 controller_unlock_2_3 controller_show controller_unlock_2_2 controller_show controller_unlock_1_2 controller_show |
||||
step controller_locks: SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock); |
||||
pg_advisory_locksess lock |
||||
|
||||
1 1 |
||||
1 2 |
||||
1 3 |
||||
2 1 |
||||
2 2 |
||||
2 3 |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step s1_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s1') ON CONFLICT (blurt_and_lock(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s1'; <waiting ...> |
||||
step s2_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s2') ON CONFLICT (blurt_and_lock(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s2'; <waiting ...> |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step controller_unlock_1_1: SELECT pg_advisory_unlock(1, 1); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_unlock_2_1: SELECT pg_advisory_unlock(2, 1); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_unlock_1_3: SELECT pg_advisory_unlock(1, 3); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_unlock_2_3: SELECT pg_advisory_unlock(2, 3); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step controller_unlock_2_2: SELECT pg_advisory_unlock(2, 2); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step s2_upsert: <... completed> |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
k1 inserted s2 |
||||
step controller_unlock_1_2: SELECT pg_advisory_unlock(1, 2); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step s1_upsert: <... completed> |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
k1 inserted s2 with conflict update s1 |
||||
|
||||
starting permutation: controller_locks controller_show s1_upsert s2_upsert controller_show controller_unlock_1_1 controller_unlock_2_1 controller_unlock_1_3 controller_unlock_2_3 controller_show controller_unlock_1_2 controller_show controller_unlock_2_2 controller_show |
||||
step controller_locks: SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock); |
||||
pg_advisory_locksess lock |
||||
|
||||
1 1 |
||||
1 2 |
||||
1 3 |
||||
2 1 |
||||
2 2 |
||||
2 3 |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step s1_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s1') ON CONFLICT (blurt_and_lock(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s1'; <waiting ...> |
||||
step s2_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s2') ON CONFLICT (blurt_and_lock(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s2'; <waiting ...> |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step controller_unlock_1_1: SELECT pg_advisory_unlock(1, 1); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_unlock_2_1: SELECT pg_advisory_unlock(2, 1); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_unlock_1_3: SELECT pg_advisory_unlock(1, 3); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_unlock_2_3: SELECT pg_advisory_unlock(2, 3); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step controller_unlock_1_2: SELECT pg_advisory_unlock(1, 2); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step s1_upsert: <... completed> |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
k1 inserted s1 |
||||
step controller_unlock_2_2: SELECT pg_advisory_unlock(2, 2); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step s2_upsert: <... completed> |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
k1 inserted s1 with conflict update s2 |
||||
|
||||
starting permutation: controller_locks controller_show s1_begin s2_begin s1_upsert s2_upsert controller_show controller_unlock_1_1 controller_unlock_2_1 controller_unlock_1_3 controller_unlock_2_3 controller_show controller_unlock_1_2 controller_show controller_unlock_2_2 controller_show s1_commit controller_show s2_commit controller_show |
||||
step controller_locks: SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock); |
||||
pg_advisory_locksess lock |
||||
|
||||
1 1 |
||||
1 2 |
||||
1 3 |
||||
2 1 |
||||
2 2 |
||||
2 3 |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step s1_begin: BEGIN; |
||||
step s2_begin: BEGIN; |
||||
step s1_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s1') ON CONFLICT (blurt_and_lock(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s1'; <waiting ...> |
||||
step s2_upsert: INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s2') ON CONFLICT (blurt_and_lock(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s2'; <waiting ...> |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step controller_unlock_1_1: SELECT pg_advisory_unlock(1, 1); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_unlock_2_1: SELECT pg_advisory_unlock(2, 1); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_unlock_1_3: SELECT pg_advisory_unlock(1, 3); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_unlock_2_3: SELECT pg_advisory_unlock(2, 3); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step controller_unlock_1_2: SELECT pg_advisory_unlock(1, 2); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step s1_upsert: <... completed> |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step controller_unlock_2_2: SELECT pg_advisory_unlock(2, 2); |
||||
pg_advisory_unlock |
||||
|
||||
t |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
step s1_commit: COMMIT; |
||||
step s2_upsert: <... completed> |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
k1 inserted s1 |
||||
step s2_commit: COMMIT; |
||||
step controller_show: SELECT * FROM upserttest; |
||||
key data |
||||
|
||||
k1 inserted s1 with conflict update s2 |
@ -0,0 +1,149 @@ |
||||
# INSERT ... ON CONFLICT test verifying that speculative insertion |
||||
# failures are handled |
||||
# |
||||
# Does this by using advisory locks controlling progress of |
||||
# insertions. By waiting when building the index keys, it's possible |
||||
# to schedule concurrent INSERT ON CONFLICTs so that there will always |
||||
# be a speculative conflict. |
||||
|
||||
setup |
||||
{ |
||||
CREATE OR REPLACE FUNCTION blurt_and_lock(text) RETURNS text IMMUTABLE LANGUAGE plpgsql AS $$ |
||||
BEGIN |
||||
RAISE NOTICE 'called for %', $1; |
||||
|
||||
-- depending on lock state, wait for lock 2 or 3 |
||||
IF pg_try_advisory_xact_lock(current_setting('spec.session')::int, 1) THEN |
||||
RAISE NOTICE 'blocking 2'; |
||||
PERFORM pg_advisory_xact_lock(current_setting('spec.session')::int, 2); |
||||
ELSE |
||||
RAISE NOTICE 'blocking 3'; |
||||
PERFORM pg_advisory_xact_lock(current_setting('spec.session')::int, 3); |
||||
END IF; |
||||
RETURN $1; |
||||
END;$$; |
||||
|
||||
CREATE TABLE upserttest(key text, data text); |
||||
|
||||
CREATE UNIQUE INDEX ON upserttest((blurt_and_lock(key))); |
||||
} |
||||
|
||||
teardown |
||||
{ |
||||
DROP TABLE upserttest; |
||||
} |
||||
|
||||
session "controller" |
||||
setup |
||||
{ |
||||
SET default_transaction_isolation = 'read committed'; |
||||
} |
||||
step "controller_locks" {SELECT pg_advisory_lock(sess, lock), sess, lock FROM generate_series(1, 2) a(sess), generate_series(1,3) b(lock);} |
||||
step "controller_unlock_1_1" { SELECT pg_advisory_unlock(1, 1); } |
||||
step "controller_unlock_2_1" { SELECT pg_advisory_unlock(2, 1); } |
||||
step "controller_unlock_1_2" { SELECT pg_advisory_unlock(1, 2); } |
||||
step "controller_unlock_2_2" { SELECT pg_advisory_unlock(2, 2); } |
||||
step "controller_unlock_1_3" { SELECT pg_advisory_unlock(1, 3); } |
||||
step "controller_unlock_2_3" { SELECT pg_advisory_unlock(2, 3); } |
||||
step "controller_show" {SELECT * FROM upserttest; } |
||||
|
||||
session "s1" |
||||
setup |
||||
{ |
||||
SET default_transaction_isolation = 'read committed'; |
||||
SET spec.session = 1; |
||||
} |
||||
step "s1_begin" { BEGIN; } |
||||
step "s1_upsert" { INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s1') ON CONFLICT (blurt_and_lock(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s1'; } |
||||
step "s1_commit" { COMMIT; } |
||||
|
||||
session "s2" |
||||
setup |
||||
{ |
||||
SET default_transaction_isolation = 'read committed'; |
||||
SET spec.session = 2; |
||||
} |
||||
step "s2_begin" { BEGIN; } |
||||
step "s2_upsert" { INSERT INTO upserttest(key, data) VALUES('k1', 'inserted s2') ON CONFLICT (blurt_and_lock(key)) DO UPDATE SET data = upserttest.data || ' with conflict update s2'; } |
||||
step "s2_commit" { COMMIT; } |
||||
|
||||
# Test that speculative locks are correctly acquired and released, s2 |
||||
# inserts, s1 updates. |
||||
permutation |
||||
# acquire a number of locks, to control execution flow - the |
||||
# blurt_and_lock function acquires advisory locks that allow us to |
||||
# continue after a) the optimistic conflict probe b) after the |
||||
# insertion of the speculative tuple. |
||||
"controller_locks" |
||||
"controller_show" |
||||
"s1_upsert" "s2_upsert" |
||||
"controller_show" |
||||
# Switch both sessions to wait on the other lock next time (the speculative insertion) |
||||
"controller_unlock_1_1" "controller_unlock_2_1" |
||||
# Allow both sessions to continue |
||||
"controller_unlock_1_3" "controller_unlock_2_3" |
||||
"controller_show" |
||||
# Allow the second session to finish insertion |
||||
"controller_unlock_2_2" |
||||
# This should now show a successful insertion |
||||
"controller_show" |
||||
# Allow the first session to finish insertion |
||||
"controller_unlock_1_2" |
||||
# This should now show a successful UPSERT |
||||
"controller_show" |
||||
|
||||
# Test that speculative locks are correctly acquired and released, s2 |
||||
# inserts, s1 updates. |
||||
permutation |
||||
# acquire a number of locks, to control execution flow - the |
||||
# blurt_and_lock function acquires advisory locks that allow us to |
||||
# continue after a) the optimistic conflict probe b) after the |
||||
# insertion of the speculative tuple. |
||||
"controller_locks" |
||||
"controller_show" |
||||
"s1_upsert" "s2_upsert" |
||||
"controller_show" |
||||
# Switch both sessions to wait on the other lock next time (the speculative insertion) |
||||
"controller_unlock_1_1" "controller_unlock_2_1" |
||||
# Allow both sessions to continue |
||||
"controller_unlock_1_3" "controller_unlock_2_3" |
||||
"controller_show" |
||||
# Allow the first session to finish insertion |
||||
"controller_unlock_1_2" |
||||
# This should now show a successful insertion |
||||
"controller_show" |
||||
# Allow the second session to finish insertion |
||||
"controller_unlock_2_2" |
||||
# This should now show a successful UPSERT |
||||
"controller_show" |
||||
|
||||
# Test that speculative locks are correctly acquired and released, s2 |
||||
# inserts, s1 updates. With the added complication that transactions |
||||
# don't immediately commit. |
||||
permutation |
||||
# acquire a number of locks, to control execution flow - the |
||||
# blurt_and_lock function acquires advisory locks that allow us to |
||||
# continue after a) the optimistic conflict probe b) after the |
||||
# insertion of the speculative tuple. |
||||
"controller_locks" |
||||
"controller_show" |
||||
"s1_begin" "s2_begin" |
||||
"s1_upsert" "s2_upsert" |
||||
"controller_show" |
||||
# Switch both sessions to wait on the other lock next time (the speculative insertion) |
||||
"controller_unlock_1_1" "controller_unlock_2_1" |
||||
# Allow both sessions to continue |
||||
"controller_unlock_1_3" "controller_unlock_2_3" |
||||
"controller_show" |
||||
# Allow the first session to finish insertion |
||||
"controller_unlock_1_2" |
||||
# But the change isn't visible yet, nor should the second session continue |
||||
"controller_show" |
||||
# Allow the second session to finish insertion, but it's blocked |
||||
"controller_unlock_2_2" |
||||
"controller_show" |
||||
# But committing should unblock |
||||
"s1_commit" |
||||
"controller_show" |
||||
"s2_commit" |
||||
"controller_show" |
Loading…
Reference in new issue