@@ -49,53 +49,30 @@ static bool all_timeouts_initialized = false;
4949static volatile int num_active_timeouts = 0 ;
5050static timeout_params * volatile active_timeouts [MAX_TIMEOUTS ];
5151
52+ /*
53+ * Flag controlling whether the signal handler is allowed to do anything.
54+ * We leave this "false" when we're not expecting interrupts, just in case.
55+ *
56+ * Note that we don't bother to reset any pending timer interrupt when we
57+ * disable the signal handler; it's not really worth the cycles to do so,
58+ * since the probability of the interrupt actually occurring while we have
59+ * it disabled is low. See comments in schedule_alarm() about that.
60+ */
61+ static volatile sig_atomic_t alarm_enabled = false;
62+
63+ #define disable_alarm () (alarm_enabled = false)
64+ #define enable_alarm () (alarm_enabled = true)
65+
5266
5367/*****************************************************************************
5468 * Internal helper functions
5569 *
5670 * For all of these, it is caller's responsibility to protect them from
5771 * interruption by the signal handler. Generally, call disable_alarm()
5872 * first to prevent interruption, then update state, and last call
59- * schedule_alarm(), which will re-enable the interrupt if needed.
73+ * schedule_alarm(), which will re-enable the signal handler if needed.
6074 *****************************************************************************/
6175
62- /*
63- * Disable alarm interrupts
64- *
65- * multi_insert must be true if the caller intends to activate multiple new
66- * timeouts. Otherwise it should be false.
67- */
68- static void
69- disable_alarm (bool multi_insert )
70- {
71- struct itimerval timeval ;
72-
73- /*
74- * If num_active_timeouts is zero, and multi_insert is false, we don't
75- * have to call setitimer. There should not be any pending interrupt, and
76- * even if there is, the worst possible case is that the signal handler
77- * fires during schedule_alarm. (If it fires at any point before
78- * insert_timeout has incremented num_active_timeouts, it will do nothing,
79- * since it sees no active timeouts.) In that case we could end up
80- * scheduling a useless interrupt ... but when the extra interrupt does
81- * happen, the signal handler will do nothing, so it's all good.
82- *
83- * However, if the caller intends to do anything more after first calling
84- * insert_timeout, the above argument breaks down, since the signal
85- * handler could interrupt the subsequent operations leading to corrupt
86- * state. Out of an abundance of caution, we forcibly disable the timer
87- * even though it should be off already, just to be sure. Even though
88- * this setitimer call is probably useless, we're still ahead of the game
89- * compared to scheduling two or more timeouts independently.
90- */
91- if (multi_insert || num_active_timeouts > 0 )
92- {
93- MemSet (& timeval , 0 , sizeof (struct itimerval ));
94- if (setitimer (ITIMER_REAL , & timeval , NULL ) != 0 )
95- elog (FATAL , "could not disable SIGALRM timer: %m" );
96- }
97- }
98-
9976/*
10077 * Find the index of a given timeout reason in the active array.
10178 * If it's not there, return -1.
@@ -132,7 +109,6 @@ insert_timeout(TimeoutId id, int index)
132109
133110 active_timeouts [index ] = & all_timeouts [id ];
134111
135- /* NB: this must be the last step, see comments in disable_alarm */
136112 num_active_timeouts ++ ;
137113}
138114
@@ -194,6 +170,7 @@ enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time)
194170 all_timeouts [id ].indicator = false;
195171 all_timeouts [id ].start_time = now ;
196172 all_timeouts [id ].fin_time = fin_time ;
173+
197174 insert_timeout (id , i );
198175}
199176
@@ -228,6 +205,38 @@ schedule_alarm(TimestampTz now)
228205 timeval .it_value .tv_sec = secs ;
229206 timeval .it_value .tv_usec = usecs ;
230207
208+ /*
209+ * We must enable the signal handler before calling setitimer(); if we
210+ * did it in the other order, we'd have a race condition wherein the
211+ * interrupt could occur before we can set alarm_enabled, so that the
212+ * signal handler would fail to do anything.
213+ *
214+ * Because we didn't bother to reset the timer in disable_alarm(),
215+ * it's possible that a previously-set interrupt will fire between
216+ * enable_alarm() and setitimer(). This is safe, however. There are
217+ * two possible outcomes:
218+ *
219+ * 1. The signal handler finds nothing to do (because the nearest
220+ * timeout event is still in the future). It will re-set the timer
221+ * and return. Then we'll overwrite the timer value with a new one.
222+ * This will mean that the timer fires a little later than we
223+ * intended, but only by the amount of time it takes for the signal
224+ * handler to do nothing useful, which shouldn't be much.
225+ *
226+ * 2. The signal handler executes and removes one or more timeout
227+ * events. When it returns, either the queue is now empty or the
228+ * frontmost event is later than the one we looked at above. So we'll
229+ * overwrite the timer value with one that is too soon (plus or minus
230+ * the signal handler's execution time), causing a useless interrupt
231+ * to occur. But the handler will then re-set the timer and
232+ * everything will still work as expected.
233+ *
234+ * Since these cases are of very low probability (the window here
235+ * being quite narrow), it's not worth adding cycles to the mainline
236+ * code to prevent occasional wasted interrupts.
237+ */
238+ enable_alarm ();
239+
231240 /* Set the alarm timer */
232241 if (setitimer (ITIMER_REAL , & timeval , NULL ) != 0 )
233242 elog (FATAL , "could not enable SIGALRM timer: %m" );
@@ -259,37 +268,47 @@ handle_sig_alarm(SIGNAL_ARGS)
259268 SetLatch (& MyProc -> procLatch );
260269
261270 /*
262- * Fire any pending timeouts.
271+ * Fire any pending timeouts, but only if we're enabled to do so .
263272 */
264- if (num_active_timeouts > 0 )
273+ if (alarm_enabled )
265274 {
266- TimestampTz now = GetCurrentTimestamp ();
275+ /*
276+ * Disable alarms, just in case this platform allows signal handlers
277+ * to interrupt themselves. schedule_alarm() will re-enable if
278+ * appropriate.
279+ */
280+ disable_alarm ();
267281
268- /* While the first pending timeout has been reached ... */
269- while (num_active_timeouts > 0 &&
270- now >= active_timeouts [0 ]-> fin_time )
282+ if (num_active_timeouts > 0 )
271283 {
272- timeout_params * this_timeout = active_timeouts [ 0 ] ;
284+ TimestampTz now = GetCurrentTimestamp () ;
273285
274- /* Remove it from the active list */
275- remove_timeout_index (0 );
286+ /* While the first pending timeout has been reached ... */
287+ while (num_active_timeouts > 0 &&
288+ now >= active_timeouts [0 ]-> fin_time )
289+ {
290+ timeout_params * this_timeout = active_timeouts [0 ];
276291
277- /* Mark it as fired */
278- this_timeout -> indicator = true ;
292+ /* Remove it from the active list */
293+ remove_timeout_index ( 0 ) ;
279294
280- /* And call its handler function */
281- ( * this_timeout -> timeout_handler ) () ;
295+ /* Mark it as fired */
296+ this_timeout -> indicator = true ;
282297
283- /*
284- * The handler might not take negligible time (CheckDeadLock for
285- * instance isn't too cheap), so let's update our idea of "now"
286- * after each one.
287- */
288- now = GetCurrentTimestamp ();
289- }
298+ /* And call its handler function */
299+ (* this_timeout -> timeout_handler ) ();
290300
291- /* Done firing timeouts, so reschedule next interrupt if any */
292- schedule_alarm (now );
301+ /*
302+ * The handler might not take negligible time (CheckDeadLock
303+ * for instance isn't too cheap), so let's update our idea of
304+ * "now" after each one.
305+ */
306+ now = GetCurrentTimestamp ();
307+ }
308+
309+ /* Done firing timeouts, so reschedule next interrupt if any */
310+ schedule_alarm (now );
311+ }
293312 }
294313
295314 errno = save_errno ;
@@ -315,6 +334,8 @@ InitializeTimeouts(void)
315334 int i ;
316335
317336 /* Initialize, or re-initialize, all local state */
337+ disable_alarm ();
338+
318339 num_active_timeouts = 0 ;
319340
320341 for (i = 0 ; i < MAX_TIMEOUTS ; i ++ )
@@ -345,6 +366,8 @@ RegisterTimeout(TimeoutId id, timeout_handler_proc handler)
345366{
346367 Assert (all_timeouts_initialized );
347368
369+ /* There's no need to disable the signal handler here. */
370+
348371 if (id >= USER_TIMEOUT )
349372 {
350373 /* Allocate a user-defined timeout reason */
@@ -376,7 +399,7 @@ enable_timeout_after(TimeoutId id, int delay_ms)
376399 TimestampTz fin_time ;
377400
378401 /* Disable timeout interrupts for safety. */
379- disable_alarm (false );
402+ disable_alarm ();
380403
381404 /* Queue the timeout at the appropriate time. */
382405 now = GetCurrentTimestamp ();
@@ -400,7 +423,7 @@ enable_timeout_at(TimeoutId id, TimestampTz fin_time)
400423 TimestampTz now ;
401424
402425 /* Disable timeout interrupts for safety. */
403- disable_alarm (false );
426+ disable_alarm ();
404427
405428 /* Queue the timeout at the appropriate time. */
406429 now = GetCurrentTimestamp ();
@@ -424,7 +447,7 @@ enable_timeouts(const EnableTimeoutParams *timeouts, int count)
424447 int i ;
425448
426449 /* Disable timeout interrupts for safety. */
427- disable_alarm (count > 1 );
450+ disable_alarm ();
428451
429452 /* Queue the timeout(s) at the appropriate times. */
430453 now = GetCurrentTimestamp ();
@@ -476,7 +499,7 @@ disable_timeout(TimeoutId id, bool keep_indicator)
476499 Assert (all_timeouts [id ].timeout_handler != NULL );
477500
478501 /* Disable timeout interrupts for safety. */
479- disable_alarm (false );
502+ disable_alarm ();
480503
481504 /* Find the timeout and remove it from the active list. */
482505 i = find_active_timeout (id );
@@ -510,7 +533,7 @@ disable_timeouts(const DisableTimeoutParams *timeouts, int count)
510533 Assert (all_timeouts_initialized );
511534
512535 /* Disable timeout interrupts for safety. */
513- disable_alarm (false );
536+ disable_alarm ();
514537
515538 /* Cancel the timeout(s). */
516539 for (i = 0 ; i < count ; i ++ )
@@ -540,18 +563,28 @@ disable_timeouts(const DisableTimeoutParams *timeouts, int count)
540563void
541564disable_all_timeouts (bool keep_indicators )
542565{
543- struct itimerval timeval ;
544- int i ;
566+ disable_alarm ();
545567
546- /* Forcibly reset the timer, whether we think it's active or not */
547- MemSet (& timeval , 0 , sizeof (struct itimerval ));
548- if (setitimer (ITIMER_REAL , & timeval , NULL ) != 0 )
549- elog (FATAL , "could not disable SIGALRM timer: %m" );
568+ /*
569+ * Only bother to reset the timer if we think it's active. We could just
570+ * let the interrupt happen anyway, but it's probably a bit cheaper to do
571+ * setitimer() than to let the useless interrupt happen.
572+ */
573+ if (num_active_timeouts > 0 )
574+ {
575+ struct itimerval timeval ;
576+
577+ MemSet (& timeval , 0 , sizeof (struct itimerval ));
578+ if (setitimer (ITIMER_REAL , & timeval , NULL ) != 0 )
579+ elog (FATAL , "could not disable SIGALRM timer: %m" );
580+ }
550581
551582 num_active_timeouts = 0 ;
552583
553584 if (!keep_indicators )
554585 {
586+ int i ;
587+
555588 for (i = 0 ; i < MAX_TIMEOUTS ; i ++ )
556589 all_timeouts [i ].indicator = false;
557590 }
0 commit comments