]> granicus.if.org Git - postgresql/commitdiff
Don't let timeout interrupts happen unless ImmediateInterruptOK is set.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 13 Dec 2013 16:50:15 +0000 (11:50 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 13 Dec 2013 16:50:25 +0000 (11:50 -0500)
Serious oversight in commit 16e1b7a1b7f7ffd8a18713e83c8cd72c9ce48e07:
we should not allow an interrupt to take control away from mainline code
except when ImmediateInterruptOK is set.  Just to be safe, let's adopt
the same save-clear-restore dance that's been used for many years in
HandleCatchupInterrupt and HandleNotifyInterrupt, so that nothing bad
happens if a timeout handler invokes code that tests or even manipulates
ImmediateInterruptOK.

Per report of "stuck spinlock" failures from Christophe Pettus, though
many other symptoms are possible.  Diagnosis by Andres Freund.

src/backend/utils/misc/timeout.c

index aae947e601b483b4ab0281cc884c6fe60a6f5380..059253904380d40a71594a35b6811ee04ecdc61c 100644 (file)
@@ -259,22 +259,27 @@ static void
 handle_sig_alarm(SIGNAL_ARGS)
 {
        int                     save_errno = errno;
+       bool            save_ImmediateInterruptOK = ImmediateInterruptOK;
 
        /*
         * We may be executing while ImmediateInterruptOK is true (e.g., when
         * mainline is waiting for a lock).  If SIGINT or similar arrives while
         * this code is running, we'd lose control and perhaps leave our data
-        * structures in an inconsistent state.  Hold off interrupts to prevent
-        * that.
+        * structures in an inconsistent state.  Disable immediate interrupts, and
+        * just to be real sure, bump the holdoff counter as well.      (The reason
+        * for this belt-and-suspenders-too approach is to make sure that nothing
+        * bad happens if a timeout handler calls code that manipulates
+        * ImmediateInterruptOK.)
         *
-        * Note: it's possible for a SIGINT to interrupt handle_sig_alarm even
-        * before we reach HOLD_INTERRUPTS(); the net effect would be as if the
-        * SIGALRM event had been silently lost.  Therefore error recovery must
-        * include some action that will allow any lost interrupt to be
-        * rescheduled.  Disabling some or all timeouts is sufficient, or if
-        * that's not appropriate, reschedule_timeouts() can be called.  Also, the
-        * signal blocking hazard described below applies here too.
+        * Note: it's possible for a SIGINT to interrupt handle_sig_alarm before
+        * we manage to do this; the net effect would be as if the SIGALRM event
+        * had been silently lost.      Therefore error recovery must include some
+        * action that will allow any lost interrupt to be rescheduled.  Disabling
+        * some or all timeouts is sufficient, or if that's not appropriate,
+        * reschedule_timeouts() can be called.  Also, the signal blocking hazard
+        * described below applies here too.
         */
+       ImmediateInterruptOK = false;
        HOLD_INTERRUPTS();
 
        /*
@@ -330,9 +335,12 @@ handle_sig_alarm(SIGNAL_ARGS)
        }
 
        /*
-        * Re-allow query cancel, and then service any cancel request that arrived
-        * meanwhile (this might in particular include a cancel request fired by
-        * one of the timeout handlers).
+        * Re-allow query cancel, and then try to service any cancel request that
+        * arrived meanwhile (this might in particular include a cancel request
+        * fired by one of the timeout handlers).  Since we are in a signal
+        * handler, we mustn't call ProcessInterrupts unless ImmediateInterruptOK
+        * is set; if it isn't, the cancel will happen at the next mainline
+        * CHECK_FOR_INTERRUPTS.
         *
         * Note: a longjmp from here is safe so far as our own data structures are
         * concerned; but on platforms that block a signal before calling the
@@ -341,7 +349,9 @@ handle_sig_alarm(SIGNAL_ARGS)
         * unblocking any blocked signals.
         */
        RESUME_INTERRUPTS();
-       CHECK_FOR_INTERRUPTS();
+       ImmediateInterruptOK = save_ImmediateInterruptOK;
+       if (save_ImmediateInterruptOK)
+               CHECK_FOR_INTERRUPTS();
 
        errno = save_errno;
 }