diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index cfe3954637d723822b95ec4e64e457e193bb0854..98fc02529e31a715fa1dcad87638b96565922317 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -345,6 +345,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, @@ -1098,8 +1099,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; @@ -1112,6 +1121,7 @@ RemoveLocalLock(LOCALLOCK *locallock) locallock->holdsStrongLockCount = FALSE; SpinLockRelease(&FastPathStrongRelationLocks->mutex); } + if (!hash_search(LockMethodLocalHash, (void *) &(locallock->tag), HASH_REMOVE, NULL)) @@ -1355,6 +1365,8 @@ GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner) lockOwners[i].owner = owner; lockOwners[i].nLocks = 1; locallock->numLockOwners++; + if (owner != NULL) + ResourceOwnerRememberLock(owner, locallock); } /* @@ -1670,6 +1682,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) @@ -1862,14 +1876,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 && @@ -1882,6 +1895,8 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) /* We aren't deleting this locallock, so done */ continue; } + else + locallock->numLockOwners = 0; } /* @@ -2067,18 +2082,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); } } @@ -2124,6 +2152,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]; } @@ -2146,57 +2176,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; + int i; - /* - * 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 7c771eb491825284843605529dcebc16f5ab1846..3ded469d1e866f4206029c785ae04dd71f1b6563 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 d629ac2ad2e7cfb11988284d7df78644de46ab3a..d56f0fa4b74f82682529e4f2586cf4a079002def 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -492,8 +492,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 VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode); extern void AtPrepare_Locks(void); diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h index 11034e454e4de9cb9d27156f362c4a4fc6883eca..e1c992e75e23b367bbed2e3ca5b1681ba1961846 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,