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