aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/storage/lmgr/lock.c129
-rw-r--r--src/test/regress/expected/prepared_xacts.out16
-rw-r--r--src/test/regress/expected/prepared_xacts_1.out17
-rw-r--r--src/test/regress/sql/prepared_xacts.sql6
4 files changed, 144 insertions, 24 deletions
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 108b4d90238..8c2138f1071 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -3205,17 +3205,112 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc,
}
/*
+ * CheckForSessionAndXactLocks
+ * Check to see if transaction holds both session-level and xact-level
+ * locks on the same object; if so, throw an error.
+ *
+ * If we have both session- and transaction-level locks on the same object,
+ * PREPARE TRANSACTION must fail. This should never happen with regular
+ * locks, since we only take those at session level in some special operations
+ * like VACUUM. It's possible to hit this with advisory locks, though.
+ *
+ * It would be nice if we could keep the session hold and give away the
+ * transactional hold to the prepared xact. However, that would require two
+ * PROCLOCK objects, and we cannot be sure that another PROCLOCK will be
+ * available when it comes time for PostPrepare_Locks to do the deed.
+ * So for now, we error out while we can still do so safely.
+ *
+ * Since the LOCALLOCK table stores a separate entry for each lockmode,
+ * we can't implement this check by examining LOCALLOCK entries in isolation.
+ * We must build a transient hashtable that is indexed by locktag only.
+ */
+static void
+CheckForSessionAndXactLocks(void)
+{
+ typedef struct
+ {
+ LOCKTAG lock; /* identifies the lockable object */
+ bool sessLock; /* is any lockmode held at session level? */
+ bool xactLock; /* is any lockmode held at xact level? */
+ } PerLockTagEntry;
+
+ HASHCTL hash_ctl;
+ HTAB *lockhtab;
+ HASH_SEQ_STATUS status;
+ LOCALLOCK *locallock;
+
+ /* Create a local hash table keyed by LOCKTAG only */
+ hash_ctl.keysize = sizeof(LOCKTAG);
+ hash_ctl.entrysize = sizeof(PerLockTagEntry);
+ hash_ctl.hcxt = CurrentMemoryContext;
+
+ lockhtab = hash_create("CheckForSessionAndXactLocks table",
+ 256, /* arbitrary initial size */
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+
+ /* Scan local lock table to find entries for each LOCKTAG */
+ hash_seq_init(&status, LockMethodLocalHash);
+
+ while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+ {
+ LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
+ PerLockTagEntry *hentry;
+ bool found;
+ int i;
+
+ /*
+ * Ignore VXID locks. We don't want those to be held by prepared
+ * transactions, since they aren't meaningful after a restart.
+ */
+ if (locallock->tag.lock.locktag_type == LOCKTAG_VIRTUALTRANSACTION)
+ continue;
+
+ /* Ignore it if we don't actually hold the lock */
+ if (locallock->nLocks <= 0)
+ continue;
+
+ /* Otherwise, find or make an entry in lockhtab */
+ hentry = (PerLockTagEntry *) hash_search(lockhtab,
+ (void *) &locallock->tag.lock,
+ HASH_ENTER, &found);
+ if (!found) /* initialize, if newly created */
+ hentry->sessLock = hentry->xactLock = false;
+
+ /* Scan to see if we hold lock at session or xact level or both */
+ for (i = locallock->numLockOwners - 1; i >= 0; i--)
+ {
+ if (lockOwners[i].owner == NULL)
+ hentry->sessLock = true;
+ else
+ hentry->xactLock = true;
+ }
+
+ /*
+ * We can throw error immediately when we see both types of locks; no
+ * need to wait around to see if there are more violations.
+ */
+ if (hentry->sessLock && hentry->xactLock)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot PREPARE while holding both session-level and transaction-level locks on the same object")));
+ }
+
+ /* Success, so clean up */
+ hash_destroy(lockhtab);
+}
+
+/*
* AtPrepare_Locks
* Do the preparatory work for a PREPARE: make 2PC state file records
* for all locks currently held.
*
* Session-level locks are ignored, as are VXID locks.
*
- * There are some special cases that we error out on: we can't be holding any
- * locks at both session and transaction level (since we must either keep or
- * give away the PROCLOCK object), and we can't be holding any locks on
- * temporary objects (since that would mess up the current backend if it tries
- * to exit before the prepared xact is committed).
+ * For the most part, we don't need to touch shared memory for this ---
+ * all the necessary state information is in the locallock table.
+ * Fast-path locks are an exception, however: we move any such locks to
+ * the main table before allowing PREPARE TRANSACTION to succeed.
*/
void
AtPrepare_Locks(void)
@@ -3223,12 +3318,10 @@ AtPrepare_Locks(void)
HASH_SEQ_STATUS status;
LOCALLOCK *locallock;
- /*
- * For the most part, we don't need to touch shared memory for this ---
- * all the necessary state information is in the locallock table.
- * Fast-path locks are an exception, however: we move any such locks to
- * the main table before allowing PREPARE TRANSACTION to succeed.
- */
+ /* First, verify there aren't locks of both xact and session level */
+ CheckForSessionAndXactLocks();
+
+ /* Now do the per-locallock cleanup work */
hash_seq_init(&status, LockMethodLocalHash);
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
@@ -3264,19 +3357,7 @@ AtPrepare_Locks(void)
if (!haveXactLock)
continue;
- /*
- * If we have both session- and transaction-level locks, fail. This
- * should never happen with regular locks, since we only take those at
- * session level in some special operations like VACUUM. It's
- * possible to hit this with advisory locks, though.
- *
- * It would be nice if we could keep the session hold and give away
- * the transactional hold to the prepared xact. However, that would
- * require two PROCLOCK objects, and we cannot be sure that another
- * PROCLOCK will be available when it comes time for PostPrepare_Locks
- * to do the deed. So for now, we error out while we can still do so
- * safely.
- */
+ /* This can't happen, because we already checked it */
if (haveSessionLock)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out
index eb77c18788e..ba8e3ccc6c9 100644
--- a/src/test/regress/expected/prepared_xacts.out
+++ b/src/test/regress/expected/prepared_xacts.out
@@ -151,6 +151,22 @@ SELECT gid FROM pg_prepared_xacts;
-- Clean up
DROP TABLE pxtest1;
+-- Test detection of session-level and xact-level locks on same object
+BEGIN;
+SELECT pg_advisory_lock(1);
+ pg_advisory_lock
+------------------
+
+(1 row)
+
+SELECT pg_advisory_xact_lock_shared(1);
+ pg_advisory_xact_lock_shared
+------------------------------
+
+(1 row)
+
+PREPARE TRANSACTION 'foo6'; -- fails
+ERROR: cannot PREPARE while holding both session-level and transaction-level locks on the same object
-- Test subtransactions
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
CREATE TABLE pxtest2 (a int);
diff --git a/src/test/regress/expected/prepared_xacts_1.out b/src/test/regress/expected/prepared_xacts_1.out
index 0857d259e07..2cd50ad9470 100644
--- a/src/test/regress/expected/prepared_xacts_1.out
+++ b/src/test/regress/expected/prepared_xacts_1.out
@@ -153,6 +153,23 @@ SELECT gid FROM pg_prepared_xacts;
-- Clean up
DROP TABLE pxtest1;
+-- Test detection of session-level and xact-level locks on same object
+BEGIN;
+SELECT pg_advisory_lock(1);
+ pg_advisory_lock
+------------------
+
+(1 row)
+
+SELECT pg_advisory_xact_lock_shared(1);
+ pg_advisory_xact_lock_shared
+------------------------------
+
+(1 row)
+
+PREPARE TRANSACTION 'foo6'; -- fails
+ERROR: prepared transactions are disabled
+HINT: Set max_prepared_transactions to a nonzero value.
-- Test subtransactions
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
CREATE TABLE pxtest2 (a int);
diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql
index d8249a27dc2..2f0bb55bb49 100644
--- a/src/test/regress/sql/prepared_xacts.sql
+++ b/src/test/regress/sql/prepared_xacts.sql
@@ -88,6 +88,12 @@ SELECT gid FROM pg_prepared_xacts;
-- Clean up
DROP TABLE pxtest1;
+-- Test detection of session-level and xact-level locks on same object
+BEGIN;
+SELECT pg_advisory_lock(1);
+SELECT pg_advisory_xact_lock_shared(1);
+PREPARE TRANSACTION 'foo6'; -- fails
+
-- Test subtransactions
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
CREATE TABLE pxtest2 (a int);