Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * timeout.c
4 : : * Routines to multiplex SIGALRM interrupts for multiple timeout reasons.
5 : : *
6 : : * Portions Copyright (c) 1996-2024, 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 "miscadmin.h"
20 : : #include "storage/latch.h"
21 : : #include "utils/timeout.h"
22 : : #include "utils/timestamp.h"
23 : :
24 : :
25 : : /* Data about any one timeout reason */
26 : : typedef struct timeout_params
27 : : {
28 : : TimeoutId index; /* identifier of timeout reason */
29 : :
30 : : /* volatile because these may be changed from the signal handler */
31 : : volatile bool active; /* true if timeout is in active_timeouts[] */
32 : : volatile bool indicator; /* true if timeout has occurred */
33 : :
34 : : /* callback function for timeout, or NULL if timeout not registered */
35 : : timeout_handler_proc timeout_handler;
36 : :
37 : : TimestampTz start_time; /* time that timeout was last activated */
38 : : TimestampTz fin_time; /* time it is, or was last, due to fire */
39 : : int interval_in_ms; /* time between firings, or 0 if just once */
40 : : } timeout_params;
41 : :
42 : : /*
43 : : * List of possible timeout reasons in the order of enum TimeoutId.
44 : : */
45 : : static timeout_params all_timeouts[MAX_TIMEOUTS];
46 : : static bool all_timeouts_initialized = false;
47 : :
48 : : /*
49 : : * List of active timeouts ordered by their fin_time and priority.
50 : : * This list is subject to change by the interrupt handler, so it's volatile.
51 : : */
52 : : static volatile int num_active_timeouts = 0;
53 : : static timeout_params *volatile active_timeouts[MAX_TIMEOUTS];
54 : :
55 : : /*
56 : : * Flag controlling whether the signal handler is allowed to do anything.
57 : : * This is useful to avoid race conditions with the handler. Note in
58 : : * particular that this lets us make changes in the data structures without
59 : : * tediously disabling and re-enabling the timer signal. Most of the time,
60 : : * no interrupt would happen anyway during such critical sections, but if
61 : : * one does, this rule ensures it's safe. Leaving the signal enabled across
62 : : * multiple operations can greatly reduce the number of kernel calls we make,
63 : : * too. See comments in schedule_alarm() about that.
64 : : *
65 : : * We leave this "false" when we're not expecting interrupts, just in case.
66 : : */
67 : : static volatile sig_atomic_t alarm_enabled = false;
68 : :
69 : : #define disable_alarm() (alarm_enabled = false)
70 : : #define enable_alarm() (alarm_enabled = true)
71 : :
72 : : /*
73 : : * State recording if and when we next expect the interrupt to fire.
74 : : * (signal_due_at is valid only when signal_pending is true.)
75 : : * Note that the signal handler will unconditionally reset signal_pending to
76 : : * false, so that can change asynchronously even when alarm_enabled is false.
77 : : */
78 : : static volatile sig_atomic_t signal_pending = false;
79 : : static volatile TimestampTz signal_due_at = 0;
80 : :
81 : :
82 : : /*****************************************************************************
83 : : * Internal helper functions
84 : : *
85 : : * For all of these, it is caller's responsibility to protect them from
86 : : * interruption by the signal handler. Generally, call disable_alarm()
87 : : * first to prevent interruption, then update state, and last call
88 : : * schedule_alarm(), which will re-enable the signal handler if needed.
89 : : *****************************************************************************/
90 : :
91 : : /*
92 : : * Find the index of a given timeout reason in the active array.
93 : : * If it's not there, return -1.
94 : : */
95 : : static int
4290 alvherre@alvh.no-ip. 96 :CBC 25531 : find_active_timeout(TimeoutId id)
97 : : {
98 : : int i;
99 : :
100 [ + - ]: 25660 : for (i = 0; i < num_active_timeouts; i++)
101 : : {
102 [ + + ]: 25660 : if (active_timeouts[i]->index == id)
103 : 25531 : return i;
104 : : }
105 : :
4290 alvherre@alvh.no-ip. 106 :UBC 0 : return -1;
107 : : }
108 : :
109 : : /*
110 : : * Insert specified timeout reason into the list of active timeouts
111 : : * at the given index.
112 : : */
113 : : static void
4290 alvherre@alvh.no-ip. 114 :CBC 56142 : insert_timeout(TimeoutId id, int index)
115 : : {
116 : : int i;
117 : :
118 [ + - - + ]: 56142 : if (index < 0 || index > num_active_timeouts)
4290 alvherre@alvh.no-ip. 119 [ # # ]:UBC 0 : elog(FATAL, "timeout index %d out of range 0..%d", index,
120 : : num_active_timeouts);
121 : :
1633 tgl@sss.pgh.pa.us 122 [ - + ]:CBC 56142 : Assert(!all_timeouts[id].active);
123 : 56142 : all_timeouts[id].active = true;
124 : :
4290 alvherre@alvh.no-ip. 125 [ + + ]: 57093 : for (i = num_active_timeouts - 1; i >= index; i--)
126 : 951 : active_timeouts[i + 1] = active_timeouts[i];
127 : :
128 : 56142 : active_timeouts[index] = &all_timeouts[id];
129 : :
130 : 56142 : num_active_timeouts++;
131 : 56142 : }
132 : :
133 : : /*
134 : : * Remove the index'th element from the timeout list.
135 : : */
136 : : static void
137 : 25635 : remove_timeout_index(int index)
138 : : {
139 : : int i;
140 : :
141 [ + - - + ]: 25635 : if (index < 0 || index >= num_active_timeouts)
4290 alvherre@alvh.no-ip. 142 [ # # ]:UBC 0 : elog(FATAL, "timeout index %d out of range 0..%d", index,
143 : : num_active_timeouts - 1);
144 : :
1633 tgl@sss.pgh.pa.us 145 [ - + ]:CBC 25635 : Assert(active_timeouts[index]->active);
146 : 25635 : active_timeouts[index]->active = false;
147 : :
4290 alvherre@alvh.no-ip. 148 [ + + ]: 26697 : for (i = index + 1; i < num_active_timeouts; i++)
149 : 1062 : active_timeouts[i - 1] = active_timeouts[i];
150 : :
151 : 25635 : num_active_timeouts--;
152 : 25635 : }
153 : :
154 : : /*
155 : : * Enable the specified timeout reason
156 : : */
157 : : static void
927 rhaas@postgresql.org 158 : 56142 : enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time,
159 : : int interval_in_ms)
160 : : {
161 : : int i;
162 : :
163 : : /* Assert request is sane */
4047 tgl@sss.pgh.pa.us 164 [ - + ]: 56142 : Assert(all_timeouts_initialized);
165 [ - + ]: 56142 : Assert(all_timeouts[id].timeout_handler != NULL);
166 : :
167 : : /*
168 : : * If this timeout was already active, momentarily disable it. We
169 : : * interpret the call as a directive to reschedule the timeout.
170 : : */
1633 171 [ - + ]: 56142 : if (all_timeouts[id].active)
1633 tgl@sss.pgh.pa.us 172 :UBC 0 : remove_timeout_index(find_active_timeout(id));
173 : :
174 : : /*
175 : : * Find out the index where to insert the new timeout. We sort by
176 : : * fin_time, and for equal fin_time by priority.
177 : : */
4047 tgl@sss.pgh.pa.us 178 [ + + ]:CBC 56392 : for (i = 0; i < num_active_timeouts; i++)
179 : : {
180 : 1191 : timeout_params *old_timeout = active_timeouts[i];
181 : :
182 [ + + ]: 1191 : if (fin_time < old_timeout->fin_time)
183 : 941 : break;
184 [ - + - - ]: 250 : if (fin_time == old_timeout->fin_time && id < old_timeout->index)
4047 tgl@sss.pgh.pa.us 185 :UBC 0 : break;
186 : : }
187 : :
188 : : /*
189 : : * Mark the timeout active, and insert it into the active list.
190 : : */
4047 tgl@sss.pgh.pa.us 191 :CBC 56142 : all_timeouts[id].indicator = false;
192 : 56142 : all_timeouts[id].start_time = now;
193 : 56142 : all_timeouts[id].fin_time = fin_time;
927 rhaas@postgresql.org 194 : 56142 : all_timeouts[id].interval_in_ms = interval_in_ms;
195 : :
4047 tgl@sss.pgh.pa.us 196 : 56142 : insert_timeout(id, i);
197 : 56142 : }
198 : :
199 : : /*
200 : : * Schedule alarm for the next active timeout, if any
201 : : *
202 : : * We assume the caller has obtained the current time, or a close-enough
203 : : * approximation. (It's okay if a tick or two has passed since "now", or
204 : : * if a little more time elapses before we reach the kernel call; that will
205 : : * cause us to ask for an interrupt a tick or two later than the nearest
206 : : * timeout, which is no big deal. Passing a "now" value that's in the future
207 : : * would be bad though.)
208 : : */
209 : : static void
4290 alvherre@alvh.no-ip. 210 : 63748 : schedule_alarm(TimestampTz now)
211 : : {
212 [ + + ]: 63748 : if (num_active_timeouts > 0)
213 : : {
214 : : struct itimerval timeval;
215 : : TimestampTz nearest_timeout;
216 : : long secs;
217 : : int usecs;
218 : :
219 [ + - + - : 318545 : MemSet(&timeval, 0, sizeof(struct itimerval));
+ - + - +
+ ]
220 : :
221 : : /*
222 : : * If we think there's a signal pending, but current time is more than
223 : : * 10ms past when the signal was due, then assume that the timeout
224 : : * request got lost somehow; clear signal_pending so that we'll reset
225 : : * the interrupt request below. (10ms corresponds to the worst-case
226 : : * timeout granularity on modern systems.) It won't hurt us if the
227 : : * interrupt does manage to fire between now and when we reach the
228 : : * setitimer() call.
229 : : */
794 tgl@sss.pgh.pa.us 230 [ + + - + ]: 63709 : if (signal_pending && now > signal_due_at + 10 * 1000)
794 tgl@sss.pgh.pa.us 231 :UBC 0 : signal_pending = false;
232 : :
233 : : /*
234 : : * Get the time remaining till the nearest pending timeout. If it is
235 : : * negative, assume that we somehow missed an interrupt, and clear
236 : : * signal_pending. This gives us another chance to recover if the
237 : : * kernel drops a timeout request for some reason.
238 : : */
1194 tgl@sss.pgh.pa.us 239 :CBC 63709 : nearest_timeout = active_timeouts[0]->fin_time;
240 [ - + ]: 63709 : if (now > nearest_timeout)
241 : : {
1194 tgl@sss.pgh.pa.us 242 :UBC 0 : signal_pending = false;
243 : : /* force an interrupt as soon as possible */
244 : 0 : secs = 0;
4290 alvherre@alvh.no-ip. 245 : 0 : usecs = 1;
246 : : }
247 : : else
248 : : {
1194 tgl@sss.pgh.pa.us 249 :CBC 63709 : TimestampDifference(now, nearest_timeout,
250 : : &secs, &usecs);
251 : :
252 : : /*
253 : : * It's possible that the difference is less than a microsecond;
254 : : * ensure we don't cancel, rather than set, the interrupt.
255 : : */
256 [ + + - + ]: 63709 : if (secs == 0 && usecs == 0)
1194 tgl@sss.pgh.pa.us 257 :UBC 0 : usecs = 1;
258 : : }
259 : :
4290 alvherre@alvh.no-ip. 260 :CBC 63709 : timeval.it_value.tv_sec = secs;
261 : 63709 : timeval.it_value.tv_usec = usecs;
262 : :
263 : : /*
264 : : * We must enable the signal handler before calling setitimer(); if we
265 : : * did it in the other order, we'd have a race condition wherein the
266 : : * interrupt could occur before we can set alarm_enabled, so that the
267 : : * signal handler would fail to do anything.
268 : : *
269 : : * Because we didn't bother to disable the timer in disable_alarm(),
270 : : * it's possible that a previously-set interrupt will fire between
271 : : * enable_alarm() and setitimer(). This is safe, however. There are
272 : : * two possible outcomes:
273 : : *
274 : : * 1. The signal handler finds nothing to do (because the nearest
275 : : * timeout event is still in the future). It will re-set the timer
276 : : * and return. Then we'll overwrite the timer value with a new one.
277 : : * This will mean that the timer fires a little later than we
278 : : * intended, but only by the amount of time it takes for the signal
279 : : * handler to do nothing useful, which shouldn't be much.
280 : : *
281 : : * 2. The signal handler executes and removes one or more timeout
282 : : * events. When it returns, either the queue is now empty or the
283 : : * frontmost event is later than the one we looked at above. So we'll
284 : : * overwrite the timer value with one that is too soon (plus or minus
285 : : * the signal handler's execution time), causing a useless interrupt
286 : : * to occur. But the handler will then re-set the timer and
287 : : * everything will still work as expected.
288 : : *
289 : : * Since these cases are of very low probability (the window here
290 : : * being quite narrow), it's not worth adding cycles to the mainline
291 : : * code to prevent occasional wasted interrupts.
292 : : */
4046 tgl@sss.pgh.pa.us 293 : 63709 : enable_alarm();
294 : :
295 : : /*
296 : : * If there is already an interrupt pending that's at or before the
297 : : * needed time, we need not do anything more. The signal handler will
298 : : * do the right thing in the first case, and re-schedule the interrupt
299 : : * for later in the second case. It might seem that the extra
300 : : * interrupt is wasted work, but it's not terribly much work, and this
301 : : * method has very significant advantages in the common use-case where
302 : : * we repeatedly set a timeout that we don't expect to reach and then
303 : : * cancel it. Instead of invoking setitimer() every time the timeout
304 : : * is set or canceled, we perform one interrupt and a re-scheduling
305 : : * setitimer() call at intervals roughly equal to the timeout delay.
306 : : * For example, with statement_timeout = 1s and a throughput of
307 : : * thousands of queries per second, this method requires an interrupt
308 : : * and setitimer() call roughly once a second, rather than thousands
309 : : * of setitimer() calls per second.
310 : : *
311 : : * Because of the possible passage of time between when we obtained
312 : : * "now" and when we reach setitimer(), the kernel's opinion of when
313 : : * to trigger the interrupt is likely to be a bit later than
314 : : * signal_due_at. That's fine, for the same reasons described above.
315 : : */
1194 316 [ + + + + ]: 63709 : if (signal_pending && nearest_timeout >= signal_due_at)
317 : 41003 : return;
318 : :
319 : : /*
320 : : * As with calling enable_alarm(), we must set signal_pending *before*
321 : : * calling setitimer(); if we did it after, the signal handler could
322 : : * trigger before we set it, leaving us with a false opinion that a
323 : : * signal is still coming.
324 : : *
325 : : * Other race conditions involved with setting/checking signal_pending
326 : : * are okay, for the reasons described above. One additional point is
327 : : * that the signal handler could fire after we set signal_due_at, but
328 : : * still before the setitimer() call. Then the handler could
329 : : * overwrite signal_due_at with a value it computes, which will be the
330 : : * same as or perhaps later than what we just computed. After we
331 : : * perform setitimer(), the net effect would be that signal_due_at
332 : : * gives a time later than when the interrupt will really happen;
333 : : * which is a safe situation.
334 : : */
335 : 22706 : signal_due_at = nearest_timeout;
336 : 22706 : signal_pending = true;
337 : :
338 : : /* Set the alarm timer */
4290 alvherre@alvh.no-ip. 339 [ - + ]: 22706 : if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
340 : : {
341 : : /*
342 : : * Clearing signal_pending here is a bit pro forma, but not
343 : : * entirely so, since something in the FATAL exit path could try
344 : : * to use timeout facilities.
345 : : */
1194 tgl@sss.pgh.pa.us 346 :UBC 0 : signal_pending = false;
4290 alvherre@alvh.no-ip. 347 [ # # ]: 0 : elog(FATAL, "could not enable SIGALRM timer: %m");
348 : : }
349 : : }
350 : : }
351 : :
352 : :
353 : : /*****************************************************************************
354 : : * Signal handler
355 : : *****************************************************************************/
356 : :
357 : : /*
358 : : * Signal handler for SIGALRM
359 : : *
360 : : * Process any active timeout reasons and then reschedule the interrupt
361 : : * as needed.
362 : : */
363 : : static void
4290 alvherre@alvh.no-ip. 364 :CBC 273 : handle_sig_alarm(SIGNAL_ARGS)
365 : : {
366 : : /*
367 : : * Bump the holdoff counter, to make sure nothing we call will process
368 : : * interrupts directly. No timeout handler should do that, but these
369 : : * failures are hard to debug, so better be sure.
370 : : */
3789 tgl@sss.pgh.pa.us 371 : 273 : HOLD_INTERRUPTS();
372 : :
373 : : /*
374 : : * SIGALRM is always cause for waking anything waiting on the process
375 : : * latch.
376 : : */
3378 andres@anarazel.de 377 : 273 : SetLatch(MyLatch);
378 : :
379 : : /*
380 : : * Always reset signal_pending, even if !alarm_enabled, since indeed no
381 : : * signal is now pending.
382 : : */
1194 tgl@sss.pgh.pa.us 383 : 273 : signal_pending = false;
384 : :
385 : : /*
386 : : * Fire any pending timeouts, but only if we're enabled to do so.
387 : : */
4046 388 [ + + ]: 273 : if (alarm_enabled)
389 : : {
390 : : /*
391 : : * Disable alarms, just in case this platform allows signal handlers
392 : : * to interrupt themselves. schedule_alarm() will re-enable if
393 : : * appropriate.
394 : : */
395 : 220 : disable_alarm();
396 : :
397 [ + - ]: 220 : if (num_active_timeouts > 0)
398 : : {
399 : 220 : TimestampTz now = GetCurrentTimestamp();
400 : :
401 : : /* While the first pending timeout has been reached ... */
402 [ + + ]: 324 : while (num_active_timeouts > 0 &&
403 [ + + ]: 285 : now >= active_timeouts[0]->fin_time)
404 : : {
405 : 104 : timeout_params *this_timeout = active_timeouts[0];
406 : :
407 : : /* Remove it from the active list */
408 : 104 : remove_timeout_index(0);
409 : :
410 : : /* Mark it as fired */
411 : 104 : this_timeout->indicator = true;
412 : :
413 : : /* And call its handler function */
2411 peter_e@gmx.net 414 : 104 : this_timeout->timeout_handler();
415 : :
416 : : /* If it should fire repeatedly, re-enable it. */
927 rhaas@postgresql.org 417 [ + + ]: 104 : if (this_timeout->interval_in_ms > 0)
418 : : {
419 : : TimestampTz new_fin_time;
420 : :
421 : : /*
422 : : * To guard against drift, schedule the next instance of
423 : : * the timeout based on the intended firing time rather
424 : : * than the actual firing time. But if the timeout was so
425 : : * late that we missed an entire cycle, fall back to
426 : : * scheduling based on the actual firing time.
427 : : */
428 : 26 : new_fin_time =
429 : 26 : TimestampTzPlusMilliseconds(this_timeout->fin_time,
430 : : this_timeout->interval_in_ms);
431 [ - + ]: 26 : if (new_fin_time < now)
927 rhaas@postgresql.org 432 :UBC 0 : new_fin_time =
433 : 0 : TimestampTzPlusMilliseconds(now,
434 : : this_timeout->interval_in_ms);
927 rhaas@postgresql.org 435 :CBC 26 : enable_timeout(this_timeout->index, now, new_fin_time,
436 : : this_timeout->interval_in_ms);
437 : : }
438 : :
439 : : /*
440 : : * The handler might not take negligible time (CheckDeadLock
441 : : * for instance isn't too cheap), so let's update our idea of
442 : : * "now" after each one.
443 : : */
4046 tgl@sss.pgh.pa.us 444 : 104 : now = GetCurrentTimestamp();
445 : : }
446 : :
447 : : /* Done firing timeouts, so reschedule next interrupt if any */
448 : 220 : schedule_alarm(now);
449 : : }
450 : : }
451 : :
3789 452 [ - + ]: 273 : RESUME_INTERRUPTS();
4290 alvherre@alvh.no-ip. 453 : 273 : }
454 : :
455 : :
456 : : /*****************************************************************************
457 : : * Public API
458 : : *****************************************************************************/
459 : :
460 : : /*
461 : : * Initialize timeout module.
462 : : *
463 : : * This must be called in every process that wants to use timeouts.
464 : : *
465 : : * If the process was forked from another one that was also using this
466 : : * module, be sure to call this before re-enabling signals; else handlers
467 : : * meant to run in the parent process might get invoked in this one.
468 : : */
469 : : void
470 : 28723 : InitializeTimeouts(void)
471 : : {
472 : : int i;
473 : :
474 : : /* Initialize, or re-initialize, all local state */
4046 tgl@sss.pgh.pa.us 475 : 28723 : disable_alarm();
476 : :
4290 alvherre@alvh.no-ip. 477 : 28723 : num_active_timeouts = 0;
478 : :
479 [ + + ]: 689352 : for (i = 0; i < MAX_TIMEOUTS; i++)
480 : : {
481 : 660629 : all_timeouts[i].index = i;
1633 tgl@sss.pgh.pa.us 482 : 660629 : all_timeouts[i].active = false;
4290 alvherre@alvh.no-ip. 483 : 660629 : all_timeouts[i].indicator = false;
484 : 660629 : all_timeouts[i].timeout_handler = NULL;
485 : 660629 : all_timeouts[i].start_time = 0;
486 : 660629 : all_timeouts[i].fin_time = 0;
927 rhaas@postgresql.org 487 : 660629 : all_timeouts[i].interval_in_ms = 0;
488 : : }
489 : :
4290 alvherre@alvh.no-ip. 490 : 28723 : all_timeouts_initialized = true;
491 : :
492 : : /* Now establish the signal handler */
493 : 28723 : pqsignal(SIGALRM, handle_sig_alarm);
494 : 28723 : }
495 : :
496 : : /*
497 : : * Register a timeout reason
498 : : *
499 : : * For predefined timeouts, this just registers the callback function.
500 : : *
501 : : * For user-defined timeouts, pass id == USER_TIMEOUT; we then allocate and
502 : : * return a timeout ID.
503 : : */
504 : : TimeoutId
4047 tgl@sss.pgh.pa.us 505 : 145930 : RegisterTimeout(TimeoutId id, timeout_handler_proc handler)
506 : : {
4290 alvherre@alvh.no-ip. 507 [ - + ]: 145930 : Assert(all_timeouts_initialized);
508 : :
509 : : /* There's no need to disable the signal handler here. */
510 : :
511 [ - + ]: 145930 : if (id >= USER_TIMEOUT)
512 : : {
513 : : /* Allocate a user-defined timeout reason */
4290 alvherre@alvh.no-ip. 514 [ # # ]:UBC 0 : for (id = USER_TIMEOUT; id < MAX_TIMEOUTS; id++)
515 [ # # ]: 0 : if (all_timeouts[id].timeout_handler == NULL)
516 : 0 : break;
517 [ # # ]: 0 : if (id >= MAX_TIMEOUTS)
518 [ # # ]: 0 : ereport(FATAL,
519 : : (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
520 : : errmsg("cannot add more timeout reasons")));
521 : : }
522 : :
4290 alvherre@alvh.no-ip. 523 [ - + ]:CBC 145930 : Assert(all_timeouts[id].timeout_handler == NULL);
524 : :
525 : 145930 : all_timeouts[id].timeout_handler = handler;
526 : :
527 : 145930 : return id;
528 : : }
529 : :
530 : : /*
531 : : * Reschedule any pending SIGALRM interrupt.
532 : : *
533 : : * This can be used during error recovery in case query cancel resulted in loss
534 : : * of a SIGALRM event (due to longjmp'ing out of handle_sig_alarm before it
535 : : * could do anything). But note it's not necessary if any of the public
536 : : * enable_ or disable_timeout functions are called in the same area, since
537 : : * those all do schedule_alarm() internally if needed.
538 : : */
539 : : void
3789 tgl@sss.pgh.pa.us 540 : 27410 : reschedule_timeouts(void)
541 : : {
542 : : /* For flexibility, allow this to be called before we're initialized. */
543 [ - + ]: 27410 : if (!all_timeouts_initialized)
3789 tgl@sss.pgh.pa.us 544 :UBC 0 : return;
545 : :
546 : : /* Disable timeout interrupts for safety. */
3789 tgl@sss.pgh.pa.us 547 :CBC 27410 : disable_alarm();
548 : :
549 : : /* Reschedule the interrupt, if any timeouts remain active. */
550 [ + + ]: 27410 : if (num_active_timeouts > 0)
551 : 6604 : schedule_alarm(GetCurrentTimestamp());
552 : : }
553 : :
554 : : /*
555 : : * Enable the specified timeout to fire after the specified delay.
556 : : *
557 : : * Delay is given in milliseconds.
558 : : */
559 : : void
4290 alvherre@alvh.no-ip. 560 : 55365 : enable_timeout_after(TimeoutId id, int delay_ms)
561 : : {
562 : : TimestampTz now;
563 : : TimestampTz fin_time;
564 : :
565 : : /* Disable timeout interrupts for safety. */
4046 tgl@sss.pgh.pa.us 566 : 55365 : disable_alarm();
567 : :
568 : : /* Queue the timeout at the appropriate time. */
4290 alvherre@alvh.no-ip. 569 : 55365 : now = GetCurrentTimestamp();
570 : 55365 : fin_time = TimestampTzPlusMilliseconds(now, delay_ms);
927 rhaas@postgresql.org 571 : 55365 : enable_timeout(id, now, fin_time, 0);
572 : :
573 : : /* Set the timer interrupt. */
574 : 55365 : schedule_alarm(now);
575 : 55365 : }
576 : :
577 : : /*
578 : : * Enable the specified timeout to fire periodically, with the specified
579 : : * delay as the time between firings.
580 : : *
581 : : * Delay is given in milliseconds.
582 : : */
583 : : void
584 : 495 : enable_timeout_every(TimeoutId id, TimestampTz fin_time, int delay_ms)
585 : : {
586 : : TimestampTz now;
587 : :
588 : : /* Disable timeout interrupts for safety. */
589 : 495 : disable_alarm();
590 : :
591 : : /* Queue the timeout at the appropriate time. */
592 : 495 : now = GetCurrentTimestamp();
593 : 495 : enable_timeout(id, now, fin_time, delay_ms);
594 : :
595 : : /* Set the timer interrupt. */
4047 tgl@sss.pgh.pa.us 596 : 495 : schedule_alarm(now);
4290 alvherre@alvh.no-ip. 597 : 495 : }
598 : :
599 : : /*
600 : : * Enable the specified timeout to fire at the specified time.
601 : : *
602 : : * This is provided to support cases where there's a reason to calculate
603 : : * the timeout by reference to some point other than "now". If there isn't,
604 : : * use enable_timeout_after(), to avoid calling GetCurrentTimestamp() twice.
605 : : */
606 : : void
4290 alvherre@alvh.no-ip. 607 :UBC 0 : enable_timeout_at(TimeoutId id, TimestampTz fin_time)
608 : : {
609 : : TimestampTz now;
610 : :
611 : : /* Disable timeout interrupts for safety. */
4046 tgl@sss.pgh.pa.us 612 : 0 : disable_alarm();
613 : :
614 : : /* Queue the timeout at the appropriate time. */
4047 615 : 0 : now = GetCurrentTimestamp();
927 rhaas@postgresql.org 616 : 0 : enable_timeout(id, now, fin_time, 0);
617 : :
618 : : /* Set the timer interrupt. */
4047 tgl@sss.pgh.pa.us 619 : 0 : schedule_alarm(now);
620 : 0 : }
621 : :
622 : : /*
623 : : * Enable multiple timeouts at once.
624 : : *
625 : : * This works like calling enable_timeout_after() and/or enable_timeout_at()
626 : : * multiple times. Use this to reduce the number of GetCurrentTimestamp()
627 : : * and setitimer() calls needed to establish multiple timeouts.
628 : : */
629 : : void
4047 tgl@sss.pgh.pa.us 630 :CBC 128 : enable_timeouts(const EnableTimeoutParams *timeouts, int count)
631 : : {
632 : : TimestampTz now;
633 : : int i;
634 : :
635 : : /* Disable timeout interrupts for safety. */
4046 636 : 128 : disable_alarm();
637 : :
638 : : /* Queue the timeout(s) at the appropriate times. */
4047 639 : 128 : now = GetCurrentTimestamp();
640 : :
641 [ + + ]: 384 : for (i = 0; i < count; i++)
642 : : {
643 : 256 : TimeoutId id = timeouts[i].id;
644 : : TimestampTz fin_time;
645 : :
646 [ + + - - ]: 256 : switch (timeouts[i].type)
647 : : {
648 : 243 : case TMPARAM_AFTER:
649 : 243 : fin_time = TimestampTzPlusMilliseconds(now,
650 : : timeouts[i].delay_ms);
927 rhaas@postgresql.org 651 : 243 : enable_timeout(id, now, fin_time, 0);
4047 tgl@sss.pgh.pa.us 652 : 243 : break;
653 : :
4047 tgl@sss.pgh.pa.us 654 :GBC 13 : case TMPARAM_AT:
927 rhaas@postgresql.org 655 : 13 : enable_timeout(id, now, timeouts[i].fin_time, 0);
656 : 13 : break;
657 : :
927 rhaas@postgresql.org 658 :UBC 0 : case TMPARAM_EVERY:
659 : 0 : fin_time = TimestampTzPlusMilliseconds(now,
660 : : timeouts[i].delay_ms);
661 : 0 : enable_timeout(id, now, fin_time, timeouts[i].delay_ms);
4047 tgl@sss.pgh.pa.us 662 : 0 : break;
663 : :
664 : 0 : default:
665 [ # # ]: 0 : elog(ERROR, "unrecognized timeout type %d",
666 : : (int) timeouts[i].type);
667 : : break;
668 : : }
669 : : }
670 : :
671 : : /* Set the timer interrupt. */
4047 tgl@sss.pgh.pa.us 672 :CBC 128 : schedule_alarm(now);
4290 alvherre@alvh.no-ip. 673 : 128 : }
674 : :
675 : : /*
676 : : * Cancel the specified timeout.
677 : : *
678 : : * The timeout's I've-been-fired indicator is reset,
679 : : * unless keep_indicator is true.
680 : : *
681 : : * When a timeout is canceled, any other active timeout remains in force.
682 : : * It's not an error to disable a timeout that is not enabled.
683 : : */
684 : : void
685 : 25679 : disable_timeout(TimeoutId id, bool keep_indicator)
686 : : {
687 : : /* Assert request is sane */
688 [ - + ]: 25679 : Assert(all_timeouts_initialized);
689 [ - + ]: 25679 : Assert(all_timeouts[id].timeout_handler != NULL);
690 : :
691 : : /* Disable timeout interrupts for safety. */
4046 tgl@sss.pgh.pa.us 692 : 25679 : disable_alarm();
693 : :
694 : : /* Find the timeout and remove it from the active list. */
1633 695 [ + + ]: 25679 : if (all_timeouts[id].active)
696 : 25274 : remove_timeout_index(find_active_timeout(id));
697 : :
698 : : /* Mark it inactive, whether it was active or not. */
4290 alvherre@alvh.no-ip. 699 [ + - ]: 25679 : if (!keep_indicator)
700 : 25679 : all_timeouts[id].indicator = false;
701 : :
702 : : /* Reschedule the interrupt, if any timeouts remain active. */
4047 tgl@sss.pgh.pa.us 703 [ + + ]: 25679 : if (num_active_timeouts > 0)
704 : 797 : schedule_alarm(GetCurrentTimestamp());
705 : 25679 : }
706 : :
707 : : /*
708 : : * Cancel multiple timeouts at once.
709 : : *
710 : : * The timeouts' I've-been-fired indicators are reset,
711 : : * unless timeouts[i].keep_indicator is true.
712 : : *
713 : : * This works like calling disable_timeout() multiple times.
714 : : * Use this to reduce the number of GetCurrentTimestamp()
715 : : * and setitimer() calls needed to cancel multiple timeouts.
716 : : */
717 : : void
718 : 148 : disable_timeouts(const DisableTimeoutParams *timeouts, int count)
719 : : {
720 : : int i;
721 : :
722 [ - + ]: 148 : Assert(all_timeouts_initialized);
723 : :
724 : : /* Disable timeout interrupts for safety. */
4046 725 : 148 : disable_alarm();
726 : :
727 : : /* Cancel the timeout(s). */
4047 728 [ + + ]: 444 : for (i = 0; i < count; i++)
729 : : {
730 : 296 : TimeoutId id = timeouts[i].id;
731 : :
732 [ - + ]: 296 : Assert(all_timeouts[id].timeout_handler != NULL);
733 : :
1633 734 [ + + ]: 296 : if (all_timeouts[id].active)
735 : 257 : remove_timeout_index(find_active_timeout(id));
736 : :
4047 737 [ + + ]: 296 : if (!timeouts[i].keep_indicator)
738 : 148 : all_timeouts[id].indicator = false;
739 : : }
740 : :
741 : : /* Reschedule the interrupt, if any timeouts remain active. */
4290 alvherre@alvh.no-ip. 742 [ + + ]: 148 : if (num_active_timeouts > 0)
743 : 139 : schedule_alarm(GetCurrentTimestamp());
744 : 148 : }
745 : :
746 : : /*
747 : : * Disable the signal handler, remove all timeouts from the active list,
748 : : * and optionally reset their timeout indicators.
749 : : */
750 : : void
751 : 19993 : disable_all_timeouts(bool keep_indicators)
752 : : {
753 : : int i;
754 : :
4046 tgl@sss.pgh.pa.us 755 : 19993 : disable_alarm();
756 : :
757 : : /*
758 : : * We used to disable the timer interrupt here, but in common usage
759 : : * patterns it's cheaper to leave it enabled; that may save us from having
760 : : * to enable it again shortly. See comments in schedule_alarm().
761 : : */
762 : :
4290 alvherre@alvh.no-ip. 763 : 19993 : num_active_timeouts = 0;
764 : :
1633 tgl@sss.pgh.pa.us 765 [ + + ]: 479832 : for (i = 0; i < MAX_TIMEOUTS; i++)
766 : : {
767 : 459839 : all_timeouts[i].active = false;
768 [ + - ]: 459839 : if (!keep_indicators)
4290 alvherre@alvh.no-ip. 769 : 459839 : all_timeouts[i].indicator = false;
770 : : }
771 : 19993 : }
772 : :
773 : : /*
774 : : * Return true if the timeout is active (enabled and not yet fired)
775 : : *
776 : : * This is, of course, subject to race conditions, as the timeout could fire
777 : : * immediately after we look.
778 : : */
779 : : bool
1633 tgl@sss.pgh.pa.us 780 : 1448526 : get_timeout_active(TimeoutId id)
781 : : {
782 : 1448526 : return all_timeouts[id].active;
783 : : }
784 : :
785 : : /*
786 : : * Return the timeout's I've-been-fired indicator
787 : : *
788 : : * If reset_indicator is true, reset the indicator when returning true.
789 : : * To avoid missing timeouts due to race conditions, we are careful not to
790 : : * reset the indicator when returning false.
791 : : */
792 : : bool
4047 793 : 108 : get_timeout_indicator(TimeoutId id, bool reset_indicator)
794 : : {
795 [ + + ]: 108 : if (all_timeouts[id].indicator)
796 : : {
797 [ + - ]: 10 : if (reset_indicator)
798 : 10 : all_timeouts[id].indicator = false;
799 : 10 : return true;
800 : : }
801 : 98 : return false;
802 : : }
803 : :
804 : : /*
805 : : * Return the time when the timeout was most recently activated
806 : : *
807 : : * Note: will return 0 if timeout has never been activated in this process.
808 : : * However, we do *not* reset the start_time when a timeout occurs, so as
809 : : * not to create a race condition if SIGALRM fires just as some code is
810 : : * about to fetch the value.
811 : : */
812 : : TimestampTz
4290 alvherre@alvh.no-ip. 813 : 1121 : get_timeout_start_time(TimeoutId id)
814 : : {
815 : 1121 : return all_timeouts[id].start_time;
816 : : }
817 : :
818 : : /*
819 : : * Return the time when the timeout is, or most recently was, due to fire
820 : : *
821 : : * Note: will return 0 if timeout has never been activated in this process.
822 : : * However, we do *not* reset the fin_time when a timeout occurs, so as
823 : : * not to create a race condition if SIGALRM fires just as some code is
824 : : * about to fetch the value.
825 : : */
826 : : TimestampTz
2879 tgl@sss.pgh.pa.us 827 :UBC 0 : get_timeout_finish_time(TimeoutId id)
828 : : {
829 : 0 : return all_timeouts[id].fin_time;
830 : : }
|