aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/access/transam/multixact.c33
-rw-r--r--src/backend/utils/time/tqual.c36
-rw-r--r--src/include/access/multixact.h1
-rw-r--r--src/test/isolation/expected/propagate-lock-delete.out105
-rw-r--r--src/test/isolation/isolation_schedule1
-rw-r--r--src/test/isolation/specs/propagate-lock-delete.spec42
6 files changed, 216 insertions, 2 deletions
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 55a8ca7ac49..03581bea663 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1275,6 +1275,39 @@ retry:
}
/*
+ * MultiXactHasRunningRemoteMembers
+ * Does the given multixact have still-live members from
+ * transactions other than our own?
+ */
+bool
+MultiXactHasRunningRemoteMembers(MultiXactId multi)
+{
+ MultiXactMember *members;
+ int nmembers;
+ int i;
+
+ nmembers = GetMultiXactIdMembers(multi, &members, true);
+ if (nmembers <= 0)
+ return false;
+
+ for (i = 0; i < nmembers; i++)
+ {
+ /* not interested in our own members */
+ if (TransactionIdIsCurrentTransactionId(members[i].xid))
+ continue;
+
+ if (TransactionIdIsInProgress(members[i].xid))
+ {
+ pfree(members);
+ return true;
+ }
+ }
+
+ pfree(members);
+ return false;
+}
+
+/*
* mxactMemberComparator
* qsort comparison function for MultiXactMember
*
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index f787f2cbdc9..44b4ddcb02f 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -686,8 +686,36 @@ HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid,
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return HeapTupleMayBeUpdated;
- if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */
- return HeapTupleMayBeUpdated;
+ if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+ {
+ TransactionId xmax;
+
+ xmax = HeapTupleHeaderGetRawXmax(tuple);
+
+ /*
+ * Careful here: even though this tuple was created by our own
+ * transaction, it might be locked by other transactions, if
+ * the original version was key-share locked when we updated
+ * it.
+ */
+
+ if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+ {
+ if (MultiXactHasRunningRemoteMembers(xmax))
+ return HeapTupleBeingUpdated;
+ else
+ return HeapTupleMayBeUpdated;
+ }
+
+ /* if locker is gone, all's well */
+ if (!TransactionIdIsInProgress(xmax))
+ return HeapTupleMayBeUpdated;
+
+ if (!TransactionIdIsCurrentTransactionId(xmax))
+ return HeapTupleBeingUpdated;
+ else
+ return HeapTupleMayBeUpdated;
+ }
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
@@ -700,7 +728,11 @@ HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid,
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
+ {
+ if (MultiXactHasRunningRemoteMembers(HeapTupleHeaderGetRawXmax(tuple)))
+ return HeapTupleBeingUpdated;
return HeapTupleMayBeUpdated;
+ }
else
{
if (HeapTupleHeaderGetCmax(tuple) >= curcid)
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 0e3b273b9e2..5f82907dacc 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -89,6 +89,7 @@ extern bool MultiXactIdIsRunning(MultiXactId multi);
extern void MultiXactIdSetOldestMember(void);
extern int GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **xids,
bool allow_old);
+extern bool MultiXactHasRunningRemoteMembers(MultiXactId multi);
extern bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2);
extern bool MultiXactIdPrecedesOrEquals(MultiXactId multi1,
MultiXactId multi2);
diff --git a/src/test/isolation/expected/propagate-lock-delete.out b/src/test/isolation/expected/propagate-lock-delete.out
new file mode 100644
index 00000000000..b668b895f1a
--- /dev/null
+++ b/src/test/isolation/expected/propagate-lock-delete.out
@@ -0,0 +1,105 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1b s1l s2b s2l s3b s3u s3d s1c s2c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s2b: BEGIN;
+step s2l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u: UPDATE parent SET c=lower(c);
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s2c: COMMIT;
+step s3d: <... completed>
+error in steps s2c s3d: ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s2b s2l s3b s3u s3svu s3d s1c s2c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s2b: BEGIN;
+step s2l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u: UPDATE parent SET c=lower(c);
+step s3svu: SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s2c: COMMIT;
+step s3d: <... completed>
+error in steps s2c s3d: ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s2b s2l s3b s3u2 s3d s1c s2c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s2b: BEGIN;
+step s2l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u2: UPDATE parent SET i = i;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s2c: COMMIT;
+step s3d: <... completed>
+error in steps s2c s3d: ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s2b s2l s3b s3u2 s3svu s3d s1c s2c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s2b: BEGIN;
+step s2l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u2: UPDATE parent SET i = i;
+step s3svu: SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s2c: COMMIT;
+step s3d: <... completed>
+error in steps s2c s3d: ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s3b s3u s3d s1c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u: UPDATE parent SET c=lower(c);
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s3d: <... completed>
+error in steps s1c s3d: ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s3b s3u s3svu s3d s1c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u: UPDATE parent SET c=lower(c);
+step s3svu: SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s3d: <... completed>
+error in steps s1c s3d: ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s3b s3u2 s3d s1c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u2: UPDATE parent SET i = i;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s3d: <... completed>
+error in steps s1c s3d: ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
+
+starting permutation: s1b s1l s3b s3u2 s3svu s3d s1c s3c
+step s1b: BEGIN;
+step s1l: INSERT INTO child VALUES (1);
+step s3b: BEGIN;
+step s3u2: UPDATE parent SET i = i;
+step s3svu: SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f;
+step s3d: DELETE FROM parent; <waiting ...>
+step s1c: COMMIT;
+step s3d: <... completed>
+error in steps s1c s3d: ERROR: update or delete on table "parent" violates foreign key constraint "child_i_fkey" on table "child"
+step s3c: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index dd4b4041836..1e73b4aebd7 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -21,5 +21,6 @@ test: delete-abort-savept-2
test: aborted-keyrevoke
test: multixact-no-deadlock
test: multixact-no-forget
+test: propagate-lock-delete
test: drop-index-concurrently-1
test: timeouts
diff --git a/src/test/isolation/specs/propagate-lock-delete.spec b/src/test/isolation/specs/propagate-lock-delete.spec
new file mode 100644
index 00000000000..857c36b3dbb
--- /dev/null
+++ b/src/test/isolation/specs/propagate-lock-delete.spec
@@ -0,0 +1,42 @@
+# When an update propagates a preexisting lock on the updated tuple, make sure
+# we don't ignore the lock in subsequent operations of the new version. (The
+# version with the aborted savepoint uses a slightly different code path).
+setup
+{
+ create table parent (i int, c char(3));
+ create unique index parent_idx on parent (i);
+ insert into parent values (1, 'AAA');
+ create table child (i int references parent(i));
+}
+
+teardown
+{
+ drop table child, parent;
+}
+
+session "s1"
+step "s1b" { BEGIN; }
+step "s1l" { INSERT INTO child VALUES (1); }
+step "s1c" { COMMIT; }
+
+session "s2"
+step "s2b" { BEGIN; }
+step "s2l" { INSERT INTO child VALUES (1); }
+step "s2c" { COMMIT; }
+
+session "s3"
+step "s3b" { BEGIN; }
+step "s3u" { UPDATE parent SET c=lower(c); } # no key update
+step "s3u2" { UPDATE parent SET i = i; } # key update
+step "s3svu" { SAVEPOINT f; UPDATE parent SET c = 'bbb'; ROLLBACK TO f; }
+step "s3d" { DELETE FROM parent; }
+step "s3c" { COMMIT; }
+
+permutation "s1b" "s1l" "s2b" "s2l" "s3b" "s3u" "s3d" "s1c" "s2c" "s3c"
+permutation "s1b" "s1l" "s2b" "s2l" "s3b" "s3u" "s3svu" "s3d" "s1c" "s2c" "s3c"
+permutation "s1b" "s1l" "s2b" "s2l" "s3b" "s3u2" "s3d" "s1c" "s2c" "s3c"
+permutation "s1b" "s1l" "s2b" "s2l" "s3b" "s3u2" "s3svu" "s3d" "s1c" "s2c" "s3c"
+permutation "s1b" "s1l" "s3b" "s3u" "s3d" "s1c" "s3c"
+permutation "s1b" "s1l" "s3b" "s3u" "s3svu" "s3d" "s1c" "s3c"
+permutation "s1b" "s1l" "s3b" "s3u2" "s3d" "s1c" "s3c"
+permutation "s1b" "s1l" "s3b" "s3u2" "s3svu" "s3d" "s1c" "s3c"