📄 deadlock.c
字号:
/*------------------------------------------------------------------------- * * deadlock.c * POSTGRES deadlock detection code * * See src/backend/storage/lmgr/README for a description of the deadlock * detection and resolution algorithms. * * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/deadlock.c,v 1.25 2003/09/25 06:58:02 petere Exp $ * * Interface: * * DeadLockCheck() * DeadLockReport() * RememberSimpleDeadLock() * InitDeadLockChecking() * *------------------------------------------------------------------------- */#include "postgres.h"#include "lib/stringinfo.h"#include "miscadmin.h"#include "storage/proc.h"#include "utils/memutils.h"/* One edge in the waits-for graph */typedef struct{ PGPROC *waiter; /* the waiting process */ PGPROC *blocker; /* the process it is waiting for */ int pred; /* workspace for TopoSort */ int link; /* workspace for TopoSort */} EDGE;/* One potential reordering of a lock's wait queue */typedef struct{ LOCK *lock; /* the lock whose wait queue is described */ PGPROC **procs; /* array of PGPROC *'s in new wait order */ int nProcs;} WAIT_ORDER;/* * Information saved about each edge in a detected deadlock cycle. This * is used to print a diagnostic message upon failure. * * Note: because we want to examine this info after releasing the LockMgrLock, * we can't just store LOCK and PGPROC pointers; we must extract out all the * info we want to be able to print. */typedef struct{ LOCKTAG locktag; /* ID of awaited lock object */ LOCKMODE lockmode; /* type of lock we're waiting for */ int pid; /* PID of blocked backend */} DEADLOCK_INFO;static bool DeadLockCheckRecurse(PGPROC *proc);static int TestConfiguration(PGPROC *startProc);static bool FindLockCycle(PGPROC *checkProc, EDGE *softEdges, int *nSoftEdges);static bool FindLockCycleRecurse(PGPROC *checkProc, int depth, EDGE *softEdges, int *nSoftEdges);static bool ExpandConstraints(EDGE *constraints, int nConstraints);static bool TopoSort(LOCK *lock, EDGE *constraints, int nConstraints, PGPROC **ordering);#ifdef DEBUG_DEADLOCKstatic void PrintLockQueue(LOCK *lock, const char *info);#endif/* * Working space for the deadlock detector *//* Workspace for FindLockCycle */static PGPROC **visitedProcs; /* Array of visited procs */static int nVisitedProcs;/* Workspace for TopoSort */static PGPROC **topoProcs; /* Array of not-yet-output procs */static int *beforeConstraints; /* Counts of remaining before-constraints */static int *afterConstraints; /* List head for after-constraints *//* Output area for ExpandConstraints */static WAIT_ORDER *waitOrders; /* Array of proposed queue rearrangements */static int nWaitOrders;static PGPROC **waitOrderProcs; /* Space for waitOrders queue contents *//* Current list of constraints being considered */static EDGE *curConstraints;static int nCurConstraints;static int maxCurConstraints;/* Storage space for results from FindLockCycle */static EDGE *possibleConstraints;static int nPossibleConstraints;static int maxPossibleConstraints;static DEADLOCK_INFO *deadlockDetails;static int nDeadlockDetails;/* * InitDeadLockChecking -- initialize deadlock checker during backend startup * * This does per-backend initialization of the deadlock checker; primarily, * allocation of working memory for DeadLockCheck. We do this per-backend * since there's no percentage in making the kernel do copy-on-write * inheritance of workspace from the postmaster. We want to allocate the * space at startup because the deadlock checker might be invoked when there's * no free memory left. */voidInitDeadLockChecking(void){ MemoryContext oldcxt; /* Make sure allocations are permanent */ oldcxt = MemoryContextSwitchTo(TopMemoryContext); /* * FindLockCycle needs at most MaxBackends entries in visitedProcs[] * and deadlockDetails[]. */ visitedProcs = (PGPROC **) palloc(MaxBackends * sizeof(PGPROC *)); deadlockDetails = (DEADLOCK_INFO *) palloc(MaxBackends * sizeof(DEADLOCK_INFO)); /* * TopoSort needs to consider at most MaxBackends wait-queue entries, * and it needn't run concurrently with FindLockCycle. */ topoProcs = visitedProcs; /* re-use this space */ beforeConstraints = (int *) palloc(MaxBackends * sizeof(int)); afterConstraints = (int *) palloc(MaxBackends * sizeof(int)); /* * We need to consider rearranging at most MaxBackends/2 wait queues * (since it takes at least two waiters in a queue to create a soft * edge), and the expanded form of the wait queues can't involve more * than MaxBackends total waiters. (But avoid palloc(0) if * MaxBackends = 1.) */ waitOrders = (WAIT_ORDER *) palloc(((MaxBackends + 1) / 2) * sizeof(WAIT_ORDER)); waitOrderProcs = (PGPROC **) palloc(MaxBackends * sizeof(PGPROC *)); /* * Allow at most MaxBackends distinct constraints in a configuration. * (Is this enough? In practice it seems it should be, but I don't * quite see how to prove it. If we run out, we might fail to find a * workable wait queue rearrangement even though one exists.) NOTE * that this number limits the maximum recursion depth of * DeadLockCheckRecurse. Making it really big might potentially allow * a stack-overflow problem. */ maxCurConstraints = MaxBackends; curConstraints = (EDGE *) palloc(maxCurConstraints * sizeof(EDGE)); /* * Allow up to 3*MaxBackends constraints to be saved without having to * re-run TestConfiguration. (This is probably more than enough, but * we can survive if we run low on space by doing excess runs of * TestConfiguration to re-compute constraint lists each time needed.) * The last MaxBackends entries in possibleConstraints[] are reserved * as output workspace for FindLockCycle. */ maxPossibleConstraints = MaxBackends * 4; possibleConstraints = (EDGE *) palloc(maxPossibleConstraints * sizeof(EDGE)); MemoryContextSwitchTo(oldcxt);}/* * DeadLockCheck -- Checks for deadlocks for a given process * * This code looks for deadlocks involving the given process. If any * are found, it tries to rearrange lock wait queues to resolve the * deadlock. If resolution is impossible, return TRUE --- the caller * is then expected to abort the given proc's transaction. * * 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 must have already locked the master lock before being called. * NOTE: although the lockmethod structure appears to allow each lock * table to have a different masterLock, all locks that can block had * better use the same LWLock, else this code will not be adequately * interlocked! * * On failure, deadlock details are recorded in deadlockDetails[] for * subsequent printing by DeadLockReport(). That activity is separate * because we don't want to do it while holding the master lock. */boolDeadLockCheck(PGPROC *proc){ int i, j; /* Initialize to "no constraints" */ nCurConstraints = 0; nPossibleConstraints = 0; nWaitOrders = 0; /* Search for deadlocks and possible fixes */ if (DeadLockCheckRecurse(proc)) { /* * Call FindLockCycle one more time, to record the correct * deadlockDetails[] for the basic state with no rearrangements. */ int nSoftEdges; nWaitOrders = 0; if (!FindLockCycle(proc, possibleConstraints, &nSoftEdges)) elog(FATAL, "deadlock seems to have disappeared"); return true; /* cannot find a non-deadlocked state */ } /* Apply any needed rearrangements of wait queues */ for (i = 0; i < nWaitOrders; i++) { LOCK *lock = waitOrders[i].lock; PGPROC **procs = waitOrders[i].procs; int nProcs = waitOrders[i].nProcs; PROC_QUEUE *waitQueue = &(lock->waitProcs); Assert(nProcs == waitQueue->size);#ifdef DEBUG_DEADLOCK PrintLockQueue(lock, "DeadLockCheck:");#endif /* Reset the queue and re-add procs in the desired order */ ProcQueueInit(waitQueue); for (j = 0; j < nProcs; j++) { SHMQueueInsertBefore(&(waitQueue->links), &(procs[j]->links)); waitQueue->size++; }#ifdef DEBUG_DEADLOCK PrintLockQueue(lock, "rearranged to:");#endif /* See if any waiters for the lock can be woken up now */ ProcLockWakeup(GetLocksMethodTable(lock), lock); } return false;}/* * DeadLockCheckRecurse -- recursively search for valid orderings * * curConstraints[] holds the current set of constraints being considered * by an outer level of recursion. Add to this each possible solution * constraint for any cycle detected at this level. * * Returns TRUE if no solution exists. Returns FALSE if a deadlock-free * state is attainable, in which case waitOrders[] shows the required * rearrangements of lock wait queues (if any). */static boolDeadLockCheckRecurse(PGPROC *proc){ int nEdges; int oldPossibleConstraints; bool savedList; int i; nEdges = TestConfiguration(proc); if (nEdges < 0) return true; /* hard deadlock --- no solution */ if (nEdges == 0) return false; /* good configuration found */ if (nCurConstraints >= maxCurConstraints) return true; /* out of room for active constraints? */ oldPossibleConstraints = nPossibleConstraints; if (nPossibleConstraints + nEdges + MaxBackends <= maxPossibleConstraints) { /* We can save the edge list in possibleConstraints[] */ nPossibleConstraints += nEdges; savedList = true; } else { /* Not room; will need to regenerate the edges on-the-fly */ savedList = false; } /* * Try each available soft edge as an addition to the configuration. */ for (i = 0; i < nEdges; i++) { if (!savedList && i > 0) { /* Regenerate the list of possible added constraints */ if (nEdges != TestConfiguration(proc)) elog(FATAL, "inconsistent results during deadlock check"); } curConstraints[nCurConstraints] = possibleConstraints[oldPossibleConstraints + i]; nCurConstraints++; if (!DeadLockCheckRecurse(proc)) return false; /* found a valid solution! */ /* give up on that added constraint, try again */ nCurConstraints--; } nPossibleConstraints = oldPossibleConstraints; return true; /* no solution found */}/*-------------------- * Test a configuration (current set of constraints) for validity. * * Returns: * 0: the configuration is good (no deadlocks) * -1: the configuration has a hard deadlock or is not self-consistent * >0: the configuration has one or more soft deadlocks * * In the soft-deadlock case, one of the soft cycles is chosen arbitrarily * and a list of its soft edges is returned beginning at * possibleConstraints+nPossibleConstraints. The return value is the * number of soft edges. *-------------------- */static intTestConfiguration(PGPROC *startProc){ int softFound = 0; EDGE *softEdges = possibleConstraints + nPossibleConstraints; int nSoftEdges; int i; /* * Make sure we have room for FindLockCycle's output. */ if (nPossibleConstraints + MaxBackends > maxPossibleConstraints) return -1; /* * Expand current constraint set into wait orderings. Fail if the * constraint set is not self-consistent. */ if (!ExpandConstraints(curConstraints, nCurConstraints)) return -1; /* * Check for cycles involving startProc or any of the procs mentioned * in constraints. We check startProc last because if it has a soft * cycle still to be dealt with, we want to deal with that first. */ for (i = 0; i < nCurConstraints; i++) { if (FindLockCycle(curConstraints[i].waiter, softEdges, &nSoftEdges)) { if (nSoftEdges == 0) return -1; /* hard deadlock detected */ softFound = nSoftEdges; } if (FindLockCycle(curConstraints[i].blocker, softEdges, &nSoftEdges)) { if (nSoftEdges == 0) return -1; /* hard deadlock detected */ softFound = nSoftEdges; } } if (FindLockCycle(startProc, softEdges, &nSoftEdges)) { if (nSoftEdges == 0) return -1; /* hard deadlock detected */ softFound = nSoftEdges; } return softFound;}/* * FindLockCycle -- basic check for deadlock cycles * * Scan outward from the given proc to see if there is a cycle in the * waits-for graph that includes this proc. Return TRUE if a cycle * is found, else FALSE. If a cycle is found, we return a list of * the "soft edges", if any, included in the cycle. These edges could * potentially be eliminated by rearranging wait queues. We also fill * deadlockDetails[] with information about the detected cycle; this info * is not used by the deadlock algorithm itself, only to print a useful * message after failing. * * Since we need to be able to check hypothetical configurations that would * exist after wait queue rearrangement, the routine pays attention to the * table of hypothetical queue orders in waitOrders[]. These orders will * be believed in preference to the actual ordering seen in the locktable. */static boolFindLockCycle(PGPROC *checkProc, EDGE *softEdges, /* output argument */ int *nSoftEdges) /* output argument */{ nVisitedProcs = 0; nDeadlockDetails = 0; *nSoftEdges = 0; return FindLockCycleRecurse(checkProc, 0, softEdges, nSoftEdges);}static boolFindLockCycleRecurse(PGPROC *checkProc, int depth, EDGE *softEdges, /* output argument */ int *nSoftEdges) /* output argument */{ PGPROC *proc; LOCK *lock; PROCLOCK *proclock; SHM_QUEUE *lockHolders; LOCKMETHODTABLE *lockMethodTable; PROC_QUEUE *waitQueue; int queue_size; int conflictMask; int i; int numLockModes, lm; /* * Have we already seen this proc? */ for (i = 0; i < nVisitedProcs; i++) { if (visitedProcs[i] == checkProc) { /* If we return to starting point, we have a deadlock cycle */ if (i == 0) { /* * record total length of cycle --- outer levels will now * fill deadlockDetails[] */ Assert(depth <= MaxBackends); nDeadlockDetails = depth; return true; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -