Allow an autovacuum worker to be interrupted automatically when it is found

to be locking another process (except when it's working to prevent Xid
wraparound problems).
This commit is contained in:
Alvaro Herrera 2007-10-26 20:45:10 +00:00
parent ffda32e9fd
commit acac68b2bc
5 changed files with 114 additions and 7 deletions

View File

@ -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();
{

View File

@ -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

View File

@ -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))

View File

@ -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);
}

View File

@ -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,