/src/freeradius-server/src/lib/util/timer.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** Various types of event timer list |
18 | | * |
19 | | * @file src/lib/util/timer.c |
20 | | * |
21 | | * @copyright 2025 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
22 | | */ |
23 | | |
24 | | #define _TIMER_PRIVATE 1 |
25 | | typedef struct fr_timer_list_s fr_timer_list_t; |
26 | | |
27 | | #include <freeradius-devel/util/debug.h> |
28 | | #include <freeradius-devel/util/time.h> |
29 | | #include <freeradius-devel/util/dlist.h> |
30 | | #include <freeradius-devel/util/event.h> |
31 | | #include <freeradius-devel/util/value.h> |
32 | | #include <freeradius-devel/util/lst.h> |
33 | | |
34 | | FR_DLIST_TYPES(timer) |
35 | | FR_DLIST_TYPEDEFS(timer, fr_timer_head_t, fr_timer_entry_t) |
36 | | |
37 | | /** What type of event list the timer is inserted into |
38 | | * |
39 | | */ |
40 | | typedef enum { |
41 | | TIMER_LIST_TYPE_LST = 1, //!< Self-sorting timer list based on a left leaning skeleton tree. |
42 | | TIMER_LIST_TYPE_ORDERED = 2, //!< Strictly ordered list of events in a dlist. |
43 | | TIMER_LIST_TYPE_SHARED = 3 //!< all events share one event callback |
44 | | } timer_list_type_t; |
45 | | |
46 | | /** An event timer list |
47 | | * |
48 | | */ |
49 | | struct fr_timer_list_s { |
50 | | struct fr_timer_list_pub_s pub; //!< Public interface to the event timer list. |
51 | | |
52 | | union { |
53 | | fr_lst_t *lst; //!< of timer events to be executed. |
54 | | timer_head_t ordered; //!< A list of timer events to be executed. |
55 | | struct { |
56 | | fr_rb_tree_t *rb; //!< a tree of raw pointers |
57 | | fr_rb_tree_t *deferred; //!< a tree of deferred things |
58 | | size_t time_offset; //!< offset from uctx to the fr_time_t it contains |
59 | | size_t node_offset; //!< offset from uctx to the fr_rb_node it contains |
60 | | fr_timer_cb_t callback; //!< the callback to run |
61 | | } shared; |
62 | | }; |
63 | | timer_list_type_t type; |
64 | | bool in_handler; //!< Whether we're currently in a callback. |
65 | | bool disarmed; //!< the entire timer list is disarmed |
66 | | |
67 | | timer_head_t deferred; //!< A list of timer events to be inserted, after |
68 | | ///< the current batch has been processed. |
69 | | ///< This prevents "busy" timer loops, where |
70 | | ///< other events may starve, or we may never exit. |
71 | | |
72 | | fr_timer_list_t *parent; //!< Parent list to insert event into (if any). |
73 | | fr_timer_t *parent_ev; //!< Event in the parent's event loop. |
74 | | |
75 | | #ifdef WITH_EVENT_DEBUG |
76 | | fr_timer_t *report; //!< Used to trigger periodic reports about the event timer list. |
77 | | #endif |
78 | | }; |
79 | | |
80 | | /** A timer event |
81 | | * |
82 | | */ |
83 | | struct fr_timer_s { |
84 | | fr_time_t when; //!< When this timer should fire. |
85 | | |
86 | | fr_timer_cb_t callback; //!< Callback to execute when the timer fires. |
87 | | void const *uctx; //!< Context pointer to pass to the callback. |
88 | | |
89 | | TALLOC_CTX *linked_ctx; //!< talloc ctx this event was bound to. |
90 | | |
91 | | fr_timer_t **parent; //!< A pointer to the parent structure containing the timer |
92 | | ///< event. |
93 | | |
94 | | fr_timer_entry_t entry; //!< Entry in a list of timer events. |
95 | | union { |
96 | | fr_dlist_t ordered_entry; //!< Entry in an ordered list of timer events. |
97 | | fr_lst_index_t lst_idx; //!< Where to store opaque lst data, not used for ordered lists. |
98 | | }; |
99 | | bool free_on_fire; //!< Whether to free the event when it fires. |
100 | | |
101 | | fr_timer_list_t *tl; //!< The event list this timer is part of. |
102 | | ///< This is set to NULL when an event is disarmed, |
103 | | ///< but all other fields are left intact. |
104 | | |
105 | | #ifndef NDEBUG |
106 | | char const *file; //!< Source file this event was last updated in. |
107 | | int line; //!< Line this event was last updated on. |
108 | | #endif |
109 | | }; |
110 | | |
111 | 0 | FR_DLIST_FUNCS(timer, fr_timer_t, entry) |
112 | | |
113 | | #define CHECK_PARENT(_ev) \ |
114 | 0 | fr_assert_msg(!(_ev)->parent || (*(_ev)->parent == ev), \ |
115 | 0 | "Event %p, allocd %s[%d], parent field points to %p", (_ev), (_ev)->file, (_ev)->line, *(_ev)->parent); |
116 | | |
117 | 0 | #define TIMER_UCTX_TO_TIME(_tl, _x) ((fr_time_t *)(((uintptr_t) (_x)) + (_tl)->shared.time_offset)) |
118 | | |
119 | | /** Specialisation function to insert a timer |
120 | | * |
121 | | * @param[in] tl Timer list to insert into. |
122 | | * @param[in] ev Timer event to insert. |
123 | | * @return |
124 | | * - 0 on success. |
125 | | * - -1 on failure. |
126 | | */ |
127 | | typedef int (*timer_insert_t)(fr_timer_list_t *tl, fr_timer_t *ev); |
128 | | |
129 | | /** Specialisation function to delete a timer |
130 | | * |
131 | | * @param[in] ev Timer event to delete. |
132 | | * @return |
133 | | * - 0 on success. |
134 | | * - -1 on failure. |
135 | | */ |
136 | | typedef int (*timer_disarm_t)(fr_timer_t *ev); |
137 | | |
138 | | /** Specialisation function to execute any pending timers |
139 | | * |
140 | | * @param[in] tl Timer list to execute. |
141 | | * @param[in,out] when Our current time, updated to the next event time (i.e. the next time we'll need to run something) |
142 | | * @return |
143 | | * - 0 no timer events fired. |
144 | | * - 1 a timer event fired. |
145 | | */ |
146 | | typedef int (*timer_list_run_t)(fr_timer_list_t *tl, fr_time_t *when); |
147 | | |
148 | | /** Return the soonest timer event |
149 | | * |
150 | | * @param[in] tl to get the head of. |
151 | | * @return |
152 | | * - The head of the list. |
153 | | * - NULL if the list is empty. |
154 | | */ |
155 | | typedef fr_timer_t *(*timer_list_head_t)(fr_timer_list_t *tl); |
156 | | |
157 | | /** Process any deferred timer events |
158 | | * |
159 | | * @param[in] tl to process deferred events for. |
160 | | * @return |
161 | | * - The head of the list. |
162 | | * - NULL if the list is empty. |
163 | | */ |
164 | | typedef int (*timer_list_deferred_t)(fr_timer_list_t *tl); |
165 | | |
166 | | /** Return the number of elements in the list |
167 | | * |
168 | | * @param[in] tl to get the number of elements from. |
169 | | * @return |
170 | | * - The number of elements in the list. |
171 | | */ |
172 | | typedef uint64_t (*timer_list_num_elements_t)(fr_timer_list_t *tl); |
173 | | |
174 | | typedef struct { |
175 | | timer_insert_t insert; //!< Function to insert a timer event. |
176 | | timer_disarm_t disarm; //!< Function to delete a timer event. |
177 | | |
178 | | timer_list_run_t run; //!< Function to run a timer event. |
179 | | timer_list_head_t head; //!< Function to get the head of the list. |
180 | | timer_list_deferred_t deferred; //!< Function to process deferred events. |
181 | | timer_list_num_elements_t num_events; //!< Function to get the number of elements in the list. |
182 | | } timer_list_funcs_t; |
183 | | |
184 | 0 | #define EVENT_ARMED(_ev) ((_ev)->tl != NULL) |
185 | | |
186 | | static fr_time_t *timer_list_when(fr_timer_list_t *tl); |
187 | | |
188 | | static int timer_lst_insert_at(fr_timer_list_t *tl, fr_timer_t *ev); |
189 | | static int timer_ordered_insert_at(fr_timer_list_t *tl, fr_timer_t *ev); |
190 | | |
191 | | static int timer_lst_disarm(fr_timer_t *ev); |
192 | | static int timer_ordered_disarm(fr_timer_t *ev); |
193 | | |
194 | | static int timer_list_lst_run(fr_timer_list_t *tl, fr_time_t *when); |
195 | | static int timer_list_ordered_run(fr_timer_list_t *tl, fr_time_t *when) |
196 | | ;static int timer_list_shared_run(fr_timer_list_t *tl, fr_time_t *when); |
197 | | |
198 | | static fr_timer_t *timer_list_lst_head(fr_timer_list_t *tl); |
199 | | static fr_timer_t *timer_list_ordered_head(fr_timer_list_t *tl); |
200 | | |
201 | | static int timer_list_lst_deferred(fr_timer_list_t *tl); |
202 | | static int timer_list_ordered_deferred(fr_timer_list_t *tl); |
203 | | static int timer_list_shared_deferred(fr_timer_list_t *tl); |
204 | | |
205 | | static uint64_t timer_list_lst_num_events(fr_timer_list_t *tl); |
206 | | static uint64_t timer_list_ordered_num_events(fr_timer_list_t *tl); |
207 | | static uint64_t timer_list_shared_num_events(fr_timer_list_t *tl); |
208 | | |
209 | | /** Functions for performing operations on various types of timer list |
210 | | * |
211 | | */ |
212 | | static timer_list_funcs_t const timer_funcs[] = { |
213 | | [TIMER_LIST_TYPE_LST] = { |
214 | | .insert = timer_lst_insert_at, |
215 | | .disarm = timer_lst_disarm, |
216 | | |
217 | | .run = timer_list_lst_run, |
218 | | .head = timer_list_lst_head, |
219 | | .deferred = timer_list_lst_deferred, |
220 | | .num_events = timer_list_lst_num_events |
221 | | }, |
222 | | [TIMER_LIST_TYPE_ORDERED] = { |
223 | | .insert = timer_ordered_insert_at, |
224 | | .disarm = timer_ordered_disarm, |
225 | | |
226 | | .run = timer_list_ordered_run, |
227 | | .head = timer_list_ordered_head, |
228 | | .deferred = timer_list_ordered_deferred, |
229 | | .num_events = timer_list_ordered_num_events |
230 | | }, |
231 | | [TIMER_LIST_TYPE_SHARED] = { |
232 | | // .insert = timer_shared_insert_at, |
233 | | // .disarm = timer_shared_disarm, |
234 | | |
235 | | .run = timer_list_shared_run, |
236 | | // .head = timer_list_shared_head, |
237 | | .deferred = timer_list_shared_deferred, |
238 | | .num_events = timer_list_shared_num_events |
239 | | }, |
240 | | }; |
241 | | |
242 | | /** Compare two timer events to see which one should occur first |
243 | | * |
244 | | * @param[in] a the first timer event. |
245 | | * @param[in] b the second timer event. |
246 | | * @return |
247 | | * - +1 if a should occur later than b. |
248 | | * - -1 if a should occur earlier than b. |
249 | | * - 0 if both events occur at the same time. |
250 | | */ |
251 | | static int8_t timer_cmp(void const *a, void const *b) |
252 | 0 | { |
253 | 0 | fr_timer_t const *ev_a = a, *ev_b = b; |
254 | |
|
255 | 0 | return fr_time_cmp(ev_a->when, ev_b->when); |
256 | 0 | } |
257 | | |
258 | | |
259 | | /** This callback fires in the parent to execute events in this sublist |
260 | | * |
261 | | * @param[in] parent_tl Parent event timer list. |
262 | | * @param[in] when When the parent timer fired. |
263 | | * @param[in] uctx Sublist to execute. |
264 | | */ |
265 | | static void _parent_timer_cb(UNUSED fr_timer_list_t *parent_tl, fr_time_t when, void *uctx) |
266 | 0 | { |
267 | 0 | fr_timer_list_t *tl = talloc_get_type_abort(uctx, fr_timer_list_t); |
268 | |
|
269 | 0 | fr_assert(!tl->disarmed); |
270 | | |
271 | | /* |
272 | | * We're in the parent timer, so we need to run the |
273 | | * events in the child timer list. |
274 | | */ |
275 | 0 | (void)fr_timer_list_run(tl, &when); |
276 | 0 | } |
277 | | |
278 | | /** Utility function to update parent timers |
279 | | * |
280 | | * @param[in] tl to update parent timers for. |
281 | | * @return |
282 | | * - 0 on success. |
283 | | * - -1 on failure. |
284 | | */ |
285 | | static inline CC_HINT(always_inline) int timer_list_parent_update(fr_timer_list_t *tl) |
286 | 0 | { |
287 | 0 | fr_time_t *when; |
288 | |
|
289 | 0 | if (!tl->parent) return 0; |
290 | | |
291 | 0 | when = timer_list_when(tl); |
292 | | |
293 | | /* |
294 | | * No events, disarm the timer |
295 | | */ |
296 | 0 | if (!when) { |
297 | | /* |
298 | | * Disables the timer in the parent, does not free the memory |
299 | | */ |
300 | 0 | if (tl->parent) FR_TIMER_DISARM_RETURN(tl->parent_ev); |
301 | 0 | return 0; |
302 | 0 | } |
303 | | |
304 | | /* |
305 | | * We have an active event, we can suppress changes which have no effect. |
306 | | */ |
307 | 0 | if (tl->parent_ev && EVENT_ARMED(tl->parent_ev)) { |
308 | 0 | fr_assert(!tl->disarmed); /* fr_timer_list_disarm() must disarm it */ |
309 | |
|
310 | 0 | if (fr_time_eq(*when, tl->parent_ev->when)) { |
311 | 0 | return 0; |
312 | 0 | } |
313 | 0 | } |
314 | | |
315 | | /* |
316 | | * This is a child list which is disabled. Don't update the parent. |
317 | | */ |
318 | 0 | if (tl->disarmed) { |
319 | 0 | fr_assert(tl->parent); |
320 | |
|
321 | 0 | fr_assert(!tl->parent_ev || !EVENT_ARMED(tl->parent_ev)); |
322 | 0 | return 0; |
323 | 0 | } |
324 | | |
325 | | /* |
326 | | * The list is armed and the head has changed, so we change the event in the parent list. |
327 | | */ |
328 | 0 | if (fr_timer_at(tl, tl->parent, &tl->parent_ev, |
329 | 0 | *when, false, _parent_timer_cb, tl) < 0) return -1; |
330 | | |
331 | 0 | return 0; |
332 | 0 | } |
333 | | |
334 | | /** Insert a timer event into a single event timer list |
335 | | * |
336 | | * @param[in] tl to insert the event into. |
337 | | * @param[in] ev to insert. |
338 | | * @return |
339 | | * - 0 on success. |
340 | | * - -1 on failure. |
341 | | */ |
342 | | static int timer_lst_insert_at(fr_timer_list_t *tl, fr_timer_t *ev) |
343 | 0 | { |
344 | 0 | if (unlikely(fr_lst_insert(tl->lst, ev) < 0)) { |
345 | 0 | fr_strerror_const_push("Failed inserting timer into lst"); |
346 | 0 | return -1; |
347 | 0 | } |
348 | | |
349 | 0 | return 0; |
350 | 0 | } |
351 | | |
352 | | /** Insert an event into an ordered timer list |
353 | | * |
354 | | * Timer must be in order, i.e. either before first event, or after last event |
355 | | * |
356 | | * @param[in] tl to insert the event into. |
357 | | * @param[in] ev to insert. |
358 | | * @return |
359 | | * - 0 on success. |
360 | | * - -1 on failure. |
361 | | */ |
362 | | static int timer_ordered_insert_at(fr_timer_list_t *tl, fr_timer_t *ev) |
363 | 0 | { |
364 | 0 | fr_timer_t *tail; |
365 | |
|
366 | 0 | tail = timer_tail(&tl->ordered); |
367 | 0 | if (tail && fr_time_lt(ev->when, tail->when)) { |
368 | 0 | fr_strerror_const("Event being inserted must occurr _after_ the last event"); |
369 | 0 | return -1; |
370 | 0 | } |
371 | | |
372 | 0 | if (unlikely(timer_insert_tail(&tl->ordered, ev) < 0)) { |
373 | 0 | fr_strerror_const_push("Failed inserting timer into ordered list"); |
374 | 0 | return -1; |
375 | 0 | } |
376 | | |
377 | 0 | return 0; |
378 | 0 | } |
379 | | |
380 | | /** Remove an event from the event loop |
381 | | * |
382 | | * @param[in] ev to free. |
383 | | * @return |
384 | | * - 0 on success. |
385 | | * - -1 on failure. |
386 | | */ |
387 | | static int _timer_free(fr_timer_t *ev) |
388 | 0 | { |
389 | 0 | fr_timer_t **ev_p; |
390 | 0 | int ret; |
391 | |
|
392 | 0 | ret = fr_timer_disarm(ev); /* Is a noop if ev->tl == NULL */ |
393 | 0 | if (ret < 0) return ret; |
394 | | |
395 | 0 | CHECK_PARENT(ev); |
396 | 0 | ev_p = ev->parent; |
397 | 0 | *ev_p = NULL; |
398 | |
|
399 | 0 | return 0; |
400 | 0 | } |
401 | | |
402 | | /** Insert a timer event into an event list |
403 | | * |
404 | | * @note The talloc parent of the memory returned in ev_p must not be changed. |
405 | | * If the lifetime of the event needs to be bound to another context |
406 | | * this function should be called with the existing event pointed to by |
407 | | * ev_p. |
408 | | * |
409 | | * @param[in] ctx to bind lifetime of the event to. |
410 | | * @param[in] tl to insert event into. |
411 | | * @param[in,out] ev_p If not NULL modify this event instead of creating a new one. This is a parent |
412 | | * in a temporal sense, not in a memory structure or dependency sense. |
413 | | * @param[in] when we should run the event. |
414 | | * @param[in] free_on_fire Whether event memory should be freed if the event fires. |
415 | | * @param[in] callback function to execute if the event fires. |
416 | | * @param[in] uctx user data to pass to the event. |
417 | | * @return |
418 | | * - 0 on success. |
419 | | * - -1 on failure. |
420 | | */ |
421 | | int _fr_timer_at(NDEBUG_LOCATION_ARGS |
422 | | TALLOC_CTX *ctx, fr_timer_list_t *tl, fr_timer_t **ev_p, |
423 | | fr_time_t when, |
424 | | bool free_on_fire, fr_timer_cb_t callback, void const *uctx) |
425 | 0 | { |
426 | 0 | fr_timer_t *ev; |
427 | |
|
428 | 0 | fr_assert(tl->type != TIMER_LIST_TYPE_SHARED); |
429 | | |
430 | | /* |
431 | | * If there is an event, reuse it instead of freeing it |
432 | | * and allocating a new one. This is to reduce memory |
433 | | * churn for repeat events. |
434 | | */ |
435 | 0 | if (!*ev_p) { |
436 | 0 | new_event: |
437 | 0 | ev = talloc_zero(tl, fr_timer_t); |
438 | 0 | if (unlikely(!ev)) { |
439 | 0 | fr_strerror_const("Out of memory"); |
440 | 0 | return -1; |
441 | 0 | } |
442 | | |
443 | 0 | EVENT_DEBUG("%p - " NDEBUG_LOCATION_FMT "Added new timer %p", tl, NDEBUG_LOCATION_VALS ev); |
444 | | /* |
445 | | * Bind the lifetime of the event to the specified |
446 | | * talloc ctx. If the talloc ctx is freed, the |
447 | | * event will also be freed. |
448 | | */ |
449 | 0 | if (ctx != tl) talloc_link_ctx(ctx, ev); |
450 | |
|
451 | 0 | talloc_set_destructor(ev, _timer_free); |
452 | 0 | } else { |
453 | 0 | ev = talloc_get_type_abort(UNCONST(fr_timer_t *, *ev_p), fr_timer_t); |
454 | |
|
455 | 0 | EVENT_DEBUG("%p - " NDEBUG_LOCATION_FMT "Re-armed timer %p", tl, NDEBUG_LOCATION_VALS ev); |
456 | | |
457 | | /* |
458 | | * We can't disarm the linking context due to |
459 | | * limitations in talloc, so if the linking |
460 | | * context changes, we need to free the old |
461 | | * event, and allocate a new one. |
462 | | * |
463 | | * Freeing the event also removes it from the lst. |
464 | | */ |
465 | 0 | if (unlikely(ev->linked_ctx != ctx)) { |
466 | 0 | talloc_free(ev); |
467 | 0 | goto new_event; |
468 | 0 | } |
469 | | |
470 | | /* |
471 | | * If the event is associated with a list, we need |
472 | | * to disarm it, before we can rearm it. |
473 | | */ |
474 | 0 | if (EVENT_ARMED(ev)) { |
475 | 0 | int ret; |
476 | 0 | char const *err_file; |
477 | 0 | int err_line; |
478 | | |
479 | | /* |
480 | | * Removed event from the event list or the |
481 | | * deferred list. |
482 | | */ |
483 | 0 | ret = fr_timer_disarm(ev); |
484 | 0 | #ifndef NDEBUG |
485 | 0 | err_file = ev->file; |
486 | 0 | err_line = ev->line; |
487 | | #else |
488 | | err_file = "not-available"; |
489 | | err_line = 0; |
490 | | #endif |
491 | | |
492 | | /* |
493 | | * Events MUST be in the lst (or the insertion list). |
494 | | */ |
495 | 0 | if (!fr_cond_assert_msg(ret == 0, |
496 | 0 | "Event %p, allocd %s[%d], was not found in the event " |
497 | 0 | "list or deferred list when re-armed: %s", ev, |
498 | 0 | err_file, err_line, fr_strerror())) return -1; |
499 | 0 | } |
500 | 0 | } |
501 | | |
502 | 0 | ev->tl = tl; /* This indicates the event memory is bound to an event loop */ |
503 | 0 | ev->when = when; |
504 | 0 | ev->free_on_fire = free_on_fire; |
505 | 0 | ev->callback = callback; |
506 | 0 | ev->uctx = uctx; |
507 | 0 | ev->linked_ctx = ctx; |
508 | 0 | ev->parent = ev_p; |
509 | 0 | #ifndef NDEBUG |
510 | 0 | ev->file = file; |
511 | 0 | ev->line = line; |
512 | 0 | #endif |
513 | | |
514 | | /* |
515 | | * No updating needed as the events are deferred |
516 | | */ |
517 | 0 | if (tl->in_handler) { |
518 | | /* |
519 | | * ...a little hacky, but we need to verify that |
520 | | * we're not inserting an event that's earlier |
521 | | * than the last event in the list for ordered |
522 | | * lists. |
523 | | * |
524 | | * Otherwise we'd end up doing this when we tried |
525 | | * to move all the deferred events into the timer |
526 | | * list, and end up making that O(n) instead of O(1). |
527 | | */ |
528 | 0 | if (tl->type == TIMER_LIST_TYPE_ORDERED) { |
529 | 0 | fr_timer_t *head = timer_list_ordered_head(tl); |
530 | |
|
531 | 0 | if (head && fr_time_lt(ev->when, head->when)) { |
532 | 0 | fr_strerror_const("Event being inserted must occurr _after_ the last event"); |
533 | |
|
534 | 0 | insert_failed: |
535 | 0 | talloc_set_destructor(ev, NULL); |
536 | 0 | talloc_free(ev); |
537 | 0 | *ev_p = NULL; |
538 | 0 | return -1; |
539 | 0 | } |
540 | 0 | } |
541 | | |
542 | 0 | if (!fr_cond_assert_msg(timer_insert_tail(&tl->deferred, ev) == 0, |
543 | 0 | "Failed inserting event into deferred list")) { |
544 | 0 | goto insert_failed; |
545 | 0 | } |
546 | 0 | } else { |
547 | 0 | int ret; |
548 | |
|
549 | 0 | ret = timer_funcs[tl->type].insert(tl, ev); |
550 | 0 | if (unlikely(ret < 0)) goto insert_failed; |
551 | | |
552 | | /* |
553 | | * We need to update the parent timer |
554 | | * to ensure it fires at the correct time. |
555 | | */ |
556 | 0 | if (unlikely(timer_list_parent_update(tl) < 0)) return -1; |
557 | 0 | } |
558 | | |
559 | 0 | *ev_p = ev; |
560 | |
|
561 | 0 | return 0; |
562 | 0 | } |
563 | | |
564 | | /** Insert a timer event into an event list |
565 | | * |
566 | | * @note The talloc parent of the memory returned in ev_p must not be changed. |
567 | | * If the lifetime of the event needs to be bound to another context |
568 | | * this function should be called with the existing event pointed to by |
569 | | * ev_p. |
570 | | * |
571 | | * @param[in] ctx to bind lifetime of the event to. |
572 | | * @param[in] tl to insert event into. |
573 | | * @param[in,out] ev_p If not NULL modify this event instead of creating a new one. This is a parent |
574 | | * in a temporal sense, not in a memory structure or dependency sense. |
575 | | * @param[in] delta In how many nanoseconds to wait before should we execute the event. |
576 | | * @param[in] free_on_fire Whether event memory should be freed if the event fires. |
577 | | * @param[in] callback function to execute if the event fires. |
578 | | * @param[in] uctx user data to pass to the event. |
579 | | * @return |
580 | | * - 0 on success. |
581 | | * - -1 on failure. |
582 | | */ |
583 | | int _fr_timer_in(NDEBUG_LOCATION_ARGS |
584 | | TALLOC_CTX *ctx, fr_timer_list_t *tl, fr_timer_t **ev_p, |
585 | | fr_time_delta_t delta, |
586 | | bool free_on_fire, fr_timer_cb_t callback, void const *uctx) |
587 | 0 | { |
588 | 0 | return _fr_timer_at(NDEBUG_LOCATION_VALS |
589 | 0 | ctx, tl, ev_p, fr_time_add(tl->pub.time(), delta), |
590 | 0 | free_on_fire, callback, uctx); |
591 | 0 | } |
592 | | |
593 | | static int timer_lst_disarm(fr_timer_t *ev) |
594 | | { |
595 | | fr_timer_list_t *tl = ev->tl; |
596 | | |
597 | | if (timer_in_list(&tl->deferred,ev)) { |
598 | | (void)timer_remove(&tl->deferred, ev); |
599 | | } else { |
600 | | int ret = fr_lst_extract(tl->lst, ev); |
601 | | char const *err_file; |
602 | | int err_line; |
603 | | |
604 | | #ifndef NDEBUG |
605 | | err_file = ev->file; |
606 | | err_line = ev->line; |
607 | | #else |
608 | | err_file = "not-available"; |
609 | | err_line = 0; |
610 | | #endif |
611 | | |
612 | | |
613 | | /* |
614 | | * Events MUST be in the lst (or the insertion list). |
615 | | */ |
616 | | if (!fr_cond_assert_msg(ret == 0, |
617 | | "Event %p, lst_id %u, allocd %s[%d], was not found in the event lst or " |
618 | | "insertion list when freed: %s", ev, ev->lst_idx, err_file, err_line, |
619 | | fr_strerror())) return -1; |
620 | | } |
621 | | |
622 | | return 0; |
623 | | } |
624 | | |
625 | | /** Remove a timer from a timer list, but don't free it |
626 | | * |
627 | | * @param[in] ev to remove. |
628 | | */ |
629 | | static int timer_ordered_disarm(fr_timer_t *ev) |
630 | | { |
631 | | /* |
632 | | * Check the check is still valid (sanity check) |
633 | | */ |
634 | | (void)talloc_get_type_abort(ev, fr_timer_t);; |
635 | | |
636 | | /* |
637 | | * Already dissassociated from a list, nothing to do. |
638 | | */ |
639 | | if (!ev->tl) return 0; |
640 | | |
641 | | /* |
642 | | * This *MUST* be in a timer list if it has a non-NULL tl pointer. |
643 | | */ |
644 | | if (unlikely(!fr_cond_assert(timer_in_list(&ev->tl->ordered, ev)))) return -1; |
645 | | |
646 | | (void)timer_remove(&ev->tl->ordered, ev); |
647 | | |
648 | | return 0; |
649 | | } |
650 | | |
651 | | /** Remove an event from the event list, but don't free the memory |
652 | | * |
653 | | * @param[in] ev to remove from the event list. |
654 | | */ |
655 | | int fr_timer_disarm(fr_timer_t *ev) |
656 | 0 | { |
657 | 0 | fr_timer_list_t *tl; |
658 | |
|
659 | 0 | if (!ev || !EVENT_ARMED(ev)) { |
660 | 0 | EVENT_DEBUG("Asked to disarm inactive timer %p (noop)", ev); |
661 | 0 | return 0; /* Noop */ |
662 | 0 | } |
663 | | |
664 | 0 | tl = ev->tl; |
665 | |
|
666 | 0 | EVENT_DEBUG("Disarming timer %p", ev); |
667 | |
|
668 | 0 | CHECK_PARENT(ev); |
669 | | |
670 | | /* |
671 | | * If the event is deferred, it's not in the event list proper |
672 | | * so just remove it, and set the tl pointer to NULL. |
673 | | */ |
674 | 0 | if (timer_in_list(&tl->deferred,ev)) { |
675 | 0 | (void)timer_remove(&tl->deferred, ev); |
676 | 0 | } else { |
677 | 0 | int ret = timer_funcs[ev->tl->type].disarm(ev); |
678 | 0 | if (ret < 0) return ret; |
679 | 0 | } |
680 | 0 | ev->tl = NULL; |
681 | |
|
682 | 0 | return timer_list_parent_update(tl); |
683 | 0 | } |
684 | | |
685 | | /** Delete a timer event and free its memory |
686 | | * |
687 | | * @param[in] ev_p of the event being deleted. |
688 | | * @return |
689 | | * - 0 on success. |
690 | | * - -1 on failure. |
691 | | */ |
692 | | int fr_timer_delete(fr_timer_t **ev_p) |
693 | 0 | { |
694 | 0 | fr_timer_t *ev; |
695 | 0 | int ret; |
696 | |
|
697 | 0 | if (unlikely(!*ev_p)) return 0; |
698 | | |
699 | 0 | ev = *ev_p; |
700 | 0 | ret = talloc_free(ev); /* Destructor removed event from any lists */ |
701 | | |
702 | | /* |
703 | | * Don't leave a garbage pointer value |
704 | | * if parent is not ev_p. |
705 | | */ |
706 | 0 | if (likely(ret == 0)) { |
707 | 0 | *ev_p = NULL; |
708 | 0 | return 0; |
709 | 0 | } |
710 | | |
711 | 0 | EVENT_DEBUG("Deleting timer %p failed: %s", ev, fr_strerror_peek()); |
712 | 0 | return -1; |
713 | 0 | } |
714 | | |
715 | | /** Internal timestamp representing when the timer should fire |
716 | | * |
717 | | * @return When the timestamp should fire. |
718 | | */ |
719 | | fr_time_t fr_timer_when(fr_timer_t *ev) |
720 | 0 | { |
721 | 0 | if (!fr_timer_armed(ev)) return fr_time_wrap(0); |
722 | 0 | return ev->when; |
723 | 0 | } |
724 | | |
725 | | /** Return time delta between now and when the timer should fire |
726 | | * |
727 | | * @param[in] ev to get the time delta for. |
728 | | */ |
729 | | fr_time_delta_t fr_timer_remaining(fr_timer_t *ev) |
730 | 0 | { |
731 | 0 | if (!fr_timer_armed(ev)) return fr_time_delta_wrap(0); |
732 | 0 | return fr_time_sub(ev->when, ev->tl->pub.time()); |
733 | 0 | } |
734 | | |
735 | | /** Check if a timer event is armed |
736 | | * |
737 | | * @param[in] ev to check. |
738 | | * @return |
739 | | * - true if the event is armed. |
740 | | * - false if the event is not armed. |
741 | | */ |
742 | | bool _fr_timer_armed(fr_timer_t *ev) |
743 | 0 | { |
744 | 0 | return EVENT_ARMED(ev); |
745 | 0 | } |
746 | | |
747 | | /** Run all scheduled timer events in a lst |
748 | | * |
749 | | * @param[in] tl containing the timer events. |
750 | | * @param[in] when Process events scheduled to run before or at this time. |
751 | | * - Set to 0 if no more events. |
752 | | * - Set to the next event time if there are more events. |
753 | | * @return |
754 | | * - 0 no timer events fired. |
755 | | * - 1 a timer event fired. |
756 | | */ |
757 | | CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private fr_timer_list_t trips --fsanitize=function*/ |
758 | | static int timer_list_lst_run(fr_timer_list_t *tl, fr_time_t *when) |
759 | 0 | { |
760 | 0 | fr_timer_cb_t callback; |
761 | 0 | void *uctx; |
762 | 0 | fr_timer_t *ev; |
763 | 0 | int fired = 0; |
764 | |
|
765 | 0 | while (fr_lst_num_elements(tl->lst) > 0) { |
766 | 0 | ev = talloc_get_type_abort(fr_lst_peek(tl->lst), fr_timer_t); |
767 | | |
768 | | /* |
769 | | * See if it's time to do this one. |
770 | | */ |
771 | 0 | if (fr_time_gt(ev->when, *when)) { |
772 | 0 | *when = ev->when; |
773 | 0 | done: |
774 | 0 | return fired; |
775 | 0 | } |
776 | | |
777 | 0 | callback = ev->callback; |
778 | 0 | memcpy(&uctx, &ev->uctx, sizeof(uctx)); |
779 | |
|
780 | 0 | CHECK_PARENT(ev); |
781 | | |
782 | | /* |
783 | | * Disarm the event before calling it. |
784 | | * |
785 | | * This leaves the memory in place, |
786 | | * but dissassociates it from the list. |
787 | | * |
788 | | * We use the public function as it |
789 | | * handles more cases. |
790 | | */ |
791 | 0 | if (!fr_cond_assert(fr_timer_disarm(ev) == 0)) return -2; |
792 | 0 | EVENT_DEBUG("Running timer %p", ev); |
793 | 0 | if (ev->free_on_fire) talloc_free(ev); |
794 | |
|
795 | 0 | callback(tl, *when, uctx); |
796 | |
|
797 | 0 | fired++; |
798 | 0 | } |
799 | | |
800 | 0 | *when = fr_time_wrap(0); |
801 | |
|
802 | 0 | goto done; |
803 | 0 | } |
804 | | |
805 | | /** Run all scheduled events in an ordered list |
806 | | * |
807 | | * @param[in] tl containing the timer events. |
808 | | * @param[in] when Process events scheduled to run before or at this time. |
809 | | * - Set to 0 if no more events. |
810 | | * - Set to the next event time if there are more events. |
811 | | * @return |
812 | | * - < 0 if we failed to updated the parent list. |
813 | | * - 0 no timer events fired. |
814 | | * - >0 number of timer event fired. |
815 | | */ |
816 | | CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private fr_timer_list_t trips --fsanitize=function*/ |
817 | | static int timer_list_ordered_run(fr_timer_list_t *tl, fr_time_t *when) |
818 | 0 | { |
819 | 0 | fr_timer_cb_t callback; |
820 | 0 | void *uctx; |
821 | 0 | fr_timer_t *ev; |
822 | 0 | unsigned int fired = 0; |
823 | |
|
824 | 0 | while ((ev = timer_head(&tl->ordered))) { |
825 | 0 | (void)talloc_get_type_abort(ev, fr_timer_t); |
826 | | |
827 | | /* |
828 | | * See if it's time to do this one. |
829 | | */ |
830 | 0 | if (fr_time_gt(ev->when, *when)) { |
831 | 0 | *when = ev->when; |
832 | 0 | done: |
833 | 0 | return fired; |
834 | 0 | } |
835 | | |
836 | 0 | callback = ev->callback; |
837 | 0 | memcpy(&uctx, &ev->uctx, sizeof(uctx)); |
838 | |
|
839 | 0 | CHECK_PARENT(ev); |
840 | | |
841 | | /* |
842 | | * Disarm the event before calling it. |
843 | | * |
844 | | * This leaves the memory in place, |
845 | | * but dissassociates it from the list. |
846 | | * |
847 | | * We use the public function as it |
848 | | * handles more cases. |
849 | | */ |
850 | 0 | if (!fr_cond_assert(fr_timer_disarm(ev) == 0)) return -2; |
851 | 0 | EVENT_DEBUG("Running timer %p", ev); |
852 | 0 | if (ev->free_on_fire) talloc_free(ev); |
853 | |
|
854 | 0 | callback(tl, *when, uctx); |
855 | |
|
856 | 0 | fired++; |
857 | 0 | } |
858 | | |
859 | 0 | *when = fr_time_wrap(0); |
860 | |
|
861 | 0 | goto done; |
862 | 0 | } |
863 | | |
864 | | /** Run all scheduled events in an ordered list |
865 | | * |
866 | | * @param[in] tl containing the timer events. |
867 | | * @param[in] when Process events scheduled to run before or at this time. |
868 | | * - Set to 0 if no more events. |
869 | | * - Set to the next event time if there are more events. |
870 | | * @return |
871 | | * - < 0 if we failed to updated the parent list. |
872 | | * - 0 no timer events fired. |
873 | | * - >0 number of timer event fired. |
874 | | */ |
875 | | CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private fr_timer_list_t trips --fsanitize=function*/ |
876 | | static int timer_list_shared_run(fr_timer_list_t *tl, fr_time_t *when) |
877 | 0 | { |
878 | 0 | void *uctx; |
879 | 0 | unsigned int fired = 0; |
880 | |
|
881 | 0 | while ((uctx = fr_rb_first(tl->shared.rb)) != NULL) { |
882 | 0 | fr_time_t const *next; |
883 | |
|
884 | 0 | next = TIMER_UCTX_TO_TIME(tl, uctx); |
885 | | |
886 | | /* |
887 | | * See if it's time to do this one. |
888 | | */ |
889 | 0 | if (fr_time_gt(*next, *when)) { |
890 | 0 | *when = *next; |
891 | 0 | done: |
892 | 0 | return fired; |
893 | 0 | } |
894 | | |
895 | 0 | fr_rb_remove(tl->shared.rb, uctx); |
896 | |
|
897 | 0 | tl->shared.callback(tl, *when, uctx); |
898 | |
|
899 | 0 | fired++; |
900 | 0 | } |
901 | | |
902 | 0 | *when = fr_time_wrap(0); |
903 | |
|
904 | 0 | goto done; |
905 | 0 | } |
906 | | |
907 | | |
908 | | /** Forcibly run all events in an event loop. |
909 | | * |
910 | | * This is used to forcefully run every event in the event loop. |
911 | | * |
912 | | * We pass in the real time, which may theoretically cause issues if timer |
913 | | * callbacks are checking... But the uses of this function are very limited. |
914 | | * |
915 | | * @return |
916 | | * - < 0 if we failed to update the parent list. |
917 | | * - 0 no timer events fired. |
918 | | * - > 0 number of timer event fired. |
919 | | */ |
920 | | int fr_timer_list_force_run(fr_timer_list_t *tl) |
921 | 0 | { |
922 | 0 | fr_time_t when = fr_time_max(); |
923 | |
|
924 | 0 | return fr_timer_list_run(tl, &when); |
925 | 0 | } |
926 | | |
927 | | /** Execute any pending events in the event loop |
928 | | * |
929 | | * @param[in] tl to execute events in. |
930 | | * @param[in] when Process events scheduled to run before or at this time. |
931 | | * - Set to 0 if no more events. |
932 | | * - Set to the next event time if there are more events. |
933 | | * @return |
934 | | * - < 0 if we failed to update the parent list. |
935 | | * - 0 no timer events fired. |
936 | | * - >0 number of timer event fired. |
937 | | */ |
938 | | int fr_timer_list_run(fr_timer_list_t *tl, fr_time_t *when) |
939 | 0 | { |
940 | 0 | int ret; |
941 | 0 | bool in_handler = tl->in_handler; /* allow nested timer execution */ |
942 | |
|
943 | 0 | tl->in_handler = true; |
944 | 0 | ret = timer_funcs[tl->type].run(tl, when); |
945 | 0 | tl->in_handler = in_handler; |
946 | | |
947 | | /* |
948 | | * Now we've executed all the pending events, |
949 | | * now merge the deferred events into the main |
950 | | * event list. |
951 | | * |
952 | | * The events don't need to be modified as they |
953 | | * were initialised completely before being |
954 | | * placed in the deferred list. |
955 | | */ |
956 | 0 | if (timer_num_elements(&tl->deferred) > 0) { |
957 | 0 | if (unlikely(timer_funcs[tl->type].deferred(tl) < 0)) return -1; |
958 | 0 | if (unlikely(timer_list_parent_update(tl) < 0)) return -1; |
959 | | /* |
960 | | * We ran some events, and have no deferred |
961 | | * events to insert, so we need to forcefully |
962 | | * update the parent timer. |
963 | | */ |
964 | 0 | } else if(ret > 0) { |
965 | 0 | if (unlikely(timer_list_parent_update(tl) < 0)) return -1; |
966 | 0 | } |
967 | | |
968 | 0 | return ret; |
969 | 0 | } |
970 | | |
971 | | /** Return the head of the lst |
972 | | * |
973 | | * @param[in] tl to get the head of. |
974 | | * @return |
975 | | * - The head of the trie. |
976 | | * - NULL, if there's no head. |
977 | | */ |
978 | | static fr_timer_t *timer_list_lst_head(fr_timer_list_t *tl) |
979 | 0 | { |
980 | 0 | return fr_lst_peek(tl->lst); |
981 | 0 | } |
982 | | |
983 | | /** Return the head of the ordered list |
984 | | * |
985 | | * @param[in] tl to get the head of. |
986 | | * @return |
987 | | * - The head of the trie. |
988 | | * - NULL, if there's no head. |
989 | | */ |
990 | | static fr_timer_t *timer_list_ordered_head(fr_timer_list_t *tl) |
991 | 0 | { |
992 | 0 | return timer_head(&tl->ordered); |
993 | 0 | } |
994 | | |
995 | | |
996 | | /** Move all deferred events into the lst |
997 | | * |
998 | | * @param[in] tl to move events in. |
999 | | * @return |
1000 | | * - 0 on success. |
1001 | | * - -1 on failure. |
1002 | | */ |
1003 | | static int timer_list_lst_deferred(fr_timer_list_t *tl) |
1004 | 0 | { |
1005 | 0 | fr_timer_t *ev; |
1006 | |
|
1007 | 0 | while((ev = timer_pop_head(&tl->deferred))) { |
1008 | 0 | if (unlikely(timer_lst_insert_at(tl, ev) < 0)) { |
1009 | 0 | timer_insert_head(&tl->deferred, ev); /* Don't lose track of events we failed to insert */ |
1010 | 0 | return -1; |
1011 | 0 | } |
1012 | 0 | } |
1013 | | |
1014 | 0 | return 0; |
1015 | 0 | } |
1016 | | |
1017 | | /** Move all deferred events into the ordered event list |
1018 | | * |
1019 | | * This operation is O(1). |
1020 | | * |
1021 | | * @param[in] tl to move events in. |
1022 | | * @return |
1023 | | * - 0 on success. |
1024 | | * - -1 on failure. |
1025 | | */ |
1026 | | static int timer_list_ordered_deferred(fr_timer_list_t *tl) |
1027 | 0 | { |
1028 | 0 | fr_timer_t *ev; |
1029 | 0 | #ifndef NDEBUG |
1030 | 0 | { |
1031 | 0 | fr_timer_t *head, *tail; |
1032 | |
|
1033 | 0 | head = timer_head(&tl->deferred); |
1034 | 0 | tail = timer_tail(&tl->ordered); |
1035 | | |
1036 | | /* |
1037 | | * Something has gone catastrophically wrong if the |
1038 | | * deferred event is earlier than the last event in |
1039 | | * the ordered list, given all the checks we do. |
1040 | | */ |
1041 | 0 | fr_cond_assert_msg(!head || !tail || fr_time_gteq(head->when, tail->when), |
1042 | 0 | "Deferred event is earlier than the last event in the ordered list"); |
1043 | 0 | } |
1044 | 0 | #endif |
1045 | | |
1046 | | /* |
1047 | | * Can't use timer_move_head as entry positions |
1048 | | * for the two lists are different. |
1049 | | */ |
1050 | 0 | while ((ev = timer_pop_head((&tl->deferred)))) { |
1051 | 0 | timer_insert_tail(&tl->ordered, ev); |
1052 | 0 | } |
1053 | |
|
1054 | 0 | return 0; |
1055 | 0 | } |
1056 | | |
1057 | | /** Move all deferred events into the shared list |
1058 | | * |
1059 | | * @param[in] tl to move events in. |
1060 | | * @return |
1061 | | * - 0 on success. |
1062 | | * - -1 on failure. |
1063 | | */ |
1064 | | static int timer_list_shared_deferred(fr_timer_list_t *tl) |
1065 | 0 | { |
1066 | 0 | void *uctx; |
1067 | |
|
1068 | 0 | while((uctx = fr_rb_first(tl->shared.deferred)) != NULL) { |
1069 | 0 | fr_rb_remove_by_inline_node(tl->shared.deferred, |
1070 | 0 | (fr_rb_node_t *) (((uintptr_t) (uctx)) + tl->shared.node_offset)); |
1071 | |
|
1072 | 0 | fr_rb_insert(tl->shared.rb, uctx); |
1073 | 0 | } |
1074 | |
|
1075 | 0 | return 0; |
1076 | 0 | } |
1077 | | |
1078 | | static uint64_t timer_list_lst_num_events(fr_timer_list_t *tl) |
1079 | 0 | { |
1080 | 0 | return fr_lst_num_elements(tl->lst); |
1081 | 0 | } |
1082 | | |
1083 | | static uint64_t timer_list_ordered_num_events(fr_timer_list_t *tl) |
1084 | 0 | { |
1085 | 0 | return timer_num_elements(&tl->ordered); |
1086 | 0 | } |
1087 | | |
1088 | | static uint64_t timer_list_shared_num_events(fr_timer_list_t *tl) |
1089 | 0 | { |
1090 | 0 | return fr_rb_num_elements(tl->shared.rb); |
1091 | 0 | } |
1092 | | |
1093 | | /** Disarm a timer list |
1094 | | * |
1095 | | * @param[in] tl Timer list to disarm |
1096 | | * @return |
1097 | | * - 0 on success. |
1098 | | * - -1 on failure. |
1099 | | */ |
1100 | | int fr_timer_list_disarm(fr_timer_list_t *tl) |
1101 | | { |
1102 | | if (!tl->parent) { |
1103 | | fr_strerror_const("Timer list does not have a parent"); |
1104 | | return -1; |
1105 | | } |
1106 | | |
1107 | | tl->disarmed = true; |
1108 | | |
1109 | | FR_TIMER_DISARM_RETURN(tl->parent_ev); |
1110 | | |
1111 | | return 0; |
1112 | | } |
1113 | | |
1114 | | /** Arm (or re-arm) a timer list |
1115 | | * |
1116 | | * @param[in] tl Timer list to (re)-arm |
1117 | | * @return |
1118 | | * - 0 on success. |
1119 | | * - -1 on failure. |
1120 | | */ |
1121 | | int fr_timer_list_arm(fr_timer_list_t *tl) |
1122 | 0 | { |
1123 | 0 | if (!tl->parent) { |
1124 | 0 | fr_strerror_const("Timer list does not have a parent"); |
1125 | 0 | return -1; |
1126 | 0 | } |
1127 | | |
1128 | 0 | if (!tl->disarmed) return 0; |
1129 | | |
1130 | 0 | tl->disarmed = false; |
1131 | | |
1132 | | /* |
1133 | | * Run any timer events which were missed during the time that the list was disarmed. |
1134 | | */ |
1135 | 0 | _parent_timer_cb(tl->parent, fr_time(), tl); |
1136 | |
|
1137 | 0 | return timer_list_parent_update(tl); |
1138 | 0 | } |
1139 | | |
1140 | | /** Return number of pending events |
1141 | | * |
1142 | | * @note This includes deferred events, i.e. those yet to be inserted into the main list |
1143 | | * |
1144 | | * @param[in] tl to get the number of events from. |
1145 | | * @return |
1146 | | * - The number of events in the list. |
1147 | | */ |
1148 | | uint64_t fr_timer_list_num_events(fr_timer_list_t *tl) |
1149 | 0 | { |
1150 | 0 | uint64_t num = timer_funcs[tl->type].num_events(tl); |
1151 | |
|
1152 | 0 | return num + timer_num_elements(&tl->deferred); |
1153 | 0 | } |
1154 | | |
1155 | | static fr_time_t *timer_list_when(fr_timer_list_t *tl) |
1156 | 0 | { |
1157 | 0 | fr_timer_t *ev; |
1158 | |
|
1159 | 0 | switch (tl->type) { |
1160 | 0 | default: |
1161 | 0 | ev = timer_funcs[tl->type].head(tl); |
1162 | 0 | if (ev) return &ev->when; |
1163 | 0 | break; |
1164 | | |
1165 | 0 | case TIMER_LIST_TYPE_SHARED: { |
1166 | 0 | void *uctx; |
1167 | |
|
1168 | 0 | uctx = fr_rb_first(tl->shared.rb); |
1169 | 0 | if (!uctx) break; |
1170 | | |
1171 | 0 | return TIMER_UCTX_TO_TIME(tl, uctx); |
1172 | 0 | } |
1173 | 0 | } |
1174 | | |
1175 | 0 | return NULL; |
1176 | 0 | } |
1177 | | |
1178 | | /** Return the time of the next event |
1179 | | * |
1180 | | * @param[in] tl to get the next event time from. |
1181 | | * @return |
1182 | | * - >0 the time of the next event. |
1183 | | * - 0 if there are no more events. |
1184 | | */ |
1185 | | fr_time_t fr_timer_list_when(fr_timer_list_t *tl) |
1186 | 0 | { |
1187 | 0 | fr_time_t const *when = timer_list_when(tl); |
1188 | |
|
1189 | 0 | if (when) return *when; |
1190 | | |
1191 | 0 | return fr_time_wrap(0); |
1192 | 0 | } |
1193 | | |
1194 | | /** Override event list time source |
1195 | | * |
1196 | | * @param[in] tl to set new time function for. |
1197 | | * @param[in] func to set. |
1198 | | */ |
1199 | | void fr_timer_list_set_time_func(fr_timer_list_t *tl, fr_event_time_source_t func) |
1200 | 0 | { |
1201 | 0 | tl->pub.time = func; |
1202 | 0 | } |
1203 | | |
1204 | | /** Cleanup all timers currently in the list |
1205 | | * |
1206 | | * @param[in] tl to cleanup. |
1207 | | * @return |
1208 | | * - 0 on success. |
1209 | | * - -1 on failure. |
1210 | | */ |
1211 | | static int _timer_list_free(fr_timer_list_t *tl) |
1212 | 0 | { |
1213 | 0 | fr_timer_t *ev; |
1214 | |
|
1215 | 0 | if (unlikely(tl->in_handler)) { |
1216 | 0 | fr_strerror_const("Cannot free event timer list while in handler"); |
1217 | 0 | return -1; |
1218 | 0 | } |
1219 | | |
1220 | 0 | if (tl->parent_ev) if (unlikely(fr_timer_delete(&tl->parent_ev) < 0)) return -1; |
1221 | | |
1222 | 0 | if (tl->type == TIMER_LIST_TYPE_SHARED) return 0; |
1223 | | |
1224 | 0 | while ((ev = timer_funcs[tl->type].head(tl))) { |
1225 | 0 | if (talloc_free(ev) < 0) return -1; |
1226 | 0 | } |
1227 | | |
1228 | 0 | return 0; |
1229 | 0 | } |
1230 | | |
1231 | | static fr_timer_list_t *timer_list_alloc(TALLOC_CTX *ctx, fr_timer_list_t *parent) |
1232 | 0 | { |
1233 | 0 | fr_timer_list_t *tl; |
1234 | |
|
1235 | 0 | fr_assert(!parent || (parent->type != TIMER_LIST_TYPE_SHARED)); |
1236 | |
|
1237 | 0 | tl = talloc_zero(ctx, fr_timer_list_t); |
1238 | 0 | if (unlikely(tl == NULL)) { |
1239 | 0 | fr_strerror_const("Out of memory"); |
1240 | 0 | return NULL; |
1241 | 0 | } |
1242 | | |
1243 | 0 | timer_talloc_init(&tl->deferred); |
1244 | 0 | if (parent) { |
1245 | 0 | tl->parent = parent; |
1246 | 0 | tl->pub.time = parent->pub.time; |
1247 | 0 | } else { |
1248 | 0 | tl->pub.time = fr_time; |
1249 | 0 | } |
1250 | 0 | talloc_set_destructor(tl, _timer_list_free); |
1251 | |
|
1252 | 0 | return tl; |
1253 | 0 | } |
1254 | | |
1255 | | /** Allocate a new lst based timer list |
1256 | | * |
1257 | | * @note Entries may be inserted in any order. |
1258 | | * |
1259 | | * @param[in] ctx to insert head timer event into. |
1260 | | * @param[in] parent to insert the head timer event into. |
1261 | | */ |
1262 | | fr_timer_list_t *fr_timer_list_lst_alloc(TALLOC_CTX *ctx, fr_timer_list_t *parent) |
1263 | 0 | { |
1264 | 0 | fr_timer_list_t *tl; |
1265 | |
|
1266 | 0 | if (unlikely((tl = timer_list_alloc(ctx, parent)) == NULL)) return NULL; |
1267 | | |
1268 | 0 | tl->lst = fr_lst_talloc_alloc(tl, timer_cmp, fr_timer_t, lst_idx, 0); |
1269 | 0 | if (unlikely(tl->lst == NULL)) { |
1270 | 0 | fr_strerror_const("Failed allocating timer list"); |
1271 | 0 | talloc_free(tl); |
1272 | 0 | return NULL; |
1273 | 0 | } |
1274 | 0 | tl->type = TIMER_LIST_TYPE_LST; |
1275 | |
|
1276 | | #ifdef WITH_EVENT_REPORT |
1277 | | fr_timer_in(tl, tl, &tl->report, fr_time_delta_from_sec(EVENT_REPORT_FREQ), false, fr_timer_report, NULL); |
1278 | | #endif |
1279 | |
|
1280 | 0 | return tl; |
1281 | 0 | } |
1282 | | |
1283 | | /** Allocate a new sorted event timer list |
1284 | | * |
1285 | | * @note Entries must be inserted in the order that they will fire. |
1286 | | * |
1287 | | * @param[in] ctx to allocate the event timer list from. |
1288 | | * @param[in] parent to insert the head timer event into. |
1289 | | */ |
1290 | | fr_timer_list_t *fr_timer_list_ordered_alloc(TALLOC_CTX *ctx, fr_timer_list_t *parent) |
1291 | 0 | { |
1292 | 0 | fr_timer_list_t *tl; |
1293 | |
|
1294 | 0 | if (unlikely((tl = timer_list_alloc(ctx, parent)) == NULL)) return NULL; |
1295 | | |
1296 | 0 | fr_dlist_talloc_init((fr_dlist_head_t *)&tl->ordered, fr_timer_t, ordered_entry); |
1297 | 0 | tl->type = TIMER_LIST_TYPE_ORDERED; |
1298 | |
|
1299 | 0 | return tl; |
1300 | 0 | } |
1301 | | |
1302 | | /** Allocate a new shared event timer list |
1303 | | * |
1304 | | * @param[in] ctx to allocate the event timer list from. |
1305 | | * @param[in] parent to insert the head timer event into. |
1306 | | * @param[in] cmp comparison routine (smaller times are earlier) |
1307 | | * @param[in] callback to run on timer event |
1308 | | * @param[in] node_offset offset from uctx to the fr_rb_node_t it contains |
1309 | | * @param[in] time_offset offset from uctx to the fr_time_t it contains |
1310 | | */ |
1311 | | fr_timer_list_t *fr_timer_list_shared_alloc(TALLOC_CTX *ctx, fr_timer_list_t *parent, fr_cmp_t cmp, |
1312 | | fr_timer_cb_t callback, size_t node_offset, size_t time_offset) |
1313 | 0 | { |
1314 | 0 | fr_timer_list_t *tl; |
1315 | |
|
1316 | 0 | if (unlikely((tl = timer_list_alloc(ctx, parent)) == NULL)) return NULL; |
1317 | | |
1318 | 0 | tl->type = TIMER_LIST_TYPE_SHARED; |
1319 | |
|
1320 | 0 | tl->shared.time_offset = time_offset; |
1321 | 0 | tl->shared.node_offset = node_offset; |
1322 | 0 | tl->shared.callback = callback; |
1323 | |
|
1324 | 0 | tl->shared.rb = _fr_rb_alloc(tl, node_offset, NULL, cmp, NULL); |
1325 | 0 | if (!tl->shared.rb) { |
1326 | 0 | talloc_free(tl); |
1327 | 0 | return NULL; |
1328 | 0 | } |
1329 | | |
1330 | 0 | tl->shared.deferred = _fr_rb_alloc(tl, node_offset, NULL, cmp, NULL); |
1331 | 0 | if (!tl->shared.deferred) { |
1332 | 0 | talloc_free(tl); |
1333 | 0 | return NULL; |
1334 | 0 | } |
1335 | | |
1336 | 0 | return tl; |
1337 | 0 | } |
1338 | | |
1339 | | /** Insert a uctx into a shared timer, and update the timer. |
1340 | | * |
1341 | | * @param[in] tl Timer list to insert into. |
1342 | | * @param[in] uctx to insert |
1343 | | * @return |
1344 | | * - 0 on success. |
1345 | | * - -1 on failure. |
1346 | | */ |
1347 | | int fr_timer_uctx_insert(fr_timer_list_t *tl, void *uctx) |
1348 | 0 | { |
1349 | 0 | fr_assert(tl->type == TIMER_LIST_TYPE_SHARED); |
1350 | |
|
1351 | 0 | if (tl->in_handler) { |
1352 | 0 | if (!fr_rb_insert(tl->shared.deferred, uctx)) return -1; |
1353 | | |
1354 | 0 | return 0; |
1355 | 0 | } |
1356 | | |
1357 | 0 | if (!fr_rb_insert(tl->shared.rb, uctx)) return -1; |
1358 | | |
1359 | 0 | return timer_list_parent_update(tl); |
1360 | 0 | } |
1361 | | |
1362 | | /** Remove a uctx from a shared timer |
1363 | | * |
1364 | | * @param[in] tl Timer list to insert into. |
1365 | | * @param[in] uctx to remove |
1366 | | * @return |
1367 | | * - 0 uctx was successfully removed. |
1368 | | * - -1 uctx was removed, but the parent timer was not updated |
1369 | | */ |
1370 | | int fr_timer_uctx_remove(fr_timer_list_t *tl, void *uctx) |
1371 | 0 | { |
1372 | 0 | fr_assert(tl->type == TIMER_LIST_TYPE_SHARED); |
1373 | |
|
1374 | 0 | fr_rb_remove_by_inline_node(tl->shared.rb, |
1375 | 0 | (fr_rb_node_t *) (((uintptr_t) (uctx)) + tl->shared.node_offset)); |
1376 | |
|
1377 | 0 | return timer_list_parent_update(tl); |
1378 | 0 | } |
1379 | | |
1380 | | void *fr_timer_uctx_peek(fr_timer_list_t *tl) |
1381 | 0 | { |
1382 | 0 | fr_assert(tl->type == TIMER_LIST_TYPE_SHARED); |
1383 | |
|
1384 | 0 | return fr_rb_first(tl->shared.rb); |
1385 | 0 | } |
1386 | | |
1387 | | |
1388 | | #if defined(WITH_EVENT_DEBUG) && !defined(NDEBUG) |
1389 | | static const fr_time_delta_t decades[18] = { |
1390 | | { 1 }, { 10 }, { 100 }, |
1391 | | { 1000 }, { 10000 }, { 100000 }, |
1392 | | { 1000000 }, { 10000000 }, { 100000000 }, |
1393 | | { 1000000000 }, { 10000000000 }, { 100000000000 }, |
1394 | | { 1000000000000 }, { 10000000000000 }, { 100000000000000 }, |
1395 | | { 1000000000000000 }, { 10000000000000000 }, { 100000000000000000 }, |
1396 | | }; |
1397 | | |
1398 | | static const char *decade_names[18] = { |
1399 | | "1ns", "10ns", "100ns", |
1400 | | "1us", "10us", "100us", |
1401 | | "1ms", "10ms", "100ms", |
1402 | | "1s", "10s", "100s", |
1403 | | "1Ks", "10Ks", "100Ks", |
1404 | | "1Ms", "10Ms", "100Ms", /* 1 year is 300Ms */ |
1405 | | }; |
1406 | | |
1407 | | typedef struct { |
1408 | | fr_rb_node_t node; |
1409 | | char const *file; |
1410 | | int line; |
1411 | | uint32_t count; |
1412 | | } fr_event_counter_t; |
1413 | | |
1414 | | static int8_t timer_location_cmp(void const *one, void const *two) |
1415 | | { |
1416 | | fr_event_counter_t const *a = one; |
1417 | | fr_event_counter_t const *b = two; |
1418 | | |
1419 | | CMP_RETURN(a, b, file); |
1420 | | |
1421 | | return CMP(a->line, b->line); |
1422 | | } |
1423 | | |
1424 | | static int _event_report_process(fr_rb_tree_t **locations, size_t array[], fr_time_t now, fr_timer_t *ev) |
1425 | | { |
1426 | | fr_time_delta_t diff = fr_time_sub(ev->when, now); |
1427 | | size_t i; |
1428 | | |
1429 | | for (i = 0; i < NUM_ELEMENTS(decades); i++) { |
1430 | | if ((fr_time_delta_cmp(diff, decades[i]) <= 0) || (i == NUM_ELEMENTS(decades) - 1)) { |
1431 | | fr_event_counter_t find = { .file = ev->file, .line = ev->line }; |
1432 | | fr_event_counter_t *counter; |
1433 | | |
1434 | | counter = fr_rb_find(locations[i], &find); |
1435 | | if (!counter) { |
1436 | | counter = talloc(locations[i], fr_event_counter_t); |
1437 | | if (!counter) { |
1438 | | EVENT_DEBUG("Can't do report, out of memory"); |
1439 | | return -1; |
1440 | | } |
1441 | | counter->file = ev->file; |
1442 | | counter->line = ev->line; |
1443 | | counter->count = 1; |
1444 | | fr_rb_insert(locations[i], counter); |
1445 | | } else { |
1446 | | counter->count++; |
1447 | | } |
1448 | | |
1449 | | array[i]++; |
1450 | | break; |
1451 | | } |
1452 | | } |
1453 | | |
1454 | | return 0; |
1455 | | } |
1456 | | |
1457 | | /** Print out information about timer events in the event loop |
1458 | | * |
1459 | | */ |
1460 | | void fr_timer_report(fr_timer_list_t *tl, fr_time_t now, void *uctx) |
1461 | | { |
1462 | | fr_lst_iter_t iter; |
1463 | | fr_timer_t *ev; |
1464 | | size_t i; |
1465 | | |
1466 | | size_t array[NUM_ELEMENTS(decades)] = { 0 }; |
1467 | | fr_rb_tree_t *locations[NUM_ELEMENTS(decades)]; |
1468 | | TALLOC_CTX *tmp_ctx; |
1469 | | static pthread_mutex_t print_lock = PTHREAD_MUTEX_INITIALIZER; |
1470 | | |
1471 | | if (tl->type == TIMER_LIST_TYPE_SHARED) { |
1472 | | EVENT_DEBUG("Cannot (yet) do timer report for TIMER_LIST_TYPE_SHARED"); |
1473 | | return; |
1474 | | } |
1475 | | |
1476 | | tmp_ctx = talloc_init_const("temporary stats"); |
1477 | | if (!tmp_ctx) { |
1478 | | oom: |
1479 | | EVENT_DEBUG("Can't do report, out of memory"); |
1480 | | talloc_free(tmp_ctx); |
1481 | | return; |
1482 | | } |
1483 | | |
1484 | | for (i = 0; i < NUM_ELEMENTS(decades); i++) { |
1485 | | locations[i] = fr_rb_inline_alloc(tmp_ctx, fr_event_counter_t, node, timer_location_cmp, NULL); |
1486 | | if (!locations[i]) goto oom; |
1487 | | } |
1488 | | |
1489 | | switch (tl->type) { |
1490 | | case TIMER_LIST_TYPE_LST: |
1491 | | /* |
1492 | | * Show which events are due, when they're due, |
1493 | | * and where they were allocated |
1494 | | */ |
1495 | | for (ev = fr_lst_iter_init(tl->lst, &iter); |
1496 | | ev != NULL; |
1497 | | ev = fr_lst_iter_next(tl->lst, &iter)) { |
1498 | | if (_event_report_process(locations, array, now, ev) < 0) goto oom; |
1499 | | } |
1500 | | break; |
1501 | | |
1502 | | case TIMER_LIST_TYPE_ORDERED: |
1503 | | /* |
1504 | | * Show which events are due, when they're due, |
1505 | | * and where they were allocated |
1506 | | */ |
1507 | | for (ev = timer_head(&tl->ordered); |
1508 | | ev != NULL; |
1509 | | ev = timer_next(&tl->ordered, ev)) { |
1510 | | if (_event_report_process(locations, array, now, ev) < 0) goto oom; |
1511 | | } |
1512 | | break; |
1513 | | |
1514 | | case TIMER_LIST_TYPE_SHARED: |
1515 | | fr_assert(0); |
1516 | | return; |
1517 | | } |
1518 | | |
1519 | | pthread_mutex_lock(&print_lock); |
1520 | | EVENT_DEBUG("num timer events: %"PRIu64, fr_timer_list_num_events(tl)); |
1521 | | |
1522 | | for (i = 0; i < NUM_ELEMENTS(decades); i++) { |
1523 | | fr_rb_iter_inorder_t event_iter; |
1524 | | void *node; |
1525 | | |
1526 | | if (!array[i]) continue; |
1527 | | |
1528 | | if (i == 0) { |
1529 | | EVENT_DEBUG(" events <= %5s : %zu", decade_names[i], array[i]); |
1530 | | } else if (i == (NUM_ELEMENTS(decades) - 1)) { |
1531 | | EVENT_DEBUG(" events > %5s : %zu", decade_names[i - 1], array[i]); |
1532 | | } else { |
1533 | | EVENT_DEBUG(" events %5s - %5s : %zu", decade_names[i - 1], decade_names[i], array[i]); |
1534 | | } |
1535 | | |
1536 | | for (node = fr_rb_iter_init_inorder(locations[i], &event_iter); |
1537 | | node; |
1538 | | node = fr_rb_iter_next_inorder(locations[i], &event_iter)) { |
1539 | | fr_event_counter_t *counter = talloc_get_type_abort(node, fr_event_counter_t); |
1540 | | |
1541 | | EVENT_DEBUG(" : %u allocd at %s[%d]", |
1542 | | counter->count, counter->file, counter->line); |
1543 | | } |
1544 | | } |
1545 | | pthread_mutex_unlock(&print_lock); |
1546 | | |
1547 | | fr_timer_in(tl, tl, &tl->report, fr_time_delta_from_sec(EVENT_REPORT_FREQ), false, fr_timer_report, uctx); |
1548 | | talloc_free(tmp_ctx); |
1549 | | } |
1550 | | |
1551 | | void fr_timer_dump(fr_timer_list_t *tl) |
1552 | | { |
1553 | | fr_lst_iter_t iter; |
1554 | | fr_timer_t *ev; |
1555 | | fr_time_t now = tl->pub.time(); /* Get the current time */ |
1556 | | |
1557 | | #define TIMER_DUMP(_ev) \ |
1558 | | EVENT_DEBUG("%s[%d]: %p time=%" PRId64 " (%c), callback=%p", \ |
1559 | | (_ev)->file, (_ev)->line, _ev, fr_time_unwrap((_ev)->when), \ |
1560 | | fr_time_gt(now, (_ev)->when) ? '<' : '>', (_ev)->callback); |
1561 | | |
1562 | | EVENT_DEBUG("Time is now %"PRId64"", fr_time_unwrap(now)); |
1563 | | |
1564 | | switch (tl->type) { |
1565 | | case TIMER_LIST_TYPE_LST: |
1566 | | EVENT_DEBUG("Dumping lst timer list"); |
1567 | | |
1568 | | for (ev = fr_lst_iter_init(tl->lst, &iter); |
1569 | | ev; |
1570 | | ev = fr_lst_iter_next(tl->lst, &iter)) { |
1571 | | (void)talloc_get_type_abort(ev, fr_timer_t); |
1572 | | TIMER_DUMP(ev); |
1573 | | } |
1574 | | break; |
1575 | | |
1576 | | case TIMER_LIST_TYPE_ORDERED: |
1577 | | EVENT_DEBUG("Dumping ordered timer list"); |
1578 | | |
1579 | | for (ev = timer_head(&tl->ordered); |
1580 | | ev; |
1581 | | ev = timer_next(&tl->ordered, ev)) { |
1582 | | (void)talloc_get_type_abort(ev, fr_timer_t); |
1583 | | TIMER_DUMP(ev); |
1584 | | } |
1585 | | break; |
1586 | | |
1587 | | case TIMER_LIST_TYPE_SHARED: |
1588 | | EVENT_DEBUG("Dumping shared timer list"); |
1589 | | |
1590 | | fr_rb_inorder_foreach(tl->shared.rb, void, uctx) { |
1591 | | EVENT_DEBUG("time %" PRIu64" uctx %p", fr_time_unwrap(*TIMER_UCTX_TO_TIME(tl, uctx)), uctx); |
1592 | | }} |
1593 | | break; |
1594 | | } |
1595 | | } |
1596 | | #endif |