Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit7b2262a

Browse files
committed
Fix check for conflicting session- vs transaction-level locks.
We have an implementation restriction that PREPARE TRANSACTION can'thandle cases where both session-lifespan and transaction-lifespan locksare held on the same lockable object. (That's because we'd otherwiseneed to acquire a new PROCLOCK entry during post-prepare cleanup, whichis an operation that might fail. The situation can only arise with oddusages of advisory locks, so removing the restriction is probably notworth the amount of effort it would take.) AtPrepare_Locks attemptedto enforce this, but its logic was many bricks shy of a load, becauseit only detected cases where the session and transaction locks had thesame lockmode. Locks of different modes on the same object would leadto the rather unhelpful message "PANIC: we seem to have dropped a bitsomewhere".To fix, build a transient hashtable with one entry per locktag,not one per locktag + mode, and use that to detect conflicts.Per bug #17122 from Alexander Pyhalov. This bug is ancient,so back-patch to all supported branches.Discussion:https://postgr.es/m/17122-04f3c32098a62233@postgresql.org
1 parent9329b92 commit7b2262a

File tree

4 files changed

+144
-24
lines changed

4 files changed

+144
-24
lines changed

‎src/backend/storage/lmgr/lock.c

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3085,31 +3085,124 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc,
30853085
}
30863086
}
30873087

