1 /* ==========================================================================
2 * timeout.c - Tickless hierarchical timing wheel.
3 * --------------------------------------------------------------------------
4 * Copyright (c) 2013, 2014 William Ahern
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to permit
11 * persons to whom the Software is furnished to do so, subject to the
12 * following conditions:
14 * The above copyright notice and this permission notice shall be included
15 * in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
20 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23 * USE OR OTHER DEALINGS IN THE SOFTWARE.
24 * ==========================================================================
29 #include <limits.h> /* CHAR_BIT */
31 #include <stddef.h> /* NULL */
32 #include <stdlib.h> /* malloc(3) free(3) */
33 #include <stdio.h> /* FILE fprintf(3) */
35 #include <inttypes.h> /* UINT64_C uint64_t */
37 #include <string.h> /* memset(3) */
39 #include <errno.h> /* errno */
41 #include "ext/tor_queue.h" /* TAILQ(3) */
43 #include "ext/timeouts/timeout.h"
46 #define TIMEOUT_DEBUG 0
50 #include "ext/timeouts/timeout-debug.h"
53 #ifdef TIMEOUT_DISABLE_RELATIVE_ACCESS
54 #define TO_SET_TIMEOUTS(to, T) ((void)0)
56 #define TO_SET_TIMEOUTS(to, T) ((to)->timeouts = (T))
60 * A N C I L L A R Y R O U T I N E S
62 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
64 #define abstime_t timeout_t /* for documentation purposes */
65 #define reltime_t timeout_t /* "" */
68 #define countof(a) (sizeof (a) / sizeof *(a))
72 #define endof(a) (&(a)[countof(a)])
76 #define MIN(a, b) (((a) < (b))? (a) : (b))
80 #define MAX(a, b) (((a) > (b))? (a) : (b))
83 #if !defined TOR_TAILQ_CONCAT
84 #define TOR_TAILQ_CONCAT(head1, head2, field) do { \
85 if (!TOR_TAILQ_EMPTY(head2)) { \
86 *(head1)->tqh_last = (head2)->tqh_first; \
87 (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
88 (head1)->tqh_last = (head2)->tqh_last; \
89 TOR_TAILQ_INIT((head2)); \
94 #if !defined TOR_TAILQ_FOREACH_SAFE
95 #define TOR_TAILQ_FOREACH_SAFE(var, head, field, tvar) \
96 for ((var) = TOR_TAILQ_FIRST(head); \
97 (var) && ((tvar) = TOR_TAILQ_NEXT(var, field), 1); \
103 * B I T M A N I P U L A T I O N R O U T I N E S
105 * The macros and routines below implement wheel parameterization. The
108 * WHEEL_BIT - The number of value bits mapped in each wheel. The
109 * lowest-order WHEEL_BIT bits index the lowest-order (highest
110 * resolution) wheel, the next group of WHEEL_BIT bits the
113 * WHEEL_NUM - The number of wheels. WHEEL_BIT * WHEEL_NUM = the number of
114 * value bits used by all the wheels. For the default of 6 and
115 * 4, only the low 24 bits are processed. Any timeout value
116 * larger than this will cycle through again.
118 * The implementation uses bit fields to remember which slot in each wheel
119 * is populated, and to generate masks of expiring slots according to the
120 * current update interval (i.e. the "tickless" aspect). The slots to
121 * process in a wheel are (populated-set & interval-mask).
123 * WHEEL_BIT cannot be larger than 6 bits because 2^6 -> 64 is the largest
124 * number of slots which can be tracked in a uint64_t integer bit field.
125 * WHEEL_BIT cannot be smaller than 3 bits because of our rotr and rotl
126 * routines, which only operate on all the value bits in an integer, and
127 * there's no integer smaller than uint8_t.
129 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
131 #if !defined WHEEL_BIT
135 #if !defined WHEEL_NUM
139 #define WHEEL_LEN (1U << WHEEL_BIT)
140 #define WHEEL_MAX (WHEEL_LEN - 1)
141 #define WHEEL_MASK (WHEEL_LEN - 1)
142 #define TIMEOUT_MAX ((TIMEOUT_C(1) << (WHEEL_BIT * WHEEL_NUM)) - 1)
144 #include "ext/timeouts/timeout-bitops.c"
147 #define ctz(n) ctz64(n)
148 #define clz(n) clz64(n)
149 #define fls(n) ((int)(64 - clz64(n)))
151 #define ctz(n) ctz32(n)
152 #define clz(n) clz32(n)
153 #define fls(n) ((int)(32 - clz32((uint32_t)n)))
157 #define WHEEL_C(n) UINT64_C(n)
158 #define WHEEL_PRIu PRIu64
159 #define WHEEL_PRIx PRIx64
161 typedef uint64_t wheel_t
;
165 #define WHEEL_C(n) UINT32_C(n)
166 #define WHEEL_PRIu PRIu32
167 #define WHEEL_PRIx PRIx32
169 typedef uint32_t wheel_t
;
173 #define WHEEL_C(n) UINT16_C(n)
174 #define WHEEL_PRIu PRIu16
175 #define WHEEL_PRIx PRIx16
177 typedef uint16_t wheel_t
;
181 #define WHEEL_C(n) UINT8_C(n)
182 #define WHEEL_PRIu PRIu8
183 #define WHEEL_PRIx PRIx8
185 typedef uint8_t wheel_t
;
188 #error invalid WHEEL_BIT value
192 static inline wheel_t
rotl(const wheel_t v
, int c
) {
193 if (!(c
&= (sizeof v
* CHAR_BIT
- 1)))
196 return (v
<< c
) | (v
>> (sizeof v
* CHAR_BIT
- c
));
200 static inline wheel_t
rotr(const wheel_t v
, int c
) {
201 if (!(c
&= (sizeof v
* CHAR_BIT
- 1)))
204 return (v
>> c
) | (v
<< (sizeof v
* CHAR_BIT
- c
));
209 * T I M E R R O U T I N E S
211 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
213 TOR_TAILQ_HEAD(timeout_list
, timeout
);
216 struct timeout_list wheel
[WHEEL_NUM
][WHEEL_LEN
], expired
;
218 wheel_t pending
[WHEEL_NUM
];
222 }; /* struct timeouts */
225 static struct timeouts
*timeouts_init(struct timeouts
*T
, timeout_t hz
) {
228 for (i
= 0; i
< countof(T
->wheel
); i
++) {
229 for (j
= 0; j
< countof(T
->wheel
[i
]); j
++) {
230 TOR_TAILQ_INIT(&T
->wheel
[i
][j
]);
234 TOR_TAILQ_INIT(&T
->expired
);
236 for (i
= 0; i
< countof(T
->pending
); i
++) {
241 T
->hertz
= (hz
)? hz
: TIMEOUT_mHZ
;
244 } /* timeouts_init() */
247 TIMEOUT_PUBLIC
struct timeouts
*timeouts_open(timeout_t hz
, int *error
) {
250 if ((T
= malloc(sizeof *T
)))
251 return timeouts_init(T
, hz
);
256 } /* timeouts_open() */
259 static void timeouts_reset(struct timeouts
*T
) {
260 struct timeout_list reset
;
264 TOR_TAILQ_INIT(&reset
);
266 for (i
= 0; i
< countof(T
->wheel
); i
++) {
267 for (j
= 0; j
< countof(T
->wheel
[i
]); j
++) {
268 TOR_TAILQ_CONCAT(&reset
, &T
->wheel
[i
][j
], tqe
);
272 TOR_TAILQ_CONCAT(&reset
, &T
->expired
, tqe
);
274 TOR_TAILQ_FOREACH(to
, &reset
, tqe
) {
276 TO_SET_TIMEOUTS(to
, NULL
);
278 } /* timeouts_reset() */
281 TIMEOUT_PUBLIC
void timeouts_close(struct timeouts
*T
) {
283 * NOTE: Delete installed timeouts so timeout_pending() and
284 * timeout_expired() worked as expected.
289 } /* timeouts_close() */
292 TIMEOUT_PUBLIC timeout_t
timeouts_hz(struct timeouts
*T
) {
294 } /* timeouts_hz() */
297 TIMEOUT_PUBLIC
void timeouts_del(struct timeouts
*T
, struct timeout
*to
) {
299 TOR_TAILQ_REMOVE(to
->pending
, to
, tqe
);
301 if (to
->pending
!= &T
->expired
&& TOR_TAILQ_EMPTY(to
->pending
)) {
302 ptrdiff_t index_
= to
->pending
- &T
->wheel
[0][0];
303 int wheel
= (int) (index_
/ WHEEL_LEN
);
304 int slot
= index_
% WHEEL_LEN
;
306 T
->pending
[wheel
] &= ~(WHEEL_C(1) << slot
);
310 TO_SET_TIMEOUTS(to
, NULL
);
312 } /* timeouts_del() */
315 static inline reltime_t
timeout_rem(struct timeouts
*T
, struct timeout
*to
) {
316 return to
->expires
- T
->curtime
;
317 } /* timeout_rem() */
320 static inline int timeout_wheel(timeout_t timeout
) {
321 /* must be called with timeout != 0, so fls input is nonzero */
322 return (fls(MIN(timeout
, TIMEOUT_MAX
)) - 1) / WHEEL_BIT
;
323 } /* timeout_wheel() */
326 static inline int timeout_slot(int wheel
, timeout_t expires
) {
327 return WHEEL_MASK
& ((expires
>> (wheel
* WHEEL_BIT
)) - !!wheel
);
328 } /* timeout_slot() */
331 static void timeouts_sched(struct timeouts
*T
, struct timeout
*to
, timeout_t expires
) {
337 to
->expires
= expires
;
339 TO_SET_TIMEOUTS(to
, T
);
341 if (expires
> T
->curtime
) {
342 rem
= timeout_rem(T
, to
);
344 /* rem is nonzero since:
345 * rem == timeout_rem(T,to),
346 * == to->expires - T->curtime
347 * and above we have expires > T->curtime.
349 wheel
= timeout_wheel(rem
);
350 slot
= timeout_slot(wheel
, to
->expires
);
352 to
->pending
= &T
->wheel
[wheel
][slot
];
353 TOR_TAILQ_INSERT_TAIL(to
->pending
, to
, tqe
);
355 T
->pending
[wheel
] |= WHEEL_C(1) << slot
;
357 to
->pending
= &T
->expired
;
358 TOR_TAILQ_INSERT_TAIL(to
->pending
, to
, tqe
);
360 } /* timeouts_sched() */
363 #ifndef TIMEOUT_DISABLE_INTERVALS
364 static void timeouts_readd(struct timeouts
*T
, struct timeout
*to
) {
365 to
->expires
+= to
->interval
;
367 if (to
->expires
<= T
->curtime
) {
368 /* If we've missed the next firing of this timeout, reschedule
369 * it to occur at the next multiple of its interval after
370 * the last time that it fired.
372 timeout_t n
= T
->curtime
- to
->expires
;
373 timeout_t r
= n
% to
->interval
;
374 to
->expires
= T
->curtime
+ (to
->interval
- r
);
377 timeouts_sched(T
, to
, to
->expires
);
378 } /* timeouts_readd() */
382 TIMEOUT_PUBLIC
void timeouts_add(struct timeouts
*T
, struct timeout
*to
, timeout_t timeout
) {
383 #ifndef TIMEOUT_DISABLE_INTERVALS
384 if (to
->flags
& TIMEOUT_INT
)
385 to
->interval
= MAX(1, timeout
);
388 if (to
->flags
& TIMEOUT_ABS
)
389 timeouts_sched(T
, to
, timeout
);
391 timeouts_sched(T
, to
, T
->curtime
+ timeout
);
392 } /* timeouts_add() */
395 TIMEOUT_PUBLIC
void timeouts_update(struct timeouts
*T
, abstime_t curtime
) {
396 timeout_t elapsed
= curtime
- T
->curtime
;
397 struct timeout_list todo
;
400 TOR_TAILQ_INIT(&todo
);
403 * There's no avoiding looping over every wheel. It's best to keep
404 * WHEEL_NUM smallish.
406 for (wheel
= 0; wheel
< WHEEL_NUM
; wheel
++) {
410 * Calculate the slots expiring in this wheel
412 * If the elapsed time is greater than the maximum period of
413 * the wheel, mark every position as expiring.
415 * Otherwise, to determine the expired slots fill in all the
416 * bits between the last slot processed and the current
417 * slot, inclusive of the last slot. We'll bitwise-AND this
418 * with our pending set below.
420 * If a wheel rolls over, force a tick of the next higher
423 if ((elapsed
>> (wheel
* WHEEL_BIT
)) > WHEEL_MAX
) {
424 pending
= (wheel_t
)~WHEEL_C(0);
426 wheel_t _elapsed
= WHEEL_MASK
& (elapsed
>> (wheel
* WHEEL_BIT
));
430 * TODO: It's likely that at least one of the
431 * following three bit fill operations is redundant
432 * or can be replaced with a simpler operation.
434 oslot
= WHEEL_MASK
& (T
->curtime
>> (wheel
* WHEEL_BIT
));
435 pending
= rotl(((WHEEL_C(1) << _elapsed
) - 1), oslot
);
437 nslot
= WHEEL_MASK
& (curtime
>> (wheel
* WHEEL_BIT
));
438 pending
|= rotr(rotl(((WHEEL_C(1) << _elapsed
) - 1), nslot
), (int)_elapsed
);
439 pending
|= WHEEL_C(1) << nslot
;
442 while (pending
& T
->pending
[wheel
]) {
443 /* ctz input cannot be zero: loop condition. */
444 int slot
= ctz(pending
& T
->pending
[wheel
]);
445 TOR_TAILQ_CONCAT(&todo
, &T
->wheel
[wheel
][slot
], tqe
);
446 T
->pending
[wheel
] &= ~(UINT64_C(1) << slot
);
449 if (!(0x1 & pending
))
450 break; /* break if we didn't wrap around end of wheel */
452 /* if we're continuing, the next wheel must tick at least once */
453 elapsed
= MAX(elapsed
, (WHEEL_LEN
<< (wheel
* WHEEL_BIT
)));
456 T
->curtime
= curtime
;
458 while (!TOR_TAILQ_EMPTY(&todo
)) {
459 struct timeout
*to
= TOR_TAILQ_FIRST(&todo
);
461 TOR_TAILQ_REMOVE(&todo
, to
, tqe
);
464 timeouts_sched(T
, to
, to
->expires
);
468 } /* timeouts_update() */
470 TIMEOUT_PUBLIC timeout_t
timeouts_get_curtime(struct timeouts
*T
) {
472 } /* timeouts_get_curtime() */
474 TIMEOUT_PUBLIC
void timeouts_step(struct timeouts
*T
, reltime_t elapsed
) {
475 timeouts_update(T
, T
->curtime
+ elapsed
);
476 } /* timeouts_step() */
479 TIMEOUT_PUBLIC
bool timeouts_pending(struct timeouts
*T
) {
483 for (wheel
= 0; wheel
< WHEEL_NUM
; wheel
++) {
484 pending
|= T
->pending
[wheel
];
488 } /* timeouts_pending() */
491 TIMEOUT_PUBLIC
bool timeouts_expired(struct timeouts
*T
) {
492 return !TOR_TAILQ_EMPTY(&T
->expired
);
493 } /* timeouts_expired() */
497 * Calculate the interval before needing to process any timeouts pending on
500 * (This is separated from the public API routine so we can evaluate our
501 * wheel invariant assertions irrespective of the expired queue.)
503 * This might return a timeout value sooner than any installed timeout if
504 * only higher-order wheels have timeouts pending. We can only know when to
505 * process a wheel, not precisely when a timeout is scheduled. Our timeout
506 * accuracy could be off by 2^(N*M)-1 units where N is the wheel number and
507 * M is WHEEL_BIT. Only timeouts which have fallen through to wheel 0 can be
510 * We should never return a timeout larger than the lowest actual timeout.
512 static timeout_t
timeouts_int(struct timeouts
*T
) {
513 timeout_t timeout
= ~TIMEOUT_C(0), _timeout
;
519 for (wheel
= 0; wheel
< WHEEL_NUM
; wheel
++) {
520 if (T
->pending
[wheel
]) {
521 slot
= WHEEL_MASK
& (T
->curtime
>> (wheel
* WHEEL_BIT
));
523 /* ctz input cannot be zero: T->pending[wheel] is
524 * nonzero, so rotr() is nonzero. */
525 _timeout
= (ctz(rotr(T
->pending
[wheel
], slot
)) + !!wheel
) << (wheel
* WHEEL_BIT
);
526 /* +1 to higher order wheels as those timeouts are one rotation in the future (otherwise they'd be on a lower wheel or expired) */
528 _timeout
-= relmask
& T
->curtime
;
529 /* reduce by how much lower wheels have progressed */
531 timeout
= MIN(_timeout
, timeout
);
534 relmask
<<= WHEEL_BIT
;
535 relmask
|= WHEEL_MASK
;
539 } /* timeouts_int() */
543 * Calculate the interval our caller can wait before needing to process
546 TIMEOUT_PUBLIC timeout_t
timeouts_timeout(struct timeouts
*T
) {
547 if (!TOR_TAILQ_EMPTY(&T
->expired
))
550 return timeouts_int(T
);
551 } /* timeouts_timeout() */
554 TIMEOUT_PUBLIC
struct timeout
*timeouts_get(struct timeouts
*T
) {
555 if (!TOR_TAILQ_EMPTY(&T
->expired
)) {
556 struct timeout
*to
= TOR_TAILQ_FIRST(&T
->expired
);
558 TOR_TAILQ_REMOVE(&T
->expired
, to
, tqe
);
560 TO_SET_TIMEOUTS(to
, NULL
);
562 #ifndef TIMEOUT_DISABLE_INTERVALS
563 if ((to
->flags
& TIMEOUT_INT
) && to
->interval
> 0)
564 timeouts_readd(T
, to
);
571 } /* timeouts_get() */
575 * Use dumb looping to locate the earliest timeout pending on the wheel so
576 * our invariant assertions can check the result of our optimized code.
578 static struct timeout
*timeouts_min(struct timeouts
*T
) {
579 struct timeout
*to
, *min
= NULL
;
582 for (i
= 0; i
< countof(T
->wheel
); i
++) {
583 for (j
= 0; j
< countof(T
->wheel
[i
]); j
++) {
584 TOR_TAILQ_FOREACH(to
, &T
->wheel
[i
][j
], tqe
) {
585 if (!min
|| to
->expires
< min
->expires
)
592 } /* timeouts_min() */
596 * Check some basic algorithm invariants. If these invariants fail then
597 * something is definitely broken.
599 #define report(...) do { \
601 fprintf(fp, __VA_ARGS__); \
604 #define check(expr, ...) do { \
606 report(__VA_ARGS__); \
611 TIMEOUT_PUBLIC
bool timeouts_check(struct timeouts
*T
, FILE *fp
) {
615 if ((to
= timeouts_min(T
))) {
616 check(to
->expires
> T
->curtime
, "missed timeout (expires:%" TIMEOUT_PRIu
" <= curtime:%" TIMEOUT_PRIu
")\n", to
->expires
, T
->curtime
);
618 timeout
= timeouts_int(T
);
619 check(timeout
<= to
->expires
- T
->curtime
, "wrong soft timeout (soft:%" TIMEOUT_PRIu
" > hard:%" TIMEOUT_PRIu
") (expires:%" TIMEOUT_PRIu
"; curtime:%" TIMEOUT_PRIu
")\n", timeout
, (to
->expires
- T
->curtime
), to
->expires
, T
->curtime
);
621 timeout
= timeouts_timeout(T
);
622 check(timeout
<= to
->expires
- T
->curtime
, "wrong soft timeout (soft:%" TIMEOUT_PRIu
" > hard:%" TIMEOUT_PRIu
") (expires:%" TIMEOUT_PRIu
"; curtime:%" TIMEOUT_PRIu
")\n", timeout
, (to
->expires
- T
->curtime
), to
->expires
, T
->curtime
);
624 timeout
= timeouts_timeout(T
);
626 if (!TOR_TAILQ_EMPTY(&T
->expired
))
627 check(timeout
== 0, "wrong soft timeout (soft:%" TIMEOUT_PRIu
" != hard:%" TIMEOUT_PRIu
")\n", timeout
, TIMEOUT_C(0));
629 check(timeout
== ~TIMEOUT_C(0), "wrong soft timeout (soft:%" TIMEOUT_PRIu
" != hard:%" TIMEOUT_PRIu
")\n", timeout
, ~TIMEOUT_C(0));
633 } /* timeouts_check() */
638 static const int pc0 = __LINE__; \
639 switch (pc0 + it->pc) { \
640 case __LINE__: (void)0
642 #define SAVE_AND_DO(do_statement) \
644 it->pc = __LINE__ - pc0; \
646 case __LINE__: (void)0; \
650 SAVE_AND_DO(return (rv))
653 SAVE_AND_DO(break); \
657 TIMEOUT_PUBLIC
struct timeout
*timeouts_next(struct timeouts
*T
, struct timeouts_it
*it
) {
662 if (it
->flags
& TIMEOUTS_EXPIRED
) {
663 if (it
->flags
& TIMEOUTS_CLEAR
) {
664 while ((to
= timeouts_get(T
))) {
668 TOR_TAILQ_FOREACH_SAFE(to
, &T
->expired
, tqe
, it
->to
) {
674 if (it
->flags
& TIMEOUTS_PENDING
) {
675 for (it
->i
= 0; it
->i
< countof(T
->wheel
); it
->i
++) {
676 for (it
->j
= 0; it
->j
< countof(T
->wheel
[it
->i
]); it
->j
++) {
677 TOR_TAILQ_FOREACH_SAFE(to
, &T
->wheel
[it
->i
][it
->j
], tqe
, it
->to
) {
687 } /* timeouts_next */
696 * T I M E O U T R O U T I N E S
698 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
700 TIMEOUT_PUBLIC
struct timeout
*timeout_init(struct timeout
*to
, int flags
) {
701 memset(to
, 0, sizeof *to
);
706 } /* timeout_init() */
709 #ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS
710 TIMEOUT_PUBLIC
bool timeout_pending(struct timeout
*to
) {
711 return to
->pending
&& to
->pending
!= &to
->timeouts
->expired
;
712 } /* timeout_pending() */
715 TIMEOUT_PUBLIC
bool timeout_expired(struct timeout
*to
) {
716 return to
->pending
&& to
->pending
== &to
->timeouts
->expired
;
717 } /* timeout_expired() */
720 TIMEOUT_PUBLIC
void timeout_del(struct timeout
*to
) {
721 timeouts_del(to
->timeouts
, to
);
722 } /* timeout_del() */
727 * V E R S I O N I N T E R F A C E S
729 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
731 TIMEOUT_PUBLIC
int timeout_version(void) {
732 return TIMEOUT_VERSION
;
733 } /* timeout_version() */
736 TIMEOUT_PUBLIC
const char *timeout_vendor(void) {
737 return TIMEOUT_VENDOR
;
738 } /* timeout_version() */
741 TIMEOUT_PUBLIC
int timeout_v_rel(void) {
742 return TIMEOUT_V_REL
;
743 } /* timeout_version() */
746 TIMEOUT_PUBLIC
int timeout_v_abi(void) {
747 return TIMEOUT_V_ABI
;
748 } /* timeout_version() */
751 TIMEOUT_PUBLIC
int timeout_v_api(void) {
752 return TIMEOUT_V_API
;
753 } /* timeout_version() */