]> granicus.if.org Git - postgresql/blob - src/backend/utils/misc/timeout.c
3c3e41eca91d13535554f1e285295f71c1468246
[postgresql] / src / backend / utils / misc / timeout.c
1 /*-------------------------------------------------------------------------
2  *
3  * timeout.c
4  *        Routines to multiplex SIGALRM interrupts for multiple timeout reasons.
5  *
6  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        src/backend/utils/misc/timeout.c
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16
17 #include <sys/time.h>
18
19 #include "storage/proc.h"
20 #include "utils/timeout.h"
21 #include "utils/timestamp.h"
22
23
24 /* Data about any one timeout reason */
25 typedef struct timeout_params
26 {
27         TimeoutId       index;                  /* identifier of timeout reason */
28
29         /* volatile because it may be changed from the signal handler */
30         volatile bool indicator;        /* true if timeout has occurred */
31
32         /* callback function for timeout, or NULL if timeout not registered */
33         timeout_handler_proc timeout_handler;
34
35         TimestampTz start_time;         /* time that timeout was last activated */
36         TimestampTz fin_time;           /* if active, time it is due to fire */
37 } timeout_params;
38
39 /*
40  * List of possible timeout reasons in the order of enum TimeoutId.
41  */
42 static timeout_params all_timeouts[MAX_TIMEOUTS];
43 static bool all_timeouts_initialized = false;
44
45 /*
46  * List of active timeouts ordered by their fin_time and priority.
47  * This list is subject to change by the interrupt handler, so it's volatile.
48  */
49 static volatile int num_active_timeouts = 0;
50 static timeout_params *volatile active_timeouts[MAX_TIMEOUTS];
51
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
66
67 /*****************************************************************************
68  * Internal helper functions
69  *
70  * For all of these, it is caller's responsibility to protect them from
71  * interruption by the signal handler.  Generally, call disable_alarm()
72  * first to prevent interruption, then update state, and last call
73  * schedule_alarm(), which will re-enable the signal handler if needed.
74  *****************************************************************************/
75
76 /*
77  * Find the index of a given timeout reason in the active array.
78  * If it's not there, return -1.
79  */
80 static int
81 find_active_timeout(TimeoutId id)
82 {
83         int                     i;
84
85         for (i = 0; i < num_active_timeouts; i++)
86         {
87                 if (active_timeouts[i]->index == id)
88                         return i;
89         }
90
91         return -1;
92 }
93
94 /*
95  * Insert specified timeout reason into the list of active timeouts
96  * at the given index.
97  */
98 static void
99 insert_timeout(TimeoutId id, int index)
100 {
101         int                     i;
102
103         if (index < 0 || index > num_active_timeouts)
104                 elog(FATAL, "timeout index %d out of range 0..%d", index,
105                          num_active_timeouts);
106
107         for (i = num_active_timeouts - 1; i >= index; i--)
108                 active_timeouts[i + 1] = active_timeouts[i];
109
110         active_timeouts[index] = &all_timeouts[id];
111
112         num_active_timeouts++;
113 }
114
115 /*
116  * Remove the index'th element from the timeout list.
117  */
118 static void
119 remove_timeout_index(int index)
120 {
121         int                     i;
122
123         if (index < 0 || index >= num_active_timeouts)
124                 elog(FATAL, "timeout index %d out of range 0..%d", index,
125                          num_active_timeouts - 1);
126
127         for (i = index + 1; i < num_active_timeouts; i++)
128                 active_timeouts[i - 1] = active_timeouts[i];
129
130         num_active_timeouts--;
131 }
132
133 /*
134  * Enable the specified timeout reason
135  */
136 static void
137 enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time)
138 {
139         int                     i;
140
141         /* Assert request is sane */
142         Assert(all_timeouts_initialized);
143         Assert(all_timeouts[id].timeout_handler != NULL);
144
145         /*
146          * If this timeout was already active, momentarily disable it.  We
147          * interpret the call as a directive to reschedule the timeout.
148          */
149         i = find_active_timeout(id);
150         if (i >= 0)
151                 remove_timeout_index(i);
152
153         /*
154          * Find out the index where to insert the new timeout.  We sort by
155          * fin_time, and for equal fin_time by priority.
156          */
157         for (i = 0; i < num_active_timeouts; i++)
158         {
159                 timeout_params *old_timeout = active_timeouts[i];
160
161                 if (fin_time < old_timeout->fin_time)
162                         break;
163                 if (fin_time == old_timeout->fin_time && id < old_timeout->index)
164                         break;
165         }
166
167         /*
168          * Mark the timeout active, and insert it into the active list.
169          */
170         all_timeouts[id].indicator = false;
171         all_timeouts[id].start_time = now;
172         all_timeouts[id].fin_time = fin_time;
173
174         insert_timeout(id, i);
175 }
176
177 /*
178  * Schedule alarm for the next active timeout, if any
179  *
180  * We assume the caller has obtained the current time, or a close-enough
181  * approximation.
182  */
183 static void
184 schedule_alarm(TimestampTz now)
185 {
186         if (num_active_timeouts > 0)
187         {
188                 struct itimerval timeval;
189                 long            secs;
190                 int                     usecs;
191
192                 MemSet(&timeval, 0, sizeof(struct itimerval));
193
194                 /* Get the time remaining till the nearest pending timeout */
195                 TimestampDifference(now, active_timeouts[0]->fin_time,
196                                                         &secs, &usecs);
197
198                 /*
199                  * It's possible that the difference is less than a microsecond;
200                  * ensure we don't cancel, rather than set, the interrupt.
201                  */
202                 if (secs == 0 && usecs == 0)
203                         usecs = 1;
204
205                 timeval.it_value.tv_sec = secs;
206                 timeval.it_value.tv_usec = usecs;
207
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
240                 /* Set the alarm timer */
241                 if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
242                         elog(FATAL, "could not enable SIGALRM timer: %m");
243         }
244 }
245
246
247 /*****************************************************************************
248  * Signal handler
249  *****************************************************************************/
250
251 /*
252  * Signal handler for SIGALRM
253  *
254  * Process any active timeout reasons and then reschedule the interrupt
255  * as needed.
256  */
257 static void
258 handle_sig_alarm(SIGNAL_ARGS)
259 {
260         int                     save_errno = errno;
261
262         /*
263          * SIGALRM is always cause for waking anything waiting on the process
264          * latch.  Cope with MyProc not being there, as the startup process also
265          * uses this signal handler.
266          */
267         if (MyProc)
268                 SetLatch(&MyProc->procLatch);
269
270         /*
271          * Fire any pending timeouts, but only if we're enabled to do so.
272          */
273         if (alarm_enabled)
274         {
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();
281
282                 if (num_active_timeouts > 0)
283                 {
284                         TimestampTz now = GetCurrentTimestamp();
285
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];
291
292                                 /* Remove it from the active list */
293                                 remove_timeout_index(0);
294
295                                 /* Mark it as fired */
296                                 this_timeout->indicator = true;
297
298                                 /* And call its handler function */
299                                 (*this_timeout->timeout_handler) ();
300
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                 }
312         }
313
314         errno = save_errno;
315 }
316
317
318 /*****************************************************************************
319  * Public API
320  *****************************************************************************/
321
322 /*
323  * Initialize timeout module.
324  *
325  * This must be called in every process that wants to use timeouts.
326  *
327  * If the process was forked from another one that was also using this
328  * module, be sure to call this before re-enabling signals; else handlers
329  * meant to run in the parent process might get invoked in this one.
330  */
331 void
332 InitializeTimeouts(void)
333 {
334         int                     i;
335
336         /* Initialize, or re-initialize, all local state */
337         disable_alarm();
338
339         num_active_timeouts = 0;
340
341         for (i = 0; i < MAX_TIMEOUTS; i++)
342         {
343                 all_timeouts[i].index = i;
344                 all_timeouts[i].indicator = false;
345                 all_timeouts[i].timeout_handler = NULL;
346                 all_timeouts[i].start_time = 0;
347                 all_timeouts[i].fin_time = 0;
348         }
349
350         all_timeouts_initialized = true;
351
352         /* Now establish the signal handler */
353         pqsignal(SIGALRM, handle_sig_alarm);
354 }
355
356 /*
357  * Register a timeout reason
358  *
359  * For predefined timeouts, this just registers the callback function.
360  *
361  * For user-defined timeouts, pass id == USER_TIMEOUT; we then allocate and
362  * return a timeout ID.
363  */
364 TimeoutId
365 RegisterTimeout(TimeoutId id, timeout_handler_proc handler)
366 {
367         Assert(all_timeouts_initialized);
368
369         /* There's no need to disable the signal handler here. */
370
371         if (id >= USER_TIMEOUT)
372         {
373                 /* Allocate a user-defined timeout reason */
374                 for (id = USER_TIMEOUT; id < MAX_TIMEOUTS; id++)
375                         if (all_timeouts[id].timeout_handler == NULL)
376                                 break;
377                 if (id >= MAX_TIMEOUTS)
378                         ereport(FATAL,
379                                         (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
380                                          errmsg("cannot add more timeout reasons")));
381         }
382
383         Assert(all_timeouts[id].timeout_handler == NULL);
384
385         all_timeouts[id].timeout_handler = handler;
386
387         return id;
388 }
389
390 /*
391  * Enable the specified timeout to fire after the specified delay.
392  *
393  * Delay is given in milliseconds.
394  */
395 void
396 enable_timeout_after(TimeoutId id, int delay_ms)
397 {
398         TimestampTz now;
399         TimestampTz fin_time;
400
401         /* Disable timeout interrupts for safety. */
402         disable_alarm();
403
404         /* Queue the timeout at the appropriate time. */
405         now = GetCurrentTimestamp();
406         fin_time = TimestampTzPlusMilliseconds(now, delay_ms);
407         enable_timeout(id, now, fin_time);
408
409         /* Set the timer interrupt. */
410         schedule_alarm(now);
411 }
412
413 /*
414  * Enable the specified timeout to fire at the specified time.
415  *
416  * This is provided to support cases where there's a reason to calculate
417  * the timeout by reference to some point other than "now".  If there isn't,
418  * use enable_timeout_after(), to avoid calling GetCurrentTimestamp() twice.
419  */
420 void
421 enable_timeout_at(TimeoutId id, TimestampTz fin_time)
422 {
423         TimestampTz now;
424
425         /* Disable timeout interrupts for safety. */
426         disable_alarm();
427
428         /* Queue the timeout at the appropriate time. */
429         now = GetCurrentTimestamp();
430         enable_timeout(id, now, fin_time);
431
432         /* Set the timer interrupt. */
433         schedule_alarm(now);
434 }
435
436 /*
437  * Enable multiple timeouts at once.
438  *
439  * This works like calling enable_timeout_after() and/or enable_timeout_at()
440  * multiple times.      Use this to reduce the number of GetCurrentTimestamp()
441  * and setitimer() calls needed to establish multiple timeouts.
442  */
443 void
444 enable_timeouts(const EnableTimeoutParams *timeouts, int count)
445 {
446         TimestampTz now;
447         int                     i;
448
449         /* Disable timeout interrupts for safety. */
450         disable_alarm();
451
452         /* Queue the timeout(s) at the appropriate times. */
453         now = GetCurrentTimestamp();
454
455         for (i = 0; i < count; i++)
456         {
457                 TimeoutId       id = timeouts[i].id;
458                 TimestampTz fin_time;
459
460                 switch (timeouts[i].type)
461                 {
462                         case TMPARAM_AFTER:
463                                 fin_time = TimestampTzPlusMilliseconds(now,
464                                                                                                            timeouts[i].delay_ms);
465                                 enable_timeout(id, now, fin_time);
466                                 break;
467
468                         case TMPARAM_AT:
469                                 enable_timeout(id, now, timeouts[i].fin_time);
470                                 break;
471
472                         default:
473                                 elog(ERROR, "unrecognized timeout type %d",
474                                          (int) timeouts[i].type);
475                                 break;
476                 }
477         }
478
479         /* Set the timer interrupt. */
480         schedule_alarm(now);
481 }
482
483 /*
484  * Cancel the specified timeout.
485  *
486  * The timeout's I've-been-fired indicator is reset,
487  * unless keep_indicator is true.
488  *
489  * When a timeout is canceled, any other active timeout remains in force.
490  * It's not an error to disable a timeout that is not enabled.
491  */
492 void
493 disable_timeout(TimeoutId id, bool keep_indicator)
494 {
495         int                     i;
496
497         /* Assert request is sane */
498         Assert(all_timeouts_initialized);
499         Assert(all_timeouts[id].timeout_handler != NULL);
500
501         /* Disable timeout interrupts for safety. */
502         disable_alarm();
503
504         /* Find the timeout and remove it from the active list. */
505         i = find_active_timeout(id);
506         if (i >= 0)
507                 remove_timeout_index(i);
508
509         /* Mark it inactive, whether it was active or not. */
510         if (!keep_indicator)
511                 all_timeouts[id].indicator = false;
512
513         /* Reschedule the interrupt, if any timeouts remain active. */
514         if (num_active_timeouts > 0)
515                 schedule_alarm(GetCurrentTimestamp());
516 }
517
518 /*
519  * Cancel multiple timeouts at once.
520  *
521  * The timeouts' I've-been-fired indicators are reset,
522  * unless timeouts[i].keep_indicator is true.
523  *
524  * This works like calling disable_timeout() multiple times.
525  * Use this to reduce the number of GetCurrentTimestamp()
526  * and setitimer() calls needed to cancel multiple timeouts.
527  */
528 void
529 disable_timeouts(const DisableTimeoutParams *timeouts, int count)
530 {
531         int                     i;
532
533         Assert(all_timeouts_initialized);
534
535         /* Disable timeout interrupts for safety. */
536         disable_alarm();
537
538         /* Cancel the timeout(s). */
539         for (i = 0; i < count; i++)
540         {
541                 TimeoutId       id = timeouts[i].id;
542                 int                     idx;
543
544                 Assert(all_timeouts[id].timeout_handler != NULL);
545
546                 idx = find_active_timeout(id);
547                 if (idx >= 0)
548                         remove_timeout_index(idx);
549
550                 if (!timeouts[i].keep_indicator)
551                         all_timeouts[id].indicator = false;
552         }
553
554         /* Reschedule the interrupt, if any timeouts remain active. */
555         if (num_active_timeouts > 0)
556                 schedule_alarm(GetCurrentTimestamp());
557 }
558
559 /*
560  * Disable SIGALRM and remove all timeouts from the active list,
561  * and optionally reset their timeout indicators.
562  */
563 void
564 disable_all_timeouts(bool keep_indicators)
565 {
566         disable_alarm();
567
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         }
581
582         num_active_timeouts = 0;
583
584         if (!keep_indicators)
585         {
586                 int                     i;
587
588                 for (i = 0; i < MAX_TIMEOUTS; i++)
589                         all_timeouts[i].indicator = false;
590         }
591 }
592
593 /*
594  * Return the timeout's I've-been-fired indicator
595  *
596  * If reset_indicator is true, reset the indicator when returning true.
597  * To avoid missing timeouts due to race conditions, we are careful not to
598  * reset the indicator when returning false.
599  */
600 bool
601 get_timeout_indicator(TimeoutId id, bool reset_indicator)
602 {
603         if (all_timeouts[id].indicator)
604         {
605                 if (reset_indicator)
606                         all_timeouts[id].indicator = false;
607                 return true;
608         }
609         return false;
610 }
611
612 /*
613  * Return the time when the timeout was most recently activated
614  *
615  * Note: will return 0 if timeout has never been activated in this process.
616  * However, we do *not* reset the start_time when a timeout occurs, so as
617  * not to create a race condition if SIGALRM fires just as some code is
618  * about to fetch the value.
619  */
620 TimestampTz
621 get_timeout_start_time(TimeoutId id)
622 {
623         return all_timeouts[id].start_time;
624 }