diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index cbc5ee1be35d59701867eb0281655870d271459f..65726196d00082e20e29762dd5b68f03b0bfbc68 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -344,6 +344,7 @@ static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode); static void FinishStrongLockAcquire(void); static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner); static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock); +static void LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent); static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode, PROCLOCK *proclock, LockMethod lockMethodTable); static void CleanUpLock(LOCK *lock, PROCLOCK *proclock, @@ -1205,8 +1206,16 @@ SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc, static void RemoveLocalLock(LOCALLOCK *locallock) { + int i; + + for (i = locallock->numLockOwners - 1; i >= 0; i--) + { + if (locallock->lockOwners[i].owner != NULL) + ResourceOwnerForgetLock(locallock->lockOwners[i].owner, locallock); + } pfree(locallock->lockOwners); locallock->lockOwners = NULL; + if (locallock->holdsStrongLockCount) { uint32 fasthashcode; @@ -1219,6 +1228,7 @@ RemoveLocalLock(LOCALLOCK *locallock) locallock->holdsStrongLockCount = FALSE; SpinLockRelease(&FastPathStrongRelationLocks->mutex); } + if (!hash_search(LockMethodLocalHash, (void *) &(locallock->tag), HASH_REMOVE, NULL)) @@ -1462,6 +1472,8 @@ GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner) lockOwners[i].owner = owner; lockOwners[i].nLocks = 1; locallock->numLockOwners++; + if (owner != NULL) + ResourceOwnerRememberLock(owner, locallock); } /* @@ -1778,6 +1790,8 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) Assert(lockOwners[i].nLocks > 0); if (--lockOwners[i].nLocks == 0) { + if (owner != NULL) + ResourceOwnerForgetLock(owner, locallock); /* compact out unused slot */ locallock->numLockOwners--; if (i < locallock->numLockOwners) @@ -1974,14 +1988,13 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) { LOCALLOCKOWNER *lockOwners = locallock->lockOwners; - /* If it's above array position 0, move it down to 0 */ - for (i = locallock->numLockOwners - 1; i > 0; i--) + /* If session lock is above array position 0, move it down to 0 */ + for (i = 0; i < locallock->numLockOwners; i++) { if (lockOwners[i].owner == NULL) - { lockOwners[0] = lockOwners[i]; - break; - } + else + ResourceOwnerForgetLock(lockOwners[i].owner, locallock); } if (locallock->numLockOwners > 0 && @@ -1994,6 +2007,8 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) /* We aren't deleting this locallock, so done */ continue; } + else + locallock->numLockOwners = 0; } /* @@ -2200,18 +2215,31 @@ LockReleaseSession(LOCKMETHODID lockmethodid) /* * LockReleaseCurrentOwner * Release all locks belonging to CurrentResourceOwner + * + * If the caller knows what those locks are, it can pass them as an array. + * That speeds up the call significantly, when a lot of locks are held. + * Otherwise, pass NULL for locallocks, and we'll traverse through our hash + * table to find them. */ void -LockReleaseCurrentOwner(void) +LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks) { - HASH_SEQ_STATUS status; - LOCALLOCK *locallock; + if (locallocks == NULL) + { + HASH_SEQ_STATUS status; + LOCALLOCK *locallock; - hash_seq_init(&status, LockMethodLocalHash); + hash_seq_init(&status, LockMethodLocalHash); - while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + ReleaseLockIfHeld(locallock, false); + } + else { - ReleaseLockIfHeld(locallock, false); + int i; + + for (i = nlocks - 1; i >= 0; i--) + ReleaseLockIfHeld(locallocks[i], false); } } @@ -2257,6 +2285,8 @@ ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock) locallock->nLocks -= lockOwners[i].nLocks; /* compact out unused slot */ locallock->numLockOwners--; + if (owner != NULL) + ResourceOwnerForgetLock(owner, locallock); if (i < locallock->numLockOwners) lockOwners[i] = lockOwners[locallock->numLockOwners]; } @@ -2279,57 +2309,83 @@ ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock) /* * LockReassignCurrentOwner * Reassign all locks belonging to CurrentResourceOwner to belong - * to its parent resource owner + * to its parent resource owner. + * + * If the caller knows what those locks are, it can pass them as an array. + * That speeds up the call significantly, when a lot of locks are held + * (e.g pg_dump with a large schema). Otherwise, pass NULL for locallocks, + * and we'll traverse through our hash table to find them. */ void -LockReassignCurrentOwner(void) +LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks) { ResourceOwner parent = ResourceOwnerGetParent(CurrentResourceOwner); - HASH_SEQ_STATUS status; - LOCALLOCK *locallock; - LOCALLOCKOWNER *lockOwners; Assert(parent != NULL); - hash_seq_init(&status, LockMethodLocalHash); + if (locallocks == NULL) + { + HASH_SEQ_STATUS status; + LOCALLOCK *locallock; - while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + hash_seq_init(&status, LockMethodLocalHash); + + while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + LockReassignOwner(locallock, parent); + } + else { int i; - int ic = -1; - int ip = -1; - /* - * Scan to see if there are any locks belonging to current owner or - * its parent - */ - lockOwners = locallock->lockOwners; - for (i = locallock->numLockOwners - 1; i >= 0; i--) - { - if (lockOwners[i].owner == CurrentResourceOwner) - ic = i; - else if (lockOwners[i].owner == parent) - ip = i; - } + for (i = nlocks - 1; i >= 0; i--) + LockReassignOwner(locallocks[i], parent); + } +} - if (ic < 0) - continue; /* no current locks */ +/* + * Subroutine of LockReassignCurrentOwner. Reassigns a given lock belonging to + * CurrentResourceOwner to its parent. + */ +static void +LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent) +{ + LOCALLOCKOWNER *lockOwners; + int i; + int ic = -1; + int ip = -1; - if (ip < 0) - { - /* Parent has no slot, so just give it child's slot */ - lockOwners[ic].owner = parent; - } - else - { - /* Merge child's count with parent's */ - lockOwners[ip].nLocks += lockOwners[ic].nLocks; - /* compact out unused slot */ - locallock->numLockOwners--; - if (ic < locallock->numLockOwners) - lockOwners[ic] = lockOwners[locallock->numLockOwners]; - } + /* + * Scan to see if there are any locks belonging to current owner or its + * parent + */ + lockOwners = locallock->lockOwners; + for (i = locallock->numLockOwners - 1; i >= 0; i--) + { + if (lockOwners[i].owner == CurrentResourceOwner) + ic = i; + else if (lockOwners[i].owner == parent) + ip = i; + } + + if (ic < 0) + return; /* no current locks */ + + if (ip < 0) + { + /* Parent has no slot, so just give it the child's slot */ + lockOwners[ic].owner = parent; + ResourceOwnerRememberLock(parent, locallock); + } + else + { + /* Merge child's count with parent's */ + lockOwners[ip].nLocks += lockOwners[ic].nLocks; + /* compact out unused slot */ + locallock->numLockOwners--; + if (ic < locallock->numLockOwners) + lockOwners[ic] = lockOwners[locallock->numLockOwners]; } + ResourceOwnerForgetLock(CurrentResourceOwner, locallock); } /* diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index 50690bc38ea9126541814e518700cf2a93717341..abbef1bac3b75edcb2f45632a9856d6bc3419142 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -27,6 +27,23 @@ #include "utils/rel.h" #include "utils/snapmgr.h" +/* + * To speed up bulk releasing or reassigning locks from a resource owner to + * its parent, each resource owner has a small cache of locks it owns. The + * lock manager has the same information in its local lock hash table, and + * we fall back on that if cache overflows, but traversing the hash table + * is slower when there are a lot of locks belonging to other resource owners. + * + * MAX_RESOWNER_LOCKS is the size of the per-resource owner cache. It's + * chosen based on some testing with pg_dump with a large schema. When the + * tests were done (on 9.2), resource owners in a pg_dump run contained up + * to 9 locks, regardless of the schema size, except for the top resource + * owner which contained much more (overflowing the cache). 15 seems like a + * nice round number that's somewhat higher than what pg_dump needs. Note that + * making this number larger is not free - the bigger the cache, the slower + * it is to release locks (in retail), when a resource owner holds many locks. + */ +#define MAX_RESOWNER_LOCKS 15 /* * ResourceOwner objects look like this @@ -43,6 +60,10 @@ typedef struct ResourceOwnerData Buffer *buffers; /* dynamically allocated array */ int maxbuffers; /* currently allocated array size */ + /* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */ + int nlocks; /* number of owned locks */ + LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */ + /* We have built-in support for remembering catcache references */ int ncatrefs; /* number of owned catcache pins */ HeapTuple *catrefs; /* dynamically allocated array */ @@ -272,11 +293,30 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, * subtransaction, we do NOT release its locks yet, but transfer * them to the parent. */ + LOCALLOCK **locks; + int nlocks; + Assert(owner->parent != NULL); + + /* + * Pass the list of locks owned by this resource owner to the lock + * manager, unless it has overflowed. + */ + if (owner->nlocks > MAX_RESOWNER_LOCKS) + { + locks = NULL; + nlocks = 0; + } + else + { + locks = owner->locks; + nlocks = owner->nlocks; + } + if (isCommit) - LockReassignCurrentOwner(); + LockReassignCurrentOwner(locks, nlocks); else - LockReleaseCurrentOwner(); + LockReleaseCurrentOwner(locks, nlocks); } } else if (phase == RESOURCE_RELEASE_AFTER_LOCKS) @@ -357,6 +397,7 @@ ResourceOwnerDelete(ResourceOwner owner) /* And it better not own any resources, either */ Assert(owner->nbuffers == 0); + Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1); Assert(owner->ncatrefs == 0); Assert(owner->ncatlistrefs == 0); Assert(owner->nrelrefs == 0); @@ -588,6 +629,56 @@ ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer) } } +/* + * Remember that a Local Lock is owned by a ResourceOwner + * + * This is different from the other Remember functions in that the list of + * locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries, + * and when it overflows, we stop tracking locks. The point of only remembering + * only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held, + * ResourceOwnerForgetLock doesn't need to scan through a large array to find + * the entry. + */ +void +ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock) +{ + if (owner->nlocks > MAX_RESOWNER_LOCKS) + return; /* we have already overflowed */ + + if (owner->nlocks < MAX_RESOWNER_LOCKS) + owner->locks[owner->nlocks] = locallock; + else + { + /* overflowed */ + } + owner->nlocks++; +} + +/* + * Forget that a Local Lock is owned by a ResourceOwner + */ +void +ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock) +{ + int i; + + if (owner->nlocks > MAX_RESOWNER_LOCKS) + return; /* we have overflowed */ + + Assert(owner->nlocks > 0); + for (i = owner->nlocks - 1; i >= 0; i--) + { + if (locallock == owner->locks[i]) + { + owner->locks[i] = owner->locks[owner->nlocks - 1]; + owner->nlocks--; + return; + } + } + elog(ERROR, "lock reference %p is not owned by resource owner %s", + locallock, owner->name); +} + /* * Make sure there is room for at least one more entry in a ResourceOwner's * catcache reference array. diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index 78c3074ad27e1d5067f0c159cec1e8d229329bfa..d1ad32ad25859da8e216978cec1ec92dc2553401 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -506,8 +506,8 @@ extern bool LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock); extern void LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks); extern void LockReleaseSession(LOCKMETHODID lockmethodid); -extern void LockReleaseCurrentOwner(void); -extern void LockReassignCurrentOwner(void); +extern void LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks); +extern void LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks); extern bool LockHasWaiters(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock); extern VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag, diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h index 47eb0ac05e50af7231e3f9c1a97cfd96c09fb2c2..336d250fbbea1b544572136c56825c61204bd12f 100644 --- a/src/include/utils/resowner.h +++ b/src/include/utils/resowner.h @@ -20,6 +20,7 @@ #define RESOWNER_H #include "storage/fd.h" +#include "storage/lock.h" #include "utils/catcache.h" #include "utils/plancache.h" #include "utils/snapshot.h" @@ -89,6 +90,10 @@ extern void ResourceOwnerEnlargeBuffers(ResourceOwner owner); extern void ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer); extern void ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer); +/* support for local lock management */ +extern void ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock); +extern void ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock); + /* support for catcache refcount management */ extern void ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner); extern void ResourceOwnerRememberCatCacheRef(ResourceOwner owner,