Add a small cache of locks owned by a resource owner in ResourceOwner.
Back-patch 9.3-era commit eeb6f37d89fc60c6449ca12ef9e91491069369cb, to improve the older branches' ability to cope with pg_dump dumping a large number of tables. I back-patched into 9.2 and 9.1, but not 9.0 as it would have required a significant amount of refactoring, thus negating the argument that this is by-now-well-tested code. Jeff Janes, reviewed by Amit Kapila and Heikki Linnakangas.
This commit is contained in:
parent
31934dd3dd
commit
9b1b9446f5
@ -257,6 +257,7 @@ static void RemoveLocalLock(LOCALLOCK *locallock);
|
||||
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
|
||||
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,
|
||||
@ -996,6 +997,13 @@ LockAcquireExtended(const LOCKTAG *locktag,
|
||||
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 (!hash_search(LockMethodLocalHash,
|
||||
@ -1241,6 +1249,8 @@ GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner)
|
||||
lockOwners[i].owner = owner;
|
||||
lockOwners[i].nLocks = 1;
|
||||
locallock->numLockOwners++;
|
||||
if (owner != NULL)
|
||||
ResourceOwnerRememberLock(owner, locallock);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1498,6 +1508,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)
|
||||
@ -1636,14 +1648,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 &&
|
||||
@ -1656,6 +1667,8 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
|
||||
/* We aren't deleting this locallock, so done */
|
||||
continue;
|
||||
}
|
||||
else
|
||||
locallock->numLockOwners = 0;
|
||||
}
|
||||
|
||||
/* Mark the proclock to show we need to release this lockmode */
|
||||
@ -1785,18 +1798,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;
|
||||
|
||||
hash_seq_init(&status, LockMethodLocalHash);
|
||||
|
||||
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
|
||||
if (locallocks == NULL)
|
||||
{
|
||||
ReleaseLockIfHeld(locallock, false);
|
||||
HASH_SEQ_STATUS status;
|
||||
LOCALLOCK *locallock;
|
||||
|
||||
hash_seq_init(&status, LockMethodLocalHash);
|
||||
|
||||
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
|
||||
ReleaseLockIfHeld(locallock, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = nlocks - 1; i >= 0; i--)
|
||||
ReleaseLockIfHeld(locallocks[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1842,6 +1868,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];
|
||||
}
|
||||
@ -1864,59 +1892,85 @@ 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;
|
||||
}
|
||||
|
||||
if (ic < 0)
|
||||
continue; /* no current locks */
|
||||
|
||||
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];
|
||||
}
|
||||
for (i = nlocks - 1; i >= 0; i--)
|
||||
LockReassignOwner(locallocks[i], parent);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetLockConflicts
|
||||
|
@ -29,6 +29,23 @@
|
||||
#include "utils/resowner.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
|
||||
@ -45,6 +62,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 */
|
||||
@ -274,11 +295,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);
|
||||
if (isCommit)
|
||||
LockReassignCurrentOwner();
|
||||
|
||||
/*
|
||||
* 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
|
||||
LockReleaseCurrentOwner();
|
||||
{
|
||||
locks = owner->locks;
|
||||
nlocks = owner->nlocks;
|
||||
}
|
||||
|
||||
if (isCommit)
|
||||
LockReassignCurrentOwner(locks, nlocks);
|
||||
else
|
||||
LockReleaseCurrentOwner(locks, nlocks);
|
||||
}
|
||||
}
|
||||
else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
|
||||
@ -359,6 +399,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);
|
||||
@ -590,6 +631,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.
|
||||
|
@ -488,8 +488,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,
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "storage/buf.h"
|
||||
#include "storage/fd.h"
|
||||
#include "storage/lock.h"
|
||||
#include "utils/catcache.h"
|
||||
#include "utils/plancache.h"
|
||||
#include "utils/snapshot.h"
|
||||
@ -90,6 +91,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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user