diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/storage/lmgr/README | 16 | ||||
-rw-r--r-- | src/backend/storage/lmgr/lock.c | 289 | ||||
-rw-r--r-- | src/backend/storage/lmgr/proc.c | 10 | ||||
-rw-r--r-- | src/include/storage/lock.h | 12 |
4 files changed, 186 insertions, 141 deletions
diff --git a/src/backend/storage/lmgr/README b/src/backend/storage/lmgr/README index 3c7c9e55e8b..88b451248b0 100644 --- a/src/backend/storage/lmgr/README +++ b/src/backend/storage/lmgr/README @@ -587,8 +587,8 @@ The caller can then send a cancellation signal. This implements the principle that autovacuum has a low locking priority (eg it must not block DDL on the table). -User Locks ----------- +User Locks (Advisory Locks) +--------------------------- User locks are handled totally on the application side as long term cooperative locks which may extend beyond the normal transaction boundaries. @@ -602,12 +602,12 @@ level by someone. User locks and normal locks are completely orthogonal and they don't interfere with each other. -There are two types of user locks: session level and transaction level. -Session level user locks are not released at transaction end. They must -be released explicitly by the application --- but they are released -automatically when a backend terminates. On the other hand, transaction -level user locks are released automatically at the end of the transaction -as like as other normal locks. +User locks can be acquired either at session level or transaction level. +A session-level lock request is not automatically released at transaction +end, but must be explicitly released by the application. (However, any +remaining locks are always released at session end.) Transaction-level +user lock requests behave the same as normal lock requests, in that they +are released at transaction end and do not need explicit unlocking. Locking during Hot Standby -------------------------- diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index a216fb90aec..50b95aca2e6 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -114,6 +114,50 @@ static const char *const lock_mode_names[] = "AccessExclusiveLock" }; +#ifndef LOCK_DEBUG +static bool Dummy_trace = false; +#endif + +static const LockMethodData default_lockmethod = { + AccessExclusiveLock, /* highest valid lock mode number */ + LockConflicts, + lock_mode_names, +#ifdef LOCK_DEBUG + &Trace_locks +#else + &Dummy_trace +#endif +}; + +static const LockMethodData user_lockmethod = { + AccessExclusiveLock, /* highest valid lock mode number */ + LockConflicts, + lock_mode_names, +#ifdef LOCK_DEBUG + &Trace_userlocks +#else + &Dummy_trace +#endif +}; + +/* + * map from lock method id to the lock table data structures + */ +static const LockMethod LockMethods[] = { + NULL, + &default_lockmethod, + &user_lockmethod +}; + + +/* Record that's written to 2PC state file when a lock is persisted */ +typedef struct TwoPhaseLockRecord +{ + LOCKTAG locktag; + LOCKMODE lockmode; +} TwoPhaseLockRecord; + + /* * Count of the number of fast path lock slots we believe to be used. This * might be higher than the real number if another backend has transferred @@ -193,51 +237,6 @@ typedef struct FastPathStrongRelationLockData *FastPathStrongRelationLocks; -#ifndef LOCK_DEBUG -static bool Dummy_trace = false; -#endif - -static const LockMethodData default_lockmethod = { - AccessExclusiveLock, /* highest valid lock mode number */ - true, - LockConflicts, - lock_mode_names, -#ifdef LOCK_DEBUG - &Trace_locks -#else - &Dummy_trace -#endif -}; - -static const LockMethodData user_lockmethod = { - AccessExclusiveLock, /* highest valid lock mode number */ - true, - LockConflicts, - lock_mode_names, -#ifdef LOCK_DEBUG - &Trace_userlocks -#else - &Dummy_trace -#endif -}; - -/* - * map from lock method id to the lock table data structures - */ -static const LockMethod LockMethods[] = { - NULL, - &default_lockmethod, - &user_lockmethod -}; - - -/* Record that's written to 2PC state file when a lock is persisted */ -typedef struct TwoPhaseLockRecord -{ - LOCKTAG locktag; - LOCKMODE lockmode; -} TwoPhaseLockRecord; - /* * Pointers to hash tables containing lock state @@ -342,7 +341,7 @@ static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner); static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode); static void FinishStrongLockAcquire(void); static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner); -static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner); +static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock); static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode, PROCLOCK *proclock, LockMethod lockMethodTable); static void CleanUpLock(LOCK *lock, PROCLOCK *proclock, @@ -621,11 +620,11 @@ LockAcquireExtended(const LOCKTAG *locktag, lockMethodTable->lockModeNames[lockmode]); #endif - /* Session locks are never transactional, else check table */ - if (!sessionLock && lockMethodTable->transactional) - owner = CurrentResourceOwner; - else + /* Identify owner for lock */ + if (sessionLock) owner = NULL; + else + owner = CurrentResourceOwner; /* * Find or create a LOCALLOCK entry for this lock and lockmode @@ -1650,11 +1649,11 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) ResourceOwner owner; int i; - /* Session locks are never transactional, else check table */ - if (!sessionLock && lockMethodTable->transactional) - owner = CurrentResourceOwner; - else + /* Identify owner for lock */ + if (sessionLock) owner = NULL; + else + owner = CurrentResourceOwner; for (i = locallock->numLockOwners - 1; i >= 0; i--) { @@ -1780,31 +1779,6 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) } /* - * LockReleaseSession -- Release all session locks of the specified lock method - * that are held by the current process. - */ -void -LockReleaseSession(LOCKMETHODID lockmethodid) -{ - HASH_SEQ_STATUS status; - LOCALLOCK *locallock; - - if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) - elog(ERROR, "unrecognized lock method: %d", lockmethodid); - - hash_seq_init(&status, LockMethodLocalHash); - - while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) - { - /* Ignore items that are not of the specified lock method */ - if (LOCALLOCK_LOCKMETHOD(*locallock) != lockmethodid) - continue; - - ReleaseLockForOwner(locallock, NULL); - } -} - -/* * LockReleaseAll -- Release all locks of the specified lock method that * are held by the current process. * @@ -2058,6 +2032,31 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) } /* + * LockReleaseSession -- Release all session locks of the specified lock method + * that are held by the current process. + */ +void +LockReleaseSession(LOCKMETHODID lockmethodid) +{ + HASH_SEQ_STATUS status; + LOCALLOCK *locallock; + + if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) + elog(ERROR, "unrecognized lock method: %d", lockmethodid); + + hash_seq_init(&status, LockMethodLocalHash); + + while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + { + /* Ignore items that are not of the specified lock method */ + if (LOCALLOCK_LOCKMETHOD(*locallock) != lockmethodid) + continue; + + ReleaseLockIfHeld(locallock, true); + } +} + +/* * LockReleaseCurrentOwner * Release all locks belonging to CurrentResourceOwner */ @@ -2071,25 +2070,37 @@ LockReleaseCurrentOwner(void) while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) { - /* Ignore items that must be nontransactional */ - if (!LockMethods[LOCALLOCK_LOCKMETHOD(*locallock)]->transactional) - continue; - - ReleaseLockForOwner(locallock, CurrentResourceOwner); + ReleaseLockIfHeld(locallock, false); } } /* - * Subroutine to release a lock belonging to the 'owner' if found. - * 'owner' can be NULL to release a session lock. + * ReleaseLockIfHeld + * Release any session-level locks on this lockable object if sessionLock + * is true; else, release any locks held by CurrentResourceOwner. + * + * It is tempting to pass this a ResourceOwner pointer (or NULL for session + * locks), but without refactoring LockRelease() we cannot support releasing + * locks belonging to resource owners other than CurrentResourceOwner. + * If we were to refactor, it'd be a good idea to fix it so we don't have to + * do a hashtable lookup of the locallock, too. However, currently this + * function isn't used heavily enough to justify refactoring for its + * convenience. */ static void -ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner) +ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock) { - int i; + ResourceOwner owner; LOCALLOCKOWNER *lockOwners; + int i; - /* Scan to see if there are any locks belonging to the owner */ + /* Identify owner for lock (must match LockRelease!) */ + if (sessionLock) + owner = NULL; + else + owner = CurrentResourceOwner; + + /* Scan to see if there are any locks belonging to the target owner */ lockOwners = locallock->lockOwners; for (i = locallock->numLockOwners - 1; i >= 0; i--) { @@ -2116,8 +2127,8 @@ ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner) locallock->nLocks = 1; if (!LockRelease(&locallock->tag.lock, locallock->tag.mode, - owner == NULL)) - elog(WARNING, "ReleaseLockForOwner: failed??"); + sessionLock)) + elog(WARNING, "ReleaseLockIfHeld: failed??"); } break; } @@ -2147,10 +2158,6 @@ LockReassignCurrentOwner(void) int ic = -1; int ip = -1; - /* Ignore items that must be nontransactional */ - if (!LockMethods[LOCALLOCK_LOCKMETHOD(*locallock)]->transactional) - continue; - /* * Scan to see if there are any locks belonging to current owner or * its parent @@ -2729,13 +2736,13 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc, * Do the preparatory work for a PREPARE: make 2PC state file records * for all locks currently held. * - * Non-transactional locks are ignored, as are VXID locks. + * 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 session locks (should be OK since only VACUUM uses those) 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). + * 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). */ void AtPrepare_Locks(void) @@ -2755,12 +2762,10 @@ AtPrepare_Locks(void) { TwoPhaseLockRecord record; LOCALLOCKOWNER *lockOwners = locallock->lockOwners; + bool haveSessionLock; + bool haveXactLock; int i; - /* Ignore nontransactional locks */ - if (!LockMethods[LOCALLOCK_LOCKMETHOD(*locallock)]->transactional) - continue; - /* * Ignore VXID locks. We don't want those to be held by prepared * transactions, since they aren't meaningful after a restart. @@ -2772,14 +2777,38 @@ AtPrepare_Locks(void) if (locallock->nLocks <= 0) continue; - /* Scan to verify there are no session locks */ + /* Scan to see whether we hold it at session or transaction level */ + haveSessionLock = haveXactLock = false; for (i = locallock->numLockOwners - 1; i >= 0; i--) { - /* elog not ereport since this should not happen */ if (lockOwners[i].owner == NULL) - elog(ERROR, "cannot PREPARE when session locks exist"); + haveSessionLock = true; + else + haveXactLock = true; } + /* Ignore it if we have only session lock */ + 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. + */ + if (haveSessionLock) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot PREPARE while holding both session-level and transaction-level locks on the same object"))); + /* * If the local lock was taken via the fast-path, we need to move it * to the primary lock table, or just get a pointer to the existing @@ -2792,7 +2821,7 @@ AtPrepare_Locks(void) } /* - * Arrange not to release any strong lock count held by this lock + * Arrange to not release any strong lock count held by this lock * entry. We must retain the count until the prepared transaction * is committed or rolled back. */ @@ -2852,6 +2881,11 @@ PostPrepare_Locks(TransactionId xid) while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) { + LOCALLOCKOWNER *lockOwners = locallock->lockOwners; + bool haveSessionLock; + bool haveXactLock; + int i; + if (locallock->proclock == NULL || locallock->lock == NULL) { /* @@ -2863,15 +2897,29 @@ PostPrepare_Locks(TransactionId xid) continue; } - /* Ignore nontransactional locks */ - if (!LockMethods[LOCALLOCK_LOCKMETHOD(*locallock)]->transactional) - continue; - /* Ignore VXID locks */ if (locallock->tag.lock.locktag_type == LOCKTAG_VIRTUALTRANSACTION) continue; - /* We already checked there are no session locks */ + /* Scan to see whether we hold it at session or transaction level */ + haveSessionLock = haveXactLock = false; + for (i = locallock->numLockOwners - 1; i >= 0; i--) + { + if (lockOwners[i].owner == NULL) + haveSessionLock = true; + else + haveXactLock = true; + } + + /* Ignore it if we have only session lock */ + if (!haveXactLock) + continue; + + /* This can't happen, because we already checked it */ + if (haveSessionLock) + ereport(PANIC, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot PREPARE while holding both session-level and transaction-level locks on the same object"))); /* Mark the proclock to show we need to release this lockmode */ if (locallock->nLocks > 0) @@ -2912,10 +2960,6 @@ PostPrepare_Locks(TransactionId xid) lock = proclock->tag.myLock; - /* Ignore nontransactional locks */ - if (!LockMethods[LOCK_LOCKMETHOD(*lock)]->transactional) - goto next_item; - /* Ignore VXID locks */ if (lock->tag.locktag_type == LOCKTAG_VIRTUALTRANSACTION) goto next_item; @@ -2927,10 +2971,11 @@ PostPrepare_Locks(TransactionId xid) Assert(lock->nGranted <= lock->nRequested); Assert((proclock->holdMask & ~lock->grantMask) == 0); - /* - * Since there were no session locks, we should be releasing all - * locks - */ + /* Ignore it if nothing to release (must be a session lock) */ + if (proclock->releaseMask == 0) + goto next_item; + + /* Else we should be releasing all locks */ if (proclock->releaseMask != proclock->holdMask) elog(PANIC, "we seem to have dropped a bit somewhere"); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index d1bf264b131..20ed5de75e7 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -697,9 +697,12 @@ LockErrorCleanup(void) * ProcReleaseLocks() -- release locks associated with current transaction * at main transaction commit or abort * - * At main transaction commit, we release all locks except session locks. + * At main transaction commit, we release standard locks except session locks. * At main transaction abort, we release all locks including session locks. * + * Advisory locks are released only if they are transaction-level; + * session-level holds remain, whether this is a commit or not. + * * At subtransaction commit, we don't release any locks (so this func is not * needed at all); we will defer the releasing to the parent transaction. * At subtransaction abort, we release all locks held by the subtransaction; @@ -713,10 +716,9 @@ ProcReleaseLocks(bool isCommit) return; /* If waiting, get off wait queue (should only be needed after error) */ LockErrorCleanup(); - /* Release locks */ + /* Release standard locks, including session-level if aborting */ LockReleaseAll(DEFAULT_LOCKMETHOD, !isCommit); - - /* Release transaction level advisory locks */ + /* Release transaction-level advisory locks */ LockReleaseAll(USER_LOCKMETHOD, false); } diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index faddef579c7..17b894285ba 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -96,15 +96,12 @@ typedef int LOCKMODE; /* * This data structure defines the locking semantics associated with a * "lock method". The semantics specify the meaning of each lock mode - * (by defining which lock modes it conflicts with), and also whether locks - * of this method are transactional (ie, are released at transaction end). + * (by defining which lock modes it conflicts with). * All of this data is constant and is kept in const tables. * * numLockModes -- number of lock modes (READ,WRITE,etc) that * are defined in this lock method. Must be less than MAX_LOCKMODES. * - * transactional -- TRUE if locks are released automatically at xact end. - * * conflictTab -- this is an array of bitmasks showing lock * mode conflicts. conflictTab[i] is a mask with the j-th bit * turned on if lock modes i and j conflict. Lock modes are @@ -112,12 +109,13 @@ typedef int LOCKMODE; * * lockModeNames -- ID strings for debug printouts. * - * trace_flag -- pointer to GUC trace flag for this lock method. + * trace_flag -- pointer to GUC trace flag for this lock method. (The + * GUC variable is not constant, but we use "const" here to denote that + * it can't be changed through this reference.) */ typedef struct LockMethodData { int numLockModes; - bool transactional; const LOCKMASK *conflictTab; const char *const * lockModeNames; const bool *trace_flag; @@ -492,8 +490,8 @@ extern LockAcquireResult LockAcquireExtended(const LOCKTAG *locktag, extern void AbortStrongLockAcquire(void); extern bool LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock); -extern void LockReleaseSession(LOCKMETHODID lockmethodid); extern void LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks); +extern void LockReleaseSession(LOCKMETHODID lockmethodid); extern void LockReleaseCurrentOwner(void); extern void LockReassignCurrentOwner(void); extern VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag, |