diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 072c7df0ada831616f58f81b1607c5b76e7331d7..01eda70f0544afdc8350c2fa3b9cbe394fb03125 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1863,7 +1863,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate, /* * Get and lock the updated version of the row; if fail, return NULL. */ - copyTuple = EvalPlanQualFetch(estate, relation, lockmode, + copyTuple = EvalPlanQualFetch(estate, relation, lockmode, false /* wait */, tid, priorXmax); if (copyTuple == NULL) @@ -1922,6 +1922,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate, * estate - executor state data * relation - table containing tuple * lockmode - requested tuple lock mode + * noWait - wait mode to pass to heap_lock_tuple * *tid - t_ctid from the outdated tuple (ie, next updated version) * priorXmax - t_xmax from the outdated tuple * @@ -1934,7 +1935,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate, * but we use "int" to avoid having to include heapam.h in executor.h. */ HeapTuple -EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, +EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait, ItemPointer tid, TransactionId priorXmax) { HeapTuple copyTuple = NULL; @@ -1978,14 +1979,23 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, /* * If tuple is being updated by other transaction then we have to - * wait for its commit/abort. + * wait for its commit/abort, or die trying. */ if (TransactionIdIsValid(SnapshotDirty.xmax)) { ReleaseBuffer(buffer); - XactLockTableWait(SnapshotDirty.xmax, - relation, &tuple.t_data->t_ctid, - XLTW_FetchUpdated); + if (noWait) + { + if (!ConditionalXactLockTableWait(SnapshotDirty.xmax)) + ereport(ERROR, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on row in relation \"%s\"", + RelationGetRelationName(relation)))); + } + else + XactLockTableWait(SnapshotDirty.xmax, + relation, &tuple.t_data->t_ctid, + XLTW_FetchUpdated); continue; /* loop back to repeat heap_fetch */ } @@ -2012,7 +2022,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, */ test = heap_lock_tuple(relation, &tuple, estate->es_output_cid, - lockmode, false /* wait */ , + lockmode, noWait, false, &buffer, &hufd); /* We now have two pins on the buffer, get rid of one */ ReleaseBuffer(buffer); diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 298d4b4d0173b71a9fbfffdb2845cb0b0123b8e4..814b61efcbaea189cbccf0eed46f3f18a2d89f7b 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -170,7 +170,7 @@ lnext: } /* updated, so fetch and lock the updated version */ - copyTuple = EvalPlanQualFetch(estate, erm->relation, lockmode, + copyTuple = EvalPlanQualFetch(estate, erm->relation, lockmode, erm->noWait, &hufd.ctid, hufd.xmax); if (copyTuple == NULL) diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 239aff3208827fc5ae01b44c3c5e90ef5e129cb4..02661350d94b85a9069b74377f6e9a7c40de16b7 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -199,7 +199,7 @@ extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate, Relation relation, Index rti, int lockmode, ItemPointer tid, TransactionId priorXmax); extern HeapTuple EvalPlanQualFetch(EState *estate, Relation relation, - int lockmode, ItemPointer tid, TransactionId priorXmax); + int lockmode, bool noWait, ItemPointer tid, TransactionId priorXmax); extern void EvalPlanQualInit(EPQState *epqstate, EState *estate, Plan *subplan, List *auxrowmarks, int epqParam); extern void EvalPlanQualSetPlan(EPQState *epqstate, diff --git a/src/test/isolation/expected/nowait-4.out b/src/test/isolation/expected/nowait-4.out new file mode 100644 index 0000000000000000000000000000000000000000..26f59bef946b2a9a8e68df4a160267c719c0d8c8 --- /dev/null +++ b/src/test/isolation/expected/nowait-4.out @@ -0,0 +1,19 @@ +Parsed test spec with 2 sessions + +starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f +step s2a: SELECT pg_advisory_lock(0); +pg_advisory_lock + + +step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL FOR UPDATE NOWAIT; <waiting ...> +step s2b: UPDATE foo SET data = data; +step s2c: BEGIN; +step s2d: UPDATE foo SET data = data; +step s2e: SELECT pg_advisory_unlock(0); +pg_advisory_unlock + +t +step s1a: <... completed> +error in steps s2e s1a: ERROR: could not obtain lock on row in relation "foo" +step s1b: COMMIT; +step s2f: COMMIT; diff --git a/src/test/isolation/expected/nowait-4_1.out b/src/test/isolation/expected/nowait-4_1.out new file mode 100644 index 0000000000000000000000000000000000000000..959d51baae32c6365215cc05d7771cae47d3bcf0 --- /dev/null +++ b/src/test/isolation/expected/nowait-4_1.out @@ -0,0 +1,19 @@ +Parsed test spec with 2 sessions + +starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f +step s2a: SELECT pg_advisory_lock(0); +pg_advisory_lock + + +step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL FOR UPDATE NOWAIT; <waiting ...> +step s2b: UPDATE foo SET data = data; +step s2c: BEGIN; +step s2d: UPDATE foo SET data = data; +step s2e: SELECT pg_advisory_unlock(0); +pg_advisory_unlock + +t +step s1a: <... completed> +error in steps s2e s1a: ERROR: could not serialize access due to concurrent update +step s1b: COMMIT; +step s2f: COMMIT; diff --git a/src/test/isolation/expected/nowait-5.out b/src/test/isolation/expected/nowait-5.out new file mode 100644 index 0000000000000000000000000000000000000000..c88aae5ef60b0a8f6f787ad2891059add258777f --- /dev/null +++ b/src/test/isolation/expected/nowait-5.out @@ -0,0 +1,37 @@ +Parsed test spec with 3 sessions + +starting permutation: sl1_prep upd_getlock sl1_exec upd_doupdate lk1_doforshare upd_releaselock +step sl1_prep: + PREPARE sl1_run AS SELECT id FROM test_nowait WHERE pg_advisory_lock(0) is not null FOR UPDATE NOWAIT; + +step upd_getlock: + SELECT pg_advisory_lock(0); + +pg_advisory_lock + + +step sl1_exec: + BEGIN ISOLATION LEVEL READ COMMITTED; + EXECUTE sl1_run; + SELECT xmin, xmax, ctid, * FROM test_nowait; + <waiting ...> +step upd_doupdate: + BEGIN ISOLATION LEVEL READ COMMITTED; + UPDATE test_nowait SET value = value WHERE id % 2 = 0; + COMMIT; + +step lk1_doforshare: + BEGIN ISOLATION LEVEL READ COMMITTED; + SELECT id FROM test_nowait WHERE id % 2 = 0 FOR SHARE; + +id + +2 +step upd_releaselock: + SELECT pg_advisory_unlock(0); + +pg_advisory_unlock + +t +step sl1_exec: <... completed> +error in steps upd_releaselock sl1_exec: ERROR: could not obtain lock on row in relation "test_nowait" diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 10c89ff1269d130238b0c6e8c6b4b682e16d9889..3241a91505cf05f6db280673c29b79a50f6513c5 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -22,9 +22,11 @@ test: aborted-keyrevoke test: multixact-no-deadlock test: multixact-no-forget test: propagate-lock-delete -test: drop-index-concurrently-1 -test: alter-table-1 -test: timeouts test: nowait test: nowait-2 test: nowait-3 +test: nowait-4 +test: nowait-5 +test: drop-index-concurrently-1 +test: alter-table-1 +test: timeouts diff --git a/src/test/isolation/specs/nowait-4.spec b/src/test/isolation/specs/nowait-4.spec new file mode 100644 index 0000000000000000000000000000000000000000..5dbebcabb03cb6f2bf343c3d86d91358e7fd328d --- /dev/null +++ b/src/test/isolation/specs/nowait-4.spec @@ -0,0 +1,30 @@ +# Test NOWAIT with an updated tuple chain. + +setup +{ + CREATE TABLE foo ( + id int PRIMARY KEY, + data text NOT NULL + ); + INSERT INTO foo VALUES (1, 'x'); +} + +teardown +{ + DROP TABLE foo; +} + +session "s1" +setup { BEGIN; } +step "s1a" { SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL FOR UPDATE NOWAIT; } +step "s1b" { COMMIT; } + +session "s2" +step "s2a" { SELECT pg_advisory_lock(0); } +step "s2b" { UPDATE foo SET data = data; } +step "s2c" { BEGIN; } +step "s2d" { UPDATE foo SET data = data; } +step "s2e" { SELECT pg_advisory_unlock(0); } +step "s2f" { COMMIT; } + +permutation "s2a" "s1a" "s2b" "s2c" "s2d" "s2e" "s1b" "s2f" diff --git a/src/test/isolation/specs/nowait-5.spec b/src/test/isolation/specs/nowait-5.spec new file mode 100644 index 0000000000000000000000000000000000000000..75e9462fc1fd94bc1b9b4e7395e10573b186af1e --- /dev/null +++ b/src/test/isolation/specs/nowait-5.spec @@ -0,0 +1,57 @@ +# Test NOWAIT on an updated tuple chain + +setup +{ + + DROP TABLE IF EXISTS test_nowait; + CREATE TABLE test_nowait ( + id integer PRIMARY KEY, + value integer not null + ); + + INSERT INTO test_nowait + SELECT x,x FROM generate_series(1,2) x; +} + +teardown +{ + DROP TABLE test_nowait; +} + +session "sl1" +step "sl1_prep" { + PREPARE sl1_run AS SELECT id FROM test_nowait WHERE pg_advisory_lock(0) is not null FOR UPDATE NOWAIT; +} +step "sl1_exec" { + BEGIN ISOLATION LEVEL READ COMMITTED; + EXECUTE sl1_run; + SELECT xmin, xmax, ctid, * FROM test_nowait; +} +teardown { COMMIT; } + +# A session that's used for an UPDATE of the rows to be locked, for when we're testing ctid +# chain following. +session "upd" +step "upd_getlock" { + SELECT pg_advisory_lock(0); +} +step "upd_doupdate" { + BEGIN ISOLATION LEVEL READ COMMITTED; + UPDATE test_nowait SET value = value WHERE id % 2 = 0; + COMMIT; +} +step "upd_releaselock" { + SELECT pg_advisory_unlock(0); +} + +# A session that acquires locks that sl1 is supposed to avoid blocking on +session "lk1" +step "lk1_doforshare" { + BEGIN ISOLATION LEVEL READ COMMITTED; + SELECT id FROM test_nowait WHERE id % 2 = 0 FOR SHARE; +} +teardown { + COMMIT; +} + +permutation "sl1_prep" "upd_getlock" "sl1_exec" "upd_doupdate" "lk1_doforshare" "upd_releaselock"