Allow an autovacuum worker to be interrupted automatically when it is found
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Fri, 26 Oct 2007 20:45:10 +0000 (20:45 +0000)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Fri, 26 Oct 2007 20:45:10 +0000 (20:45 +0000)
to be locking another process (except when it's working to prevent Xid
wraparound problems).

src/backend/postmaster/autovacuum.c
src/backend/storage/lmgr/README
src/backend/storage/lmgr/deadlock.c
src/backend/storage/lmgr/proc.c
src/include/storage/lock.h

index e2a03a307c855cfa17beed2c0afc10374c8aa9c8..45428011d20895ea266d9f6d8e9335974a5c8dd8 100644 (file)
@@ -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();
                {
index c2503898318fbd37e7b4deaa1a7d972e5a148d79..62d826f75a38576cfb2c8201f81ef7dcb51c48bd 100644 (file)
@@ -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
 
index 1f32a7be972e3d9f96fa7c2b6193fc15c85eb990..d8b0daf6c3842311de6ca095d7584f7dff489fba 100644 (file)
@@ -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))
index dbfb6cee55fc0358c44cd0dad14a1bd31e1a3df2..64a9288a6f523c9d8392100b0dfa1c52d0702fd5 100644 (file)
@@ -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);
        }
index ffd4424e0fb05c0838aa177b75623c12c136d92b..84664f2b4e0b9dad381ae58bdde9be7492d6aefe 100644 (file)
@@ -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,