3088+
/*
3089+
* CheckForSessionAndXactLocks
3090+
*Check to see if transaction holds both session-level and xact-level
3091+
*locks on the same object; if so, throw an error.
3092+
*
3093+
* If we have both session- and transaction-level locks on the same object,
3094+
* PREPARE TRANSACTION must fail. This should never happen with regular
3095+
* locks, since we only take those at session level in some special operations
3096+
* like VACUUM. It's possible to hit this with advisory locks, though.
3097+
*
3098+
* It would be nice if we could keep the session hold and give away the
3099+
* transactional hold to the prepared xact. However, that would require two
3100+
* PROCLOCK objects, and we cannot be sure that another PROCLOCK will be
3101+
* available when it comes time for PostPrepare_Locks to do the deed.
3102+
* So for now, we error out while we can still do so safely.
3103+
*
3104+
* Since the LOCALLOCK table stores a separate entry for each lockmode,
3105+
* we can't implement this check by examining LOCALLOCK entries in isolation.
3106+
* We must build a transient hashtable that is indexed by locktag only.
3107+
*/
3108+
staticvoid
3109+
CheckForSessionAndXactLocks(void)
3110+
{
3111+
typedefstruct
3112+
{
3113+
LOCKTAGlock;/* identifies the lockable object */
3114+
boolsessLock;/* is any lockmode held at session level? */
3115+
boolxactLock;/* is any lockmode held at xact level? */
3116+
}PerLockTagEntry;
3117+
3118+
HASHCTLhash_ctl;
3119+
HTAB*lockhtab;
3120+
HASH_SEQ_STATUSstatus;
3121+
LOCALLOCK*locallock;
3122+
3123+
/* Create a local hash table keyed by LOCKTAG only */
3124+
hash_ctl.keysize=sizeof(LOCKTAG);
3125+
hash_ctl.entrysize=sizeof(PerLockTagEntry);
3126+
hash_ctl.hcxt=CurrentMemoryContext;
3127+
3128+
lockhtab=hash_create("CheckForSessionAndXactLocks table",
3129+
256,/* arbitrary initial size */
3130+
&hash_ctl,
3131+
HASH_ELEM |HASH_BLOBS |HASH_CONTEXT);
3132+
3133+
/* Scan local lock table to find entries for each LOCKTAG */
3134+
hash_seq_init(&status,LockMethodLocalHash);
3135+
3136+
while ((locallock= (LOCALLOCK*)hash_seq_search(&status))!=NULL)
3137+
{
3138+
LOCALLOCKOWNER*lockOwners=locallock->lockOwners;
3139+
PerLockTagEntry*hentry;
3140+
boolfound;
3141+
inti;
3142+
3143+
/*
3144+
* Ignore VXID locks. We don't want those to be held by prepared
3145+
* transactions, since they aren't meaningful after a restart.
3146+
*/
3147+
if (locallock->tag.lock.locktag_type==LOCKTAG_VIRTUALTRANSACTION)
3148+
continue;
3149+
3150+
/* Ignore it if we don't actually hold the lock */
3151+
if (locallock->nLocks <=0)
3152+
continue;
3153+
3154+
/* Otherwise, find or make an entry in lockhtab */
3155+
hentry= (PerLockTagEntry*)hash_search(lockhtab,
3156+
(void*)&locallock->tag.lock,
3157+
HASH_ENTER,&found);
3158+
if (!found)/* initialize, if newly created */
3159+
hentry->sessLock=hentry->xactLock= false;
3160+
3161+
/* Scan to see if we hold lock at session or xact level or both */
3162+
for (i=locallock->numLockOwners-1;i >=0;i--)
3163+
{
3164+
if (lockOwners[i].owner==NULL)
3165+
hentry->sessLock= true;
3166+
else
3167+
hentry->xactLock= true;
3168+
}
3169+
3170+
/*
3171+
* We can throw error immediately when we see both types of locks; no
3172+
* need to wait around to see if there are more violations.
3173+
*/
3174+
if (hentry->sessLock&&hentry->xactLock)
3175+
ereport(ERROR,
3176+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3177+
errmsg("cannot PREPARE while holding both session-level and transaction-level locks on the same object")));
3178+
}
3179+
3180+
/* Success, so clean up */
3181+
hash_destroy(lockhtab);
3182+
}
3183+
30883184
/*
30893185
* AtPrepare_Locks
30903186
*Do the preparatory work for a PREPARE: make 2PC state file records
30913187
*for all locks currently held.
30923188
*
30933189
* Session-level locks are ignored, as are VXID locks.
30943190
*
3095-
* There are some special cases that we error out on: we can't be holding any
3096-
* locks at both session and transaction level (since we must either keep or
3097-
* give away the PROCLOCK object), and we can't be holding any locks on
3098-
* temporary objects (since that would mess up the current backend if it tries
3099-
* to exit before the prepared xact is committed).
3191+
* For the most part, we don't need to touch shared memory for this ---
3192+
* all the necessary state information is in the locallock table.
3193+
* Fast-path locks are an exception, however: we move any such locks to
3194+
* the main table before allowing PREPARE TRANSACTION to succeed.
31003195
*/
31013196
void
31023197
AtPrepare_Locks(void)
31033198
{
31043199
HASH_SEQ_STATUSstatus;
31053200
LOCALLOCK*locallock;
31063201

3107-
/*
3108-
* For the most part, we don't need to touch shared memory for this ---
3109-
* all the necessary state information is in the locallock table.
3110-
* Fast-path locks are an exception, however: we move any such locks to
3111-
* the main table before allowing PREPARE TRANSACTION to succeed.
3112-
*/
3202+
/* First, verify there aren't locks of both xact and session level */
3203+
CheckForSessionAndXactLocks();
3204+
3205+
/* Now do the per-locallock cleanup work */
31133206
hash_seq_init(&status,LockMethodLocalHash);
31143207

31153208
while ((locallock= (LOCALLOCK*)hash_seq_search(&status))!=NULL)
@@ -3145,19 +3238,7 @@ AtPrepare_Locks(void)
31453238
if (!haveXactLock)
31463239
continue;
31473240

3148-
/*
3149-
* If we have both session- and transaction-level locks, fail. This
3150-
* should never happen with regular locks, since we only take those at
3151-
* session level in some special operations like VACUUM. It's
3152-
* possible to hit this with advisory locks, though.
3153-
*
3154-
* It would be nice if we could keep the session hold and give away
3155-
* the transactional hold to the prepared xact. However, that would
3156-
* require two PROCLOCK objects, and we cannot be sure that another
3157-
* PROCLOCK will be available when it comes time for PostPrepare_Locks
3158-
* to do the deed. So for now, we error out while we can still do so
3159-
* safely.
3160-
*/
3241+
/* This can't happen, because we already checked it */
31613242
if (haveSessionLock)
31623243
ereport(ERROR,
31633244
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),

‎src/test/regress/expected/prepared_xacts.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,22 @@ SELECT gid FROM pg_prepared_xacts;
151151

152152
-- Clean up
153153
DROP TABLE pxtest1;
154+
-- Test detection of session-level and xact-level locks on same object
155+
BEGIN;
156+
SELECT pg_advisory_lock(1);
157+
pg_advisory_lock
158+
------------------
159+
160+
(1 row)
161+
162+
SELECT pg_advisory_xact_lock_shared(1);
163+
pg_advisory_xact_lock_shared
164+
------------------------------
165+
166+
(1 row)
167+
168+
PREPARE TRANSACTION 'foo6'; -- fails
169+
ERROR: cannot PREPARE while holding both session-level and transaction-level locks on the same object
154170
-- Test subtransactions
155171
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
156172
CREATE TABLE pxtest2 (a int);

‎src/test/regress/expected/prepared_xacts_1.out

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,23 @@ SELECT gid FROM pg_prepared_xacts;
153153

154154
-- Clean up
155155
DROP TABLE pxtest1;
156+
-- Test detection of session-level and xact-level locks on same object
157+
BEGIN;
158+
SELECT pg_advisory_lock(1);
159+
pg_advisory_lock
160+
------------------
161+
162+
(1 row)
163+
164+
SELECT pg_advisory_xact_lock_shared(1);
165+
pg_advisory_xact_lock_shared
166+
------------------------------
167+
168+
(1 row)
169+
170+
PREPARE TRANSACTION 'foo6'; -- fails
171+
ERROR: prepared transactions are disabled
172+
HINT: Set max_prepared_transactions to a nonzero value.
156173
-- Test subtransactions
157174
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
158175
CREATE TABLE pxtest2 (a int);

‎src/test/regress/sql/prepared_xacts.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ SELECT gid FROM pg_prepared_xacts;
8888
-- Clean up
8989
DROPTABLE pxtest1;
9090

91+
-- Test detection of session-level and xact-level locks on same object
92+
BEGIN;
93+
SELECT pg_advisory_lock(1);
94+
SELECT pg_advisory_xact_lock_shared(1);
95+
PREPARE TRANSACTION'foo6';-- fails
96+
9197
-- Test subtransactions
9298
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
9399
CREATETABLEpxtest2 (aint);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp