mirror of
https://github.com/postgres/postgres.git
synced 2025-11-06 07:49:08 +03:00
Re-implement deadlock detection and resolution, per design notes posted
to pghackers on 18-Jan-01.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/storage/lmgr/lock.c,v 1.80 2001/01/24 19:43:08 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/storage/lmgr/lock.c,v 1.81 2001/01/25 03:31:16 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* Outside modules can create a lock table and acquire/release
|
||||
@@ -24,16 +24,16 @@
|
||||
*
|
||||
* LockAcquire(), LockRelease(), LockMethodTableInit(),
|
||||
* LockMethodTableRename(), LockReleaseAll,
|
||||
* LockResolveConflicts(), GrantLock()
|
||||
* LockCheckConflicts(), GrantLock()
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/xact.h"
|
||||
#include "miscadmin.h"
|
||||
#include "storage/proc.h"
|
||||
@@ -44,7 +44,6 @@ static int WaitOnLock(LOCKMETHOD lockmethod, LOCKMODE lockmode,
|
||||
LOCK *lock, HOLDER *holder);
|
||||
static void LockCountMyLocks(SHMEM_OFFSET lockOffset, PROC *proc,
|
||||
int *myHolding);
|
||||
static int LockGetMyHeldLocks(SHMEM_OFFSET lockOffset, PROC *proc);
|
||||
|
||||
static char *lock_types[] =
|
||||
{
|
||||
@@ -211,6 +210,18 @@ LockingDisabled(void)
|
||||
return LockingIsDisabled;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch the lock method table associated with a given lock
|
||||
*/
|
||||
LOCKMETHODTABLE *
|
||||
GetLocksMethodTable(LOCK *lock)
|
||||
{
|
||||
LOCKMETHOD lockmethod = LOCK_LOCKMETHOD(*lock);
|
||||
|
||||
Assert(lockmethod > 0 && lockmethod < NumLockMethods);
|
||||
return LockMethodTable[lockmethod];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* LockMethodInit -- initialize the lock table's lock type
|
||||
@@ -559,7 +570,7 @@ LockAcquire(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
if (!holder)
|
||||
{
|
||||
SpinRelease(masterLock);
|
||||
elog(NOTICE, "LockAcquire: holder table corrupted");
|
||||
elog(FATAL, "LockAcquire: holder table corrupted");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -623,11 +634,11 @@ LockAcquire(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
Assert((lock->nRequested > 0) && (lock->requested[lockmode] > 0));
|
||||
|
||||
/* --------------------
|
||||
* If I'm the only one holding any lock on this object, then there
|
||||
* cannot be a conflict. The same is true if I already hold this lock.
|
||||
* If I already hold one or more locks of the requested type,
|
||||
* just grant myself another one without blocking.
|
||||
* --------------------
|
||||
*/
|
||||
if (holder->nHolding == lock->nGranted || holder->holding[lockmode] != 0)
|
||||
if (holder->holding[lockmode] > 0)
|
||||
{
|
||||
GrantLock(lock, holder, lockmode);
|
||||
HOLDER_PRINT("LockAcquire: owning", holder);
|
||||
@@ -637,11 +648,11 @@ LockAcquire(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
|
||||
/* --------------------
|
||||
* If this process (under any XID) is a holder of the lock,
|
||||
* then there is no conflict, either.
|
||||
* also grant myself another one without blocking.
|
||||
* --------------------
|
||||
*/
|
||||
LockCountMyLocks(holder->tag.lock, MyProc, myHolding);
|
||||
if (myHolding[lockmode] != 0)
|
||||
if (myHolding[lockmode] > 0)
|
||||
{
|
||||
GrantLock(lock, holder, lockmode);
|
||||
HOLDER_PRINT("LockAcquire: my other XID owning", holder);
|
||||
@@ -649,42 +660,27 @@ LockAcquire(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* If lock requested conflicts with locks requested by waiters...
|
||||
/* --------------------
|
||||
* If lock requested conflicts with locks requested by waiters,
|
||||
* must join wait queue. Otherwise, check for conflict with
|
||||
* already-held locks. (That's last because most complex check.)
|
||||
* --------------------
|
||||
*/
|
||||
if (lockMethodTable->ctl->conflictTab[lockmode] & lock->waitMask)
|
||||
{
|
||||
/*
|
||||
* If my process doesn't hold any locks that conflict with waiters
|
||||
* then force to sleep, so that prior waiters get first chance.
|
||||
*/
|
||||
for (i = 1; i <= lockMethodTable->ctl->numLockModes; i++)
|
||||
{
|
||||
if (myHolding[i] > 0 &&
|
||||
lockMethodTable->ctl->conflictTab[i] & lock->waitMask)
|
||||
break; /* yes, there is a conflict */
|
||||
}
|
||||
|
||||
if (i > lockMethodTable->ctl->numLockModes)
|
||||
{
|
||||
HOLDER_PRINT("LockAcquire: another proc already waiting",
|
||||
holder);
|
||||
status = STATUS_FOUND;
|
||||
}
|
||||
else
|
||||
status = LockResolveConflicts(lockmethod, lockmode,
|
||||
lock, holder,
|
||||
MyProc, myHolding);
|
||||
}
|
||||
status = STATUS_FOUND;
|
||||
else
|
||||
status = LockResolveConflicts(lockmethod, lockmode,
|
||||
lock, holder,
|
||||
MyProc, myHolding);
|
||||
status = LockCheckConflicts(lockMethodTable, lockmode,
|
||||
lock, holder,
|
||||
MyProc, myHolding);
|
||||
|
||||
if (status == STATUS_OK)
|
||||
GrantLock(lock, holder, lockmode);
|
||||
else if (status == STATUS_FOUND)
|
||||
{
|
||||
/* No conflict with held or previously requested locks */
|
||||
GrantLock(lock, holder, lockmode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(status == STATUS_FOUND);
|
||||
#ifdef USER_LOCKS
|
||||
|
||||
/*
|
||||
@@ -765,49 +761,50 @@ LockAcquire(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
}
|
||||
|
||||
/* ----------------------------
|
||||
* LockResolveConflicts -- test for lock conflicts
|
||||
* LockCheckConflicts -- test whether requested lock conflicts
|
||||
* with those already granted
|
||||
*
|
||||
* Returns STATUS_FOUND if conflict, STATUS_OK if no conflict.
|
||||
*
|
||||
* NOTES:
|
||||
* Here's what makes this complicated: one transaction's
|
||||
* locks don't conflict with one another. When many processes
|
||||
* hold locks, each has to subtract off the other's locks when
|
||||
* determining whether or not any new lock acquired conflicts with
|
||||
* the old ones.
|
||||
* Here's what makes this complicated: one process's locks don't
|
||||
* conflict with one another, even if they are held under different
|
||||
* transaction IDs (eg, session and xact locks do not conflict).
|
||||
* So, we must subtract off our own locks when determining whether the
|
||||
* requested new lock conflicts with those already held.
|
||||
*
|
||||
* The caller can optionally pass the process's total holding counts, if
|
||||
* known. If NULL is passed then these values will be computed internally.
|
||||
* ----------------------------
|
||||
*/
|
||||
int
|
||||
LockResolveConflicts(LOCKMETHOD lockmethod,
|
||||
LOCKMODE lockmode,
|
||||
LOCK *lock,
|
||||
HOLDER *holder,
|
||||
PROC *proc,
|
||||
int *myHolding) /* myHolding[] array or NULL */
|
||||
LockCheckConflicts(LOCKMETHODTABLE *lockMethodTable,
|
||||
LOCKMODE lockmode,
|
||||
LOCK *lock,
|
||||
HOLDER *holder,
|
||||
PROC *proc,
|
||||
int *myHolding) /* myHolding[] array or NULL */
|
||||
{
|
||||
LOCKMETHODCTL *lockctl = LockMethodTable[lockmethod]->ctl;
|
||||
LOCKMETHODCTL *lockctl = lockMethodTable->ctl;
|
||||
int numLockModes = lockctl->numLockModes;
|
||||
int bitmask;
|
||||
int i,
|
||||
tmpMask;
|
||||
int localHolding[MAX_LOCKMODES];
|
||||
|
||||
Assert((holder->nHolding >= 0) && (holder->holding[lockmode] >= 0));
|
||||
|
||||
/* ----------------------------
|
||||
* first check for global conflicts: If no locks conflict
|
||||
* with mine, then I get the lock.
|
||||
* with my request, then I get the lock.
|
||||
*
|
||||
* Checking for conflict: lock->grantMask represents the types of
|
||||
* currently held locks. conflictTable[lockmode] has a bit
|
||||
* set for each type of lock that conflicts with mine. Bitwise
|
||||
* set for each type of lock that conflicts with request. Bitwise
|
||||
* compare tells if there is a conflict.
|
||||
* ----------------------------
|
||||
*/
|
||||
if (!(lockctl->conflictTab[lockmode] & lock->grantMask))
|
||||
{
|
||||
HOLDER_PRINT("LockResolveConflicts: no conflict", holder);
|
||||
HOLDER_PRINT("LockCheckConflicts: no conflict", holder);
|
||||
return STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -844,11 +841,11 @@ LockResolveConflicts(LOCKMETHOD lockmethod,
|
||||
if (!(lockctl->conflictTab[lockmode] & bitmask))
|
||||
{
|
||||
/* no conflict. OK to get the lock */
|
||||
HOLDER_PRINT("LockResolveConflicts: resolved", holder);
|
||||
HOLDER_PRINT("LockCheckConflicts: resolved", holder);
|
||||
return STATUS_OK;
|
||||
}
|
||||
|
||||
HOLDER_PRINT("LockResolveConflicts: conflicting", holder);
|
||||
HOLDER_PRINT("LockCheckConflicts: conflicting", holder);
|
||||
return STATUS_FOUND;
|
||||
}
|
||||
|
||||
@@ -889,33 +886,12 @@ LockCountMyLocks(SHMEM_OFFSET lockOffset, PROC *proc, int *myHolding)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* LockGetMyHeldLocks -- compute bitmask of lock types held by a process
|
||||
* for a given lockable object.
|
||||
*/
|
||||
static int
|
||||
LockGetMyHeldLocks(SHMEM_OFFSET lockOffset, PROC *proc)
|
||||
{
|
||||
int myHolding[MAX_LOCKMODES];
|
||||
int heldLocks = 0;
|
||||
int i,
|
||||
tmpMask;
|
||||
|
||||
LockCountMyLocks(lockOffset, proc, myHolding);
|
||||
|
||||
for (i = 1, tmpMask = 2;
|
||||
i < MAX_LOCKMODES;
|
||||
i++, tmpMask <<= 1)
|
||||
{
|
||||
if (myHolding[i] > 0)
|
||||
heldLocks |= tmpMask;
|
||||
}
|
||||
return heldLocks;
|
||||
}
|
||||
|
||||
/*
|
||||
* GrantLock -- update the lock and holder data structures to show
|
||||
* the lock request has been granted.
|
||||
*
|
||||
* NOTE: if proc was blocked, it also needs to be removed from the wait list
|
||||
* and have its waitLock/waitHolder fields cleared. That's not done here.
|
||||
*/
|
||||
void
|
||||
GrantLock(LOCK *lock, HOLDER *holder, LOCKMODE lockmode)
|
||||
@@ -936,6 +912,9 @@ GrantLock(LOCK *lock, HOLDER *holder, LOCKMODE lockmode)
|
||||
/*
|
||||
* WaitOnLock -- wait to acquire a lock
|
||||
*
|
||||
* Caller must have set MyProc->heldLocks to reflect locks already held
|
||||
* on the lockable object by this process (under all XIDs).
|
||||
*
|
||||
* The locktable spinlock must be held at entry.
|
||||
*/
|
||||
static int
|
||||
@@ -956,7 +935,7 @@ WaitOnLock(LOCKMETHOD lockmethod, LOCKMODE lockmode,
|
||||
strcat(new_status, " waiting");
|
||||
set_ps_display(new_status);
|
||||
|
||||
/*
|
||||
/* -------------------
|
||||
* NOTE: Think not to put any lock state cleanup after the call to
|
||||
* ProcSleep, in either the normal or failure path. The lock state
|
||||
* must be fully set by the lock grantor, or by HandleDeadLock if we
|
||||
@@ -965,12 +944,13 @@ WaitOnLock(LOCKMETHOD lockmethod, LOCKMODE lockmode,
|
||||
* after someone else grants us the lock, but before we've noticed it.
|
||||
* Hence, after granting, the locktable state must fully reflect the
|
||||
* fact that we own the lock; we can't do additional work on return.
|
||||
* -------------------
|
||||
*/
|
||||
|
||||
if (ProcSleep(lockMethodTable->ctl,
|
||||
if (ProcSleep(lockMethodTable,
|
||||
lockmode,
|
||||
lock,
|
||||
holder) != NO_ERROR)
|
||||
holder) != STATUS_OK)
|
||||
{
|
||||
/* -------------------
|
||||
* We failed as a result of a deadlock, see HandleDeadLock().
|
||||
@@ -992,14 +972,60 @@ WaitOnLock(LOCKMETHOD lockmethod, LOCKMODE lockmode,
|
||||
return STATUS_OK;
|
||||
}
|
||||
|
||||
/*--------------------
|
||||
* Remove a proc from the wait-queue it is on
|
||||
* (caller must know it is on one).
|
||||
*
|
||||
* Locktable lock must be held by caller.
|
||||
*
|
||||
* NB: this does not remove the process' holder object, nor the lock object,
|
||||
* even though their counts might now have gone to zero. That will happen
|
||||
* during a subsequent LockReleaseAll call, which we expect will happen
|
||||
* during transaction cleanup. (Removal of a proc from its wait queue by
|
||||
* this routine can only happen if we are aborting the transaction.)
|
||||
*--------------------
|
||||
*/
|
||||
void
|
||||
RemoveFromWaitQueue(PROC *proc)
|
||||
{
|
||||
LOCK *waitLock = proc->waitLock;
|
||||
LOCKMODE lockmode = proc->waitLockMode;
|
||||
|
||||
/* Make sure proc is waiting */
|
||||
Assert(proc->links.next != INVALID_OFFSET);
|
||||
Assert(waitLock);
|
||||
Assert(waitLock->waitProcs.size > 0);
|
||||
|
||||
/* Remove proc from lock's wait queue */
|
||||
SHMQueueDelete(&(proc->links));
|
||||
waitLock->waitProcs.size--;
|
||||
|
||||
/* Undo increments of request counts by waiting process */
|
||||
Assert(waitLock->nRequested > 0);
|
||||
Assert(waitLock->nRequested > proc->waitLock->nGranted);
|
||||
waitLock->nRequested--;
|
||||
Assert(waitLock->requested[lockmode] > 0);
|
||||
waitLock->requested[lockmode]--;
|
||||
/* don't forget to clear waitMask bit if appropriate */
|
||||
if (waitLock->granted[lockmode] == waitLock->requested[lockmode])
|
||||
waitLock->waitMask &= BITS_OFF[lockmode];
|
||||
|
||||
/* Clean up the proc's own state */
|
||||
proc->waitLock = NULL;
|
||||
proc->waitHolder = NULL;
|
||||
|
||||
/* See if any other waiters for the lock can be woken up now */
|
||||
ProcLockWakeup(GetLocksMethodTable(waitLock), waitLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* LockRelease -- look up 'locktag' in lock table 'lockmethod' and
|
||||
* release it.
|
||||
* release one 'lockmode' lock on it.
|
||||
*
|
||||
* Side Effects: if the lock no longer conflicts with the highest
|
||||
* priority waiting process, that process is granted the lock
|
||||
* and awoken. (We have to grant the lock here to avoid a
|
||||
* race between the waking process and any new process to
|
||||
* Side Effects: find any waiting processes that are now wakable,
|
||||
* grant them their requested locks and awaken them.
|
||||
* (We have to grant the lock here to avoid a race between
|
||||
* the waking process and any new process to
|
||||
* come along and request the lock.)
|
||||
*/
|
||||
bool
|
||||
@@ -1013,7 +1039,7 @@ LockRelease(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
HOLDER *holder;
|
||||
HOLDERTAG holdertag;
|
||||
HTAB *holderTable;
|
||||
bool wakeupNeeded = true;
|
||||
bool wakeupNeeded = false;
|
||||
|
||||
#ifdef LOCK_DEBUG
|
||||
if (lockmethod == USER_LOCKMETHOD && Trace_userlocks)
|
||||
@@ -1086,7 +1112,6 @@ LockRelease(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
return FALSE;
|
||||
}
|
||||
HOLDER_PRINT("LockRelease: found", holder);
|
||||
Assert(holder->tag.lock == MAKE_OFFSET(lock));
|
||||
|
||||
/*
|
||||
* Check that we are actually holding a lock of the type we want to
|
||||
@@ -1094,11 +1119,11 @@ LockRelease(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
*/
|
||||
if (!(holder->holding[lockmode] > 0))
|
||||
{
|
||||
SpinRelease(masterLock);
|
||||
HOLDER_PRINT("LockRelease: WRONGTYPE", holder);
|
||||
Assert(holder->holding[lockmode] >= 0);
|
||||
SpinRelease(masterLock);
|
||||
elog(NOTICE, "LockRelease: you don't own a lock of type %s",
|
||||
lock_types[lockmode]);
|
||||
Assert(holder->holding[lockmode] >= 0);
|
||||
return FALSE;
|
||||
}
|
||||
Assert(holder->nHolding > 0);
|
||||
@@ -1120,34 +1145,24 @@ LockRelease(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
lock->grantMask &= BITS_OFF[lockmode];
|
||||
}
|
||||
|
||||
#ifdef NOT_USED
|
||||
/* --------------------------
|
||||
* If there are still active locks of the type I just released, no one
|
||||
* should be woken up. Whoever is asleep will still conflict
|
||||
* with the remaining locks.
|
||||
* --------------------------
|
||||
*/
|
||||
if (lock->granted[lockmode])
|
||||
wakeupNeeded = false;
|
||||
else
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Above is not valid any more (due to MVCC lock modes). Actually
|
||||
* we should compare granted[lockmode] with number of
|
||||
* waiters holding lock of this type and try to wakeup only if
|
||||
* these numbers are equal (and lock released conflicts with locks
|
||||
* requested by waiters). For the moment we only check the last
|
||||
* condition.
|
||||
*/
|
||||
if (lockMethodTable->ctl->conflictTab[lockmode] & lock->waitMask)
|
||||
wakeupNeeded = true;
|
||||
|
||||
LOCK_PRINT("LockRelease: updated", lock, lockmode);
|
||||
Assert((lock->nRequested >= 0) && (lock->requested[lockmode] >= 0));
|
||||
Assert((lock->nGranted >= 0) && (lock->granted[lockmode] >= 0));
|
||||
Assert(lock->nGranted <= lock->nRequested);
|
||||
|
||||
/* --------------------------
|
||||
* We need only run ProcLockWakeup if the released lock conflicts with
|
||||
* at least one of the lock types requested by waiter(s). Otherwise
|
||||
* whatever conflict made them wait must still exist. NOTE: before MVCC,
|
||||
* we could skip wakeup if lock->granted[lockmode] was still positive.
|
||||
* But that's not true anymore, because the remaining granted locks might
|
||||
* belong to some waiter, who could now be awakened because he doesn't
|
||||
* conflict with his own locks.
|
||||
* --------------------------
|
||||
*/
|
||||
if (lockMethodTable->ctl->conflictTab[lockmode] & lock->waitMask)
|
||||
wakeupNeeded = true;
|
||||
|
||||
if (lock->nRequested == 0)
|
||||
{
|
||||
/* ------------------
|
||||
@@ -1161,8 +1176,13 @@ LockRelease(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
(Pointer) &(lock->tag),
|
||||
HASH_REMOVE,
|
||||
&found);
|
||||
Assert(lock && found);
|
||||
wakeupNeeded = false;
|
||||
if (!lock || !found)
|
||||
{
|
||||
SpinRelease(masterLock);
|
||||
elog(NOTICE, "LockRelease: remove lock, table corrupted");
|
||||
return FALSE;
|
||||
}
|
||||
wakeupNeeded = false; /* should be false, but make sure */
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1192,12 +1212,11 @@ LockRelease(LOCKMETHOD lockmethod, LOCKTAG *locktag,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wake up waiters if needed.
|
||||
*/
|
||||
if (wakeupNeeded)
|
||||
ProcLockWakeup(lockmethod, lock);
|
||||
#ifdef LOCK_DEBUG
|
||||
else if (LOCK_DEBUG_ENABLED(lock))
|
||||
elog(DEBUG, "LockRelease: no wakeup needed");
|
||||
#endif
|
||||
ProcLockWakeup(lockMethodTable, lock);
|
||||
|
||||
SpinRelease(masterLock);
|
||||
return TRUE;
|
||||
@@ -1310,8 +1329,8 @@ LockReleaseAll(LOCKMETHOD lockmethod, PROC *proc,
|
||||
else
|
||||
{
|
||||
/* --------------
|
||||
* set nRequested to zero so that we can garbage collect the lock
|
||||
* down below...
|
||||
* This holder accounts for all the requested locks on the object,
|
||||
* so we can be lazy and just zero things out.
|
||||
* --------------
|
||||
*/
|
||||
lock->nRequested = 0;
|
||||
@@ -1347,7 +1366,7 @@ LockReleaseAll(LOCKMETHOD lockmethod, PROC *proc,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!lock->nRequested)
|
||||
if (lock->nRequested == 0)
|
||||
{
|
||||
/* --------------------
|
||||
* We've just released the last lock, so garbage-collect the
|
||||
@@ -1359,7 +1378,7 @@ LockReleaseAll(LOCKMETHOD lockmethod, PROC *proc,
|
||||
lock = (LOCK *) hash_search(lockMethodTable->lockHash,
|
||||
(Pointer) &(lock->tag),
|
||||
HASH_REMOVE, &found);
|
||||
if ((!lock) || (!found))
|
||||
if (!lock || !found)
|
||||
{
|
||||
SpinRelease(masterLock);
|
||||
elog(NOTICE, "LockReleaseAll: cannot remove lock from HTAB");
|
||||
@@ -1367,7 +1386,7 @@ LockReleaseAll(LOCKMETHOD lockmethod, PROC *proc,
|
||||
}
|
||||
}
|
||||
else if (wakeupNeeded)
|
||||
ProcLockWakeup(lockmethod, lock);
|
||||
ProcLockWakeup(lockMethodTable, lock);
|
||||
|
||||
next_item:
|
||||
holder = nextHolder;
|
||||
@@ -1412,245 +1431,6 @@ LockShmemSize(int maxBackends)
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* DeadLockCheck -- Checks for deadlocks for a given process
|
||||
*
|
||||
* This code takes a list of locks a process holds, and the lock that
|
||||
* the process is sleeping on, and tries to find if any of the processes
|
||||
* waiting on its locks hold the lock it is waiting for. If no deadlock
|
||||
* is found, it goes on to look at all the processes waiting on their locks.
|
||||
*
|
||||
* We can't block on user locks, so no sense testing for deadlock
|
||||
* because there is no blocking, and no timer for the block. So,
|
||||
* only look at regular locks.
|
||||
*
|
||||
* We have already locked the master lock before being called.
|
||||
*/
|
||||
bool
|
||||
DeadLockCheck(PROC *thisProc, LOCK *findlock)
|
||||
{
|
||||
PROC *waitProc;
|
||||
PROC_QUEUE *waitQueue;
|
||||
SHM_QUEUE *procHolders = &(thisProc->procHolders);
|
||||
HOLDER *holder;
|
||||
HOLDER *nextHolder;
|
||||
LOCKMETHODCTL *lockctl = LockMethodTable[DEFAULT_LOCKMETHOD]->ctl;
|
||||
LOCK *lock;
|
||||
int i,
|
||||
j;
|
||||
bool first_run = (thisProc == MyProc);
|
||||
|
||||
static PROC *checked_procs[MAXBACKENDS];
|
||||
static int nprocs;
|
||||
|
||||
/* initialize at start of recursion */
|
||||
if (first_run)
|
||||
{
|
||||
checked_procs[0] = thisProc;
|
||||
nprocs = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan over all the locks held/awaited by thisProc.
|
||||
*/
|
||||
holder = (HOLDER *) SHMQueueNext(procHolders, procHolders,
|
||||
offsetof(HOLDER, procLink));
|
||||
|
||||
while (holder)
|
||||
{
|
||||
/* Get link first, since we may unlink/delete this holder */
|
||||
nextHolder = (HOLDER *) SHMQueueNext(procHolders, &holder->procLink,
|
||||
offsetof(HOLDER, procLink));
|
||||
|
||||
Assert(holder->tag.proc == MAKE_OFFSET(thisProc));
|
||||
|
||||
lock = (LOCK *) MAKE_PTR(holder->tag.lock);
|
||||
|
||||
/* Ignore user locks */
|
||||
if (lock->tag.lockmethod != DEFAULT_LOCKMETHOD)
|
||||
goto nxtl;
|
||||
|
||||
HOLDER_PRINT("DeadLockCheck", holder);
|
||||
LOCK_PRINT("DeadLockCheck", lock, 0);
|
||||
|
||||
/*
|
||||
* waitLock is always in procHolders of waiting proc, if !first_run
|
||||
* then upper caller will handle waitProcs queue of waitLock.
|
||||
*/
|
||||
if (thisProc->waitLock == lock && !first_run)
|
||||
goto nxtl;
|
||||
|
||||
/*
|
||||
* If we found proc holding findlock and sleeping on some my other
|
||||
* lock then we have to check does it block me or another waiters.
|
||||
*/
|
||||
if (lock == findlock && !first_run)
|
||||
{
|
||||
int lm;
|
||||
|
||||
Assert(holder->nHolding > 0);
|
||||
for (lm = 1; lm <= lockctl->numLockModes; lm++)
|
||||
{
|
||||
if (holder->holding[lm] > 0 &&
|
||||
lockctl->conflictTab[lm] & findlock->waitMask)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Else - get the next lock from thisProc's procHolders
|
||||
*/
|
||||
goto nxtl;
|
||||
}
|
||||
|
||||
waitQueue = &(lock->waitProcs);
|
||||
waitProc = (PROC *) MAKE_PTR(waitQueue->links.next);
|
||||
|
||||
/*
|
||||
* Inner loop scans over all processes waiting for this lock.
|
||||
*
|
||||
* NOTE: loop must count down because we want to examine each item
|
||||
* in the queue even if waitQueue->size decreases due to waking up
|
||||
* some of the processes.
|
||||
*/
|
||||
for (i = waitQueue->size; --i >= 0; )
|
||||
{
|
||||
Assert(waitProc->waitLock == lock);
|
||||
if (waitProc == thisProc)
|
||||
{
|
||||
/* This should only happen at first level */
|
||||
Assert(waitProc == MyProc);
|
||||
goto nextWaitProc;
|
||||
}
|
||||
if (lock == findlock) /* first_run also true */
|
||||
{
|
||||
/*
|
||||
* If I'm blocked by his heldLocks...
|
||||
*/
|
||||
if (lockctl->conflictTab[MyProc->waitLockMode] & waitProc->heldLocks)
|
||||
{
|
||||
/* and he blocked by me -> deadlock */
|
||||
if (lockctl->conflictTab[waitProc->waitLockMode] & MyProc->heldLocks)
|
||||
return true;
|
||||
/* we shouldn't look at procHolders of our blockers */
|
||||
goto nextWaitProc;
|
||||
}
|
||||
|
||||
/*
|
||||
* If he isn't blocked by me and we request
|
||||
* non-conflicting lock modes - no deadlock here because
|
||||
* he isn't blocked by me in any sense (explicitly or
|
||||
* implicitly). Note that we don't do like test if
|
||||
* !first_run (when thisProc is holder and non-waiter on
|
||||
* lock) and so we call DeadLockCheck below for every
|
||||
* waitProc in thisProc->procHolders, even for waitProc-s
|
||||
* un-blocked by thisProc. Should we? This could save us
|
||||
* some time...
|
||||
*/
|
||||
if (!(lockctl->conflictTab[waitProc->waitLockMode] & MyProc->heldLocks) &&
|
||||
!(lockctl->conflictTab[waitProc->waitLockMode] & (1 << MyProc->waitLockMode)))
|
||||
goto nextWaitProc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip this waiter if already checked.
|
||||
*/
|
||||
for (j = 0; j < nprocs; j++)
|
||||
{
|
||||
if (checked_procs[j] == waitProc)
|
||||
goto nextWaitProc;
|
||||
}
|
||||
|
||||
/* Recursively check this process's procHolders. */
|
||||
Assert(nprocs < MAXBACKENDS);
|
||||
checked_procs[nprocs++] = waitProc;
|
||||
|
||||
if (DeadLockCheck(waitProc, findlock))
|
||||
{
|
||||
int heldLocks;
|
||||
|
||||
/*
|
||||
* Ok, but is waitProc waiting for me (thisProc) ?
|
||||
*/
|
||||
if (thisProc->waitLock == lock)
|
||||
{
|
||||
Assert(first_run);
|
||||
heldLocks = thisProc->heldLocks;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* should we cache heldLocks to speed this up? */
|
||||
heldLocks = LockGetMyHeldLocks(holder->tag.lock, thisProc);
|
||||
Assert(heldLocks != 0);
|
||||
}
|
||||
if (lockctl->conflictTab[waitProc->waitLockMode] & heldLocks)
|
||||
{
|
||||
/*
|
||||
* Last attempt to avoid deadlock: try to wakeup myself.
|
||||
*/
|
||||
if (first_run)
|
||||
{
|
||||
if (LockResolveConflicts(DEFAULT_LOCKMETHOD,
|
||||
MyProc->waitLockMode,
|
||||
MyProc->waitLock,
|
||||
MyProc->waitHolder,
|
||||
MyProc,
|
||||
NULL) == STATUS_OK)
|
||||
{
|
||||
GrantLock(MyProc->waitLock,
|
||||
MyProc->waitHolder,
|
||||
MyProc->waitLockMode);
|
||||
ProcWakeup(MyProc, NO_ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hell! Is he blocked by any (other) holder ?
|
||||
*/
|
||||
if (LockResolveConflicts(DEFAULT_LOCKMETHOD,
|
||||
waitProc->waitLockMode,
|
||||
lock,
|
||||
waitProc->waitHolder,
|
||||
waitProc,
|
||||
NULL) != STATUS_OK)
|
||||
{
|
||||
/*
|
||||
* Blocked by others - no deadlock...
|
||||
*/
|
||||
LOCK_PRINT("DeadLockCheck: blocked by others",
|
||||
lock, waitProc->waitLockMode);
|
||||
goto nextWaitProc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Well - wakeup this guy! This is the case of
|
||||
* implicit blocking: thisProc blocked someone who
|
||||
* blocked waitProc by the fact that he/someone is
|
||||
* already waiting for lock. We do this for
|
||||
* anti-starving.
|
||||
*/
|
||||
GrantLock(lock, waitProc->waitHolder, waitProc->waitLockMode);
|
||||
waitProc = ProcWakeup(waitProc, NO_ERROR);
|
||||
/*
|
||||
* Use next-proc link returned by ProcWakeup, since this
|
||||
* proc's own links field is now cleared.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
nextWaitProc:
|
||||
waitProc = (PROC *) MAKE_PTR(waitProc->links.next);
|
||||
}
|
||||
|
||||
nxtl:
|
||||
holder = nextHolder;
|
||||
}
|
||||
|
||||
/* if we got here, no deadlock */
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef LOCK_DEBUG
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user