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 82c31d918cdae6b21494ef7d6555418c225316d7..73cb19757c9c97d9a70a69fdcc474f6a5a04bc2e 100644 (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();
        {
index 8884676d28458d4c8dd805f613b9cb5e981b7f28..5abef171169b9b87565d041477a4c4b504e48d38 100644 (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
 
index 36e1b84a0839885c564f0aac96e8144c4cb5cd41..e599fa3fb8d7bcf3cd4c65314b65cf2924d8f873 100644 (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))
index fdf089f836c39dc8dc707f551913d47ba2101d8b..4b2280b55067f23eea07d4f2e378dceb07ed245e 100644 (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);
    }
index 30c8a3fa2bc208f71a59f354651772082450fa29..005c99ee7dc6218896d3901190c68964bf317c0f 100644 (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,