diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 82c31d918c..73cb19757c 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -55,7 +55,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.65 2007/10/25 19:13:37 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.66 2007/10/26 20:45:10 alvherre Exp $ * *------------------------------------------------------------------------- */ @@ -2120,6 +2120,14 @@ next_worker: tab->at_doanalyze, tab->at_freeze_min_age, bstrategy); + + /* + * Clear a possible query-cancel signal, to avoid a late reaction + * to an automatically-sent signal because of vacuuming the current + * table (we're done with it, so it would make no sense to cancel + * at this point.) + */ + QueryCancelPending = false; } PG_CATCH(); { diff --git a/src/backend/storage/lmgr/README b/src/backend/storage/lmgr/README index 8884676d28..5abef17116 100644 --- a/src/backend/storage/lmgr/README +++ b/src/backend/storage/lmgr/README @@ -1,4 +1,4 @@ -$PostgreSQL: pgsql/src/backend/storage/lmgr/README,v 1.21 2006/09/18 22:40:36 tgl Exp $ +$PostgreSQL: pgsql/src/backend/storage/lmgr/README,v 1.22 2007/10/26 20:45:10 alvherre Exp $ LOCKING OVERVIEW @@ -487,6 +487,13 @@ seems a safer approach than trying to allocate workspace on the fly; we don't want to risk having the deadlock detector run out of memory, else we really have no guarantees at all that deadlock will be detected. +6. We abuse the deadlock detector to implement autovacuum cancellation. +When we run the detector and we find that there's an autovacuum worker +involved in the waits-for graph, we store a pointer to its PGPROC, and +return a special return code (unless a hard deadlock has been detected). +The caller can then send a cancellation signal. This implements the +principle that autovacuum has a low locking priority (eg it must not block +DDL on the table). USER LOCKS diff --git a/src/backend/storage/lmgr/deadlock.c b/src/backend/storage/lmgr/deadlock.c index 36e1b84a08..e599fa3fb8 100644 --- a/src/backend/storage/lmgr/deadlock.c +++ b/src/backend/storage/lmgr/deadlock.c @@ -12,7 +12,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/storage/lmgr/deadlock.c,v 1.48 2007/06/19 20:13:21 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/storage/lmgr/deadlock.c,v 1.49 2007/10/26 20:45:10 alvherre Exp $ * * Interface: * @@ -109,6 +109,9 @@ static int maxPossibleConstraints; static DEADLOCK_INFO *deadlockDetails; static int nDeadlockDetails; +/* PGPROC pointer of any blocking autovacuum worker found */ +static PGPROC *blocking_autovacuum_proc = NULL; + /* * InitDeadLockChecking -- initialize deadlock checker during backend startup @@ -206,6 +209,9 @@ DeadLockCheck(PGPROC *proc) nPossibleConstraints = 0; nWaitOrders = 0; + /* Initialize to not blocked by an autovacuum worker */ + blocking_autovacuum_proc = NULL; + /* Search for deadlocks and possible fixes */ if (DeadLockCheckRecurse(proc)) { @@ -255,10 +261,28 @@ DeadLockCheck(PGPROC *proc) /* Return code tells caller if we had to escape a deadlock or not */ if (nWaitOrders > 0) return DS_SOFT_DEADLOCK; + else if (blocking_autovacuum_proc != NULL) + return DS_BLOCKED_BY_AUTOVACUUM; else return DS_NO_DEADLOCK; } +/* + * Return the PGPROC of the autovacuum that's blocking a process. + * + * We reset the saved pointer as soon as we pass it back. + */ +PGPROC * +GetBlockingAutoVacuumPgproc(void) +{ + PGPROC *ptr; + + ptr = blocking_autovacuum_proc; + blocking_autovacuum_proc = NULL; + + return ptr; +} + /* * DeadLockCheckRecurse -- recursively search for valid orderings * @@ -497,6 +521,25 @@ FindLockCycleRecurse(PGPROC *checkProc, if ((proclock->holdMask & LOCKBIT_ON(lm)) && (conflictMask & LOCKBIT_ON(lm))) { + /* + * Look for a blocking autovacuum. There can be more than + * one in the deadlock cycle, in which case we just pick a + * random one. We stash the autovacuum worker's PGPROC so + * that the caller can send a cancel signal to it, if + * appropriate. + * + * Note we read vacuumFlags without any locking. This is + * OK only for checking the PROC_IS_AUTOVACUUM flag, + * because that flag is set at process start and never + * reset; there is logic elsewhere to avoid cancelling an + * autovacuum that is working for preventing Xid wraparound + * problems (which needs to read a different vacuumFlag + * bit), but we don't do that here to avoid grabbing + * ProcArrayLock. + */ + if (proc->vacuumFlags & PROC_IS_AUTOVACUUM) + blocking_autovacuum_proc = proc; + /* This proc hard-blocks checkProc */ if (FindLockCycleRecurse(proc, depth + 1, softEdges, nSoftEdges)) diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index fdf089f836..4b2280b550 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.195 2007/10/24 20:55:36 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.196 2007/10/26 20:45:10 alvherre Exp $ * *------------------------------------------------------------------------- */ @@ -734,6 +734,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) PROC_QUEUE *waitQueue = &(lock->waitProcs); LOCKMASK myHeldLocks = MyProc->heldLocks; bool early_deadlock = false; + bool allow_autovacuum_cancel = true; int myWaitStatus; PGPROC *proc; int i; @@ -893,6 +894,48 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) */ myWaitStatus = MyProc->waitStatus; + /* + * If we are not deadlocked, but are waiting on an autovacuum-induced + * task, send a signal to interrupt it. + */ + if (deadlock_state == DS_BLOCKED_BY_AUTOVACUUM && allow_autovacuum_cancel) + { + PGPROC *autovac = GetBlockingAutoVacuumPgproc(); + + LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + + /* + * Only do it if the worker is not working to protect against Xid + * wraparound. + */ + if ((autovac != NULL) && + (autovac->vacuumFlags & PROC_IS_AUTOVACUUM) && + !(autovac->vacuumFlags & PROC_VACUUM_FOR_WRAPAROUND)) + { + int pid = autovac->pid; + + elog(DEBUG2, "sending cancel to blocking autovacuum pid = %d", + pid); + + /* don't hold the lock across the kill() syscall */ + LWLockRelease(ProcArrayLock); + + /* send the autovacuum worker Back to Old Kent Road */ + if (kill(pid, SIGINT) < 0) + { + /* Just a warning to allow multiple callers */ + ereport(WARNING, + (errmsg("could not send signal to process %d: %m", + pid))); + } + } + else + LWLockRelease(ProcArrayLock); + + /* prevent signal from being resent more than once */ + allow_autovacuum_cancel = false; + } + /* * If awoken after the deadlock check interrupt has run, and * log_lock_waits is on, then report about the wait. @@ -1189,13 +1232,16 @@ CheckDeadLock(void) * RemoveFromWaitQueue took care of waking up any such processes. */ } - else if (log_lock_waits) + else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM) { /* * Unlock my semaphore so that the interrupted ProcSleep() call can * print the log message (we daren't do it here because we are inside * a signal handler). It will then sleep again until someone * releases the lock. + * + * If blocked by autovacuum, this wakeup will enable ProcSleep to send + * the cancelling signal to the autovacuum worker. */ PGSemaphoreUnlock(&MyProc->sem); } diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index 30c8a3fa2b..005c99ee7d 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/storage/lock.h,v 1.107 2007/09/05 18:10:48 tgl Exp $ + * $PostgreSQL: pgsql/src/include/storage/lock.h,v 1.108 2007/10/26 20:45:10 alvherre Exp $ * *------------------------------------------------------------------------- */ @@ -442,7 +442,9 @@ typedef enum DS_NOT_YET_CHECKED, /* no deadlock check has run yet */ DS_NO_DEADLOCK, /* no deadlock detected */ DS_SOFT_DEADLOCK, /* deadlock avoided by queue rearrangement */ - DS_HARD_DEADLOCK /* deadlock, no way out but ERROR */ + DS_HARD_DEADLOCK, /* deadlock, no way out but ERROR */ + DS_BLOCKED_BY_AUTOVACUUM /* no deadlock; queue blocked by autovacuum + worker */ } DeadLockState; @@ -495,6 +497,7 @@ extern void lock_twophase_postabort(TransactionId xid, uint16 info, void *recdata, uint32 len); extern DeadLockState DeadLockCheck(PGPROC *proc); +extern PGPROC *GetBlockingAutoVacuumPgproc(void); extern void DeadLockReport(void); extern void RememberSimpleDeadLock(PGPROC *proc1, LOCKMODE lockmode,