/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 : uint8_t { |
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 | | /* |
120 | | * When looping over a timer list, stop if the entry in the list is in the future. |
121 | | * |
122 | | * OR stop if a callback disarmed the list. When the list is re-armed with fr_timer_list_arm(), that |
123 | | * function will take care of running any pending timer events. |
124 | | */ |
125 | | #define TIMER_STOP_IF_DONE(_next) \ |
126 | 0 | do { \ |
127 | 0 | if (fr_time_gt(_next, *when) || tl->disarmed) { \ |
128 | 0 | *when = _next; \ |
129 | 0 | goto done; \ |
130 | 0 | } \ |
131 | 0 | } while (0) |
132 | | |
133 | | #define TIMER_RETURN_DONE \ |
134 | 0 | do { \ |
135 | 0 | *when = fr_time_wrap(0); \ |
136 | 0 | done: \ |
137 | 0 | return fired; \ |
138 | 0 | } while (0) |
139 | | |
140 | | /** Specialisation function to insert a timer |
141 | | * |
142 | | * @param[in] tl Timer list to insert into. |
143 | | * @param[in] ev Timer event to insert. |
144 | | * @return |
145 | | * - 0 on success. |
146 | | * - -1 on failure. |
147 | | */ |
148 | | typedef int (*timer_insert_t)(fr_timer_list_t *tl, fr_timer_t *ev); |
149 | | |
150 | | /** Specialisation function to delete a timer |
151 | | * |
152 | | * @param[in] ev Timer event to delete. |
153 | | * @return |
154 | | * - 0 on success. |
155 | | * - -1 on failure. |
156 | | */ |
157 | | typedef int (*timer_disarm_t)(fr_timer_t *ev); |
158 | | |
159 | | /** Specialisation function to execute any pending timers |
160 | | * |
161 | | * @param[in] tl Timer list to execute. |
162 | | * @param[in,out] when Our current time, updated to the next event time (i.e. the next time we'll need to run something) |
163 | | * @return |
164 | | * - 0 no timer events fired. |
165 | | * - 1 a timer event fired. |
166 | | */ |
167 | | typedef int (*timer_list_run_t)(fr_timer_list_t *tl, fr_time_t *when); |
168 | | |
169 | | /** Return the soonest timer event |
170 | | * |
171 | | * @param[in] tl to get the head of. |
172 | | * @return |
173 | | * - The head of the list. |
174 | | * - NULL if the list is empty. |
175 | | */ |
176 | | typedef fr_timer_t *(*timer_list_head_t)(fr_timer_list_t *tl); |
177 | | |
178 | | /** Process any deferred timer events |
179 | | * |
180 | | * @param[in] tl to process deferred events for. |
181 | | * @return |
182 | | * - The head of the list. |
183 | | * - NULL if the list is empty. |
184 | | */ |
185 | | typedef int (*timer_list_deferred_t)(fr_timer_list_t *tl); |
186 | | |
187 | | /** Return the number of elements in the list |
188 | | * |
189 | | * @param[in] tl to get the number of elements from. |
190 | | * @return |
191 | | * - The number of elements in the list. |
192 | | */ |
193 | | typedef uint64_t (*timer_list_num_elements_t)(fr_timer_list_t *tl); |
194 | | |
195 | | typedef struct { |
196 | | timer_insert_t insert; //!< Function to insert a timer event. |
197 | | timer_disarm_t disarm; //!< Function to delete a timer event. |
198 | | |
199 | | timer_list_run_t run; //!< Function to run a timer event. |
200 | | timer_list_head_t head; //!< Function to get the head of the list. |
201 | | timer_list_deferred_t deferred; //!< Function to process deferred events. |
202 | | timer_list_num_elements_t num_events; //!< Function to get the number of elements in the list. |
203 | | } timer_list_funcs_t; |
204 | | |
205 | 0 | #define EVENT_ARMED(_ev) ((_ev)->tl != NULL) |
206 | | |
207 | | static fr_time_t *timer_list_when(fr_timer_list_t *tl); |
208 | | |
209 | | static int timer_lst_insert_at(fr_timer_list_t *tl, fr_timer_t *ev); |
210 | | static int timer_ordered_insert_at(fr_timer_list_t *tl, fr_timer_t *ev); |
211 | | |
212 | | static int timer_lst_disarm(fr_timer_t *ev); |
213 | | static int timer_ordered_disarm(fr_timer_t *ev); |
214 | | |
215 | | static int timer_list_lst_run(fr_timer_list_t *tl, fr_time_t *when); |
216 | | static int timer_list_ordered_run(fr_timer_list_t *tl, fr_time_t *when) |
217 | | ;static int timer_list_shared_run(fr_timer_list_t *tl, fr_time_t *when); |
218 | | |
219 | | static fr_timer_t *timer_list_lst_head(fr_timer_list_t *tl); |
220 | | static fr_timer_t *timer_list_ordered_head(fr_timer_list_t *tl); |
221 | | |
222 | | static int timer_list_lst_deferred(fr_timer_list_t *tl); |
223 | | static int timer_list_ordered_deferred(fr_timer_list_t *tl); |
224 | | static int timer_list_shared_deferred(fr_timer_list_t *tl); |
225 | | |
226 | | static uint64_t timer_list_lst_num_events(fr_timer_list_t *tl); |
227 | | static uint64_t timer_list_ordered_num_events(fr_timer_list_t *tl); |
228 | | static uint64_t timer_list_shared_num_events(fr_timer_list_t *tl); |
229 | | |
230 | | /** Functions for performing operations on various types of timer list |
231 | | * |
232 | | */ |
233 | | static timer_list_funcs_t const timer_funcs[] = { |
234 | | [TIMER_LIST_TYPE_LST] = { |
235 | | .insert = timer_lst_insert_at, |
236 | | .disarm = timer_lst_disarm, |
237 | | |
238 | | .run = timer_list_lst_run, |
239 | | .head = timer_list_lst_head, |
240 | | .deferred = timer_list_lst_deferred, |
241 | | .num_events = timer_list_lst_num_events |
242 | | }, |
243 | | [TIMER_LIST_TYPE_ORDERED] = { |
244 | | .insert = timer_ordered_insert_at, |
245 | | .disarm = timer_ordered_disarm, |
246 | | |
247 | | .run = timer_list_ordered_run, |
248 | | .head = timer_list_ordered_head, |
249 | | .deferred = timer_list_ordered_deferred, |
250 | | .num_events = timer_list_ordered_num_events |
251 | | }, |
252 | | [TIMER_LIST_TYPE_SHARED] = { |
253 | | // .insert = timer_shared_insert_at, |
254 | | // .disarm = timer_shared_disarm, |
255 | | |
256 | | .run = timer_list_shared_run, |
257 | | // .head = timer_list_shared_head, |
258 | | .deferred = timer_list_shared_deferred, |
259 | | .num_events = timer_list_shared_num_events |
260 | | }, |
261 | | }; |
262 | | |
263 | | /** Compare two timer events to see which one should occur first |
264 | | * |
265 | | * @param[in] a the first timer event. |
266 | | * @param[in] b the second timer event. |
267 | | * @return |
268 | | * - +1 if a should occur later than b. |
269 | | * - -1 if a should occur earlier than b. |
270 | | * - 0 if both events occur at the same time. |
271 | | */ |
272 | | static fr_cmp_ret_t timer_cmp(void const *a, void const *b) |
273 | 0 | { |
274 | 0 | fr_timer_t const *ev_a = a, *ev_b = b; |
275 | |
|
276 | 0 | return fr_time_cmp(ev_a->when, ev_b->when); |
277 | 0 | } |
278 | | |
279 | | |
280 | | /** This callback fires in the parent to execute events in this sublist |
281 | | * |
282 | | * @param[in] parent_tl Parent event timer list. |
283 | | * @param[in] when When the parent timer fired. |
284 | | * @param[in] uctx Sublist to execute. |
285 | | */ |
286 | | static void _parent_timer_cb(UNUSED fr_timer_list_t *parent_tl, fr_time_t when, void *uctx) |
287 | 0 | { |
288 | 0 | fr_timer_list_t *tl = talloc_get_type_abort(uctx, fr_timer_list_t); |
289 | |
|
290 | 0 | fr_assert(!tl->disarmed); |
291 | | |
292 | | /* |
293 | | * We're in the parent timer, so we need to run the |
294 | | * events in the child timer list. |
295 | | */ |
296 | 0 | (void)fr_timer_list_run(tl, &when); |
297 | 0 | } |
298 | | |
299 | | /** Utility function to update parent timers |
300 | | * |
301 | | * @param[in] tl to update parent timers for. |
302 | | * @return |
303 | | * - 0 on success. |
304 | | * - -1 on failure. |
305 | | */ |
306 | | static inline CC_HINT(always_inline) int timer_list_parent_update(fr_timer_list_t *tl) |
307 | 0 | { |
308 | 0 | fr_time_t *when; |
309 | |
|
310 | 0 | if (!tl->parent) return 0; |
311 | | |
312 | 0 | when = timer_list_when(tl); |
313 | | |
314 | | /* |
315 | | * No events, disarm the timer |
316 | | */ |
317 | 0 | if (!when) { |
318 | | /* |
319 | | * Disables the timer in the parent, does not free the memory |
320 | | */ |
321 | 0 | if (tl->parent) FR_TIMER_DISARM_RETURN(tl->parent_ev); |
322 | 0 | return 0; |
323 | 0 | } |
324 | | |
325 | | /* |
326 | | * We have an active event, we can suppress changes which have no effect. |
327 | | */ |
328 | 0 | if (tl->parent_ev && EVENT_ARMED(tl->parent_ev)) { |
329 | 0 | fr_assert(!tl->disarmed); /* fr_timer_list_disarm() must disarm it */ |
330 | |
|
331 | 0 | if (fr_time_eq(*when, tl->parent_ev->when)) { |
332 | 0 | return 0; |
333 | 0 | } |
334 | 0 | } |
335 | | |
336 | | /* |
337 | | * This is a child list which is disabled. Don't update the parent. |
338 | | */ |
339 | 0 | if (tl->disarmed) { |
340 | 0 | fr_assert(tl->parent); |
341 | |
|
342 | 0 | fr_assert(!tl->parent_ev || !EVENT_ARMED(tl->parent_ev)); |
343 | 0 | return 0; |
344 | 0 | } |
345 | | |
346 | | /* |
347 | | * The list is armed and the head has changed, so we change the event in the parent list. |
348 | | */ |
349 | 0 | if (fr_timer_at(tl, tl->parent, &tl->parent_ev, |
350 | 0 | *when, false, _parent_timer_cb, tl) < 0) return -1; |
351 | | |
352 | 0 | return 0; |
353 | 0 | } |
354 | | |
355 | | /** Insert a timer event into a single event timer list |
356 | | * |
357 | | * @param[in] tl to insert the event into. |
358 | | * @param[in] ev to insert. |
359 | | * @return |
360 | | * - 0 on success. |
361 | | * - -1 on failure. |
362 | | */ |
363 | | static int timer_lst_insert_at(fr_timer_list_t *tl, fr_timer_t *ev) |
364 | 0 | { |
365 | 0 | if (unlikely(fr_lst_insert(tl->lst, ev) < 0)) { |
366 | 0 | fr_strerror_const_push("Failed inserting timer into lst"); |
367 | 0 | return -1; |
368 | 0 | } |
369 | | |
370 | 0 | return 0; |
371 | 0 | } |
372 | | |
373 | | /** Insert an event into an ordered timer list |
374 | | * |
375 | | * Timer must be in order, i.e. either before first event, or after last event |
376 | | * |
377 | | * @param[in] tl to insert the event into. |
378 | | * @param[in] ev to insert. |
379 | | * @return |
380 | | * - 0 on success. |
381 | | * - -1 on failure. |
382 | | */ |
383 | | static int timer_ordered_insert_at(fr_timer_list_t *tl, fr_timer_t *ev) |
384 | 0 | { |
385 | 0 | fr_timer_t *tail; |
386 | |
|
387 | 0 | tail = timer_tail(&tl->ordered); |
388 | 0 | if (tail && fr_time_lt(ev->when, tail->when)) { |
389 | 0 | fr_strerror_const("Event being inserted must occurr _after_ the last event"); |
390 | 0 | return -1; |
391 | 0 | } |
392 | | |
393 | 0 | if (unlikely(timer_insert_tail(&tl->ordered, ev) < 0)) { |
394 | 0 | fr_strerror_const_push("Failed inserting timer into ordered list"); |
395 | 0 | return -1; |
396 | 0 | } |
397 | | |
398 | 0 | return 0; |
399 | 0 | } |
400 | | |
401 | | /** Remove an event from the event loop |
402 | | * |
403 | | * @param[in] ev to free. |
404 | | * @return |
405 | | * - 0 on success. |
406 | | * - -1 on failure. |
407 | | */ |
408 | | static int _timer_free(fr_timer_t *ev) |
409 | 0 | { |
410 | 0 | fr_timer_t **ev_p; |
411 | 0 | int ret; |
412 | |
|
413 | 0 | ret = fr_timer_disarm(ev); /* Is a noop if ev->tl == NULL */ |
414 | 0 | if (ret < 0) return ret; |
415 | | |
416 | 0 | CHECK_PARENT(ev); |
417 | 0 | ev_p = ev->parent; |
418 | 0 | *ev_p = NULL; |
419 | |
|
420 | 0 | return 0; |
421 | 0 | } |
422 | | |
423 | | /** Insert a timer event into an event list |
424 | | * |
425 | | * @note The talloc parent of the memory returned in ev_p must not be changed. |
426 | | * If the lifetime of the event needs to be bound to another context |
427 | | * this function should be called with the existing event pointed to by |
428 | | * ev_p. |
429 | | * |
430 | | * @param[in] ctx to bind lifetime of the event to. |
431 | | * @param[in] tl to insert event into. |
432 | | * @param[in,out] ev_p If not NULL modify this event instead of creating a new one. This is a parent |
433 | | * in a temporal sense, not in a memory structure or dependency sense. |
434 | | * @param[in] when we should run the event. |
435 | | * @param[in] free_on_fire Whether event memory should be freed if the event fires. |
436 | | * @param[in] callback function to execute if the event fires. |
437 | | * @param[in] uctx user data to pass to the event. |
438 | | * @return |
439 | | * - 0 on success. |
440 | | * - -1 on failure. |
441 | | */ |
442 | | int _fr_timer_at(NDEBUG_LOCATION_ARGS |
443 | | TALLOC_CTX *ctx, fr_timer_list_t *tl, fr_timer_t **ev_p, |
444 | | fr_time_t when, |
445 | | bool free_on_fire, fr_timer_cb_t callback, void const *uctx) |
446 | 0 | { |
447 | 0 | fr_timer_t *ev; |
448 | |
|
449 | 0 | fr_assert(tl->type != TIMER_LIST_TYPE_SHARED); |
450 | | |
451 | | /* |
452 | | * If there is an event, reuse it instead of freeing it |
453 | | * and allocating a new one. This is to reduce memory |
454 | | * churn for repeat events. |
455 | | */ |
456 | 0 | if (!*ev_p) { |
457 | 0 | new_event: |
458 | 0 | ev = talloc_zero(tl, fr_timer_t); |
459 | 0 | if (unlikely(!ev)) { |
460 | 0 | fr_strerror_const("Out of memory"); |
461 | 0 | return -1; |
462 | 0 | } |
463 | | |
464 | 0 | EVENT_DEBUG("%p - " NDEBUG_LOCATION_FMT "Added new timer %p", tl, NDEBUG_LOCATION_VALS ev); |
465 | | /* |
466 | | * Bind the lifetime of the event to the specified |
467 | | * talloc ctx. If the talloc ctx is freed, the |
468 | | * event will also be freed. |
469 | | */ |
470 | 0 | if (ctx != tl) talloc_link_ctx(ctx, ev); |
471 | |
|
472 | 0 | talloc_set_destructor(ev, _timer_free); |
473 | 0 | } else { |
474 | 0 | ev = talloc_get_type_abort(UNCONST(fr_timer_t *, *ev_p), fr_timer_t); |
475 | |
|
476 | 0 | EVENT_DEBUG("%p - " NDEBUG_LOCATION_FMT "Re-armed timer %p", tl, NDEBUG_LOCATION_VALS ev); |
477 | | |
478 | | /* |
479 | | * We can't disarm the linking context due to |
480 | | * limitations in talloc, so if the linking |
481 | | * context changes, we need to free the old |
482 | | * event, and allocate a new one. |
483 | | * |
484 | | * Freeing the event also removes it from the lst. |
485 | | */ |
486 | 0 | if (unlikely(ev->linked_ctx != ctx)) { |
487 | 0 | talloc_free(ev); |
488 | 0 | goto new_event; |
489 | 0 | } |
490 | | |
491 | | /* |
492 | | * If the event is associated with a list, we need |
493 | | * to disarm it, before we can rearm it. |
494 | | */ |
495 | 0 | if (EVENT_ARMED(ev)) { |
496 | 0 | int ret; |
497 | 0 | char const *err_file; |
498 | 0 | int err_line; |
499 | | |
500 | | /* |
501 | | * Removed event from the event list or the |
502 | | * deferred list. |
503 | | */ |
504 | 0 | ret = fr_timer_disarm(ev); |
505 | 0 | #ifndef NDEBUG |
506 | 0 | err_file = ev->file; |
507 | 0 | err_line = ev->line; |
508 | | #else |
509 | | err_file = "not-available"; |
510 | | err_line = 0; |
511 | | #endif |
512 | | |
513 | | /* |
514 | | * Events MUST be in the lst (or the insertion list). |
515 | | */ |
516 | 0 | if (!fr_cond_assert_msg(ret == 0, |
517 | 0 | "Event %p, allocd %s[%d], was not found in the event " |
518 | 0 | "list or deferred list when re-armed: %s", ev, |
519 | 0 | err_file, err_line, fr_strerror())) return -1; |
520 | 0 | } |
521 | 0 | } |
522 | | |
523 | 0 | ev->tl = tl; /* This indicates the event memory is bound to an event loop */ |
524 | 0 | ev->when = when; |
525 | 0 | ev->free_on_fire = free_on_fire; |
526 | 0 | ev->callback = callback; |
527 | 0 | ev->uctx = uctx; |
528 | 0 | ev->linked_ctx = ctx; |
529 | 0 | ev->parent = ev_p; |
530 | 0 | #ifndef NDEBUG |
531 | 0 | ev->file = file; |
532 | 0 | ev->line = line; |
533 | 0 | #endif |
534 | | |
535 | | /* |
536 | | * No updating needed as the events are deferred |
537 | | */ |
538 | 0 | if (tl->in_handler) { |
539 | | /* |
540 | | * ...a little hacky, but we need to verify that |
541 | | * we're not inserting an event that's earlier |
542 | | * than the last event in the list for ordered |
543 | | * lists. |
544 | | * |
545 | | * Otherwise we'd end up doing this when we tried |
546 | | * to move all the deferred events into the timer |
547 | | * list, and end up making that O(n) instead of O(1). |
548 | | */ |
549 | 0 | if (tl->type == TIMER_LIST_TYPE_ORDERED) { |
550 | 0 | fr_timer_t *head = timer_list_ordered_head(tl); |
551 | |
|
552 | 0 | if (head && fr_time_lt(ev->when, head->when)) { |
553 | 0 | fr_strerror_const("Event being inserted must occurr _after_ the last event"); |
554 | |
|
555 | 0 | insert_failed: |
556 | 0 | talloc_set_destructor(ev, NULL); |
557 | 0 | talloc_free(ev); |
558 | 0 | *ev_p = NULL; |
559 | 0 | return -1; |
560 | 0 | } |
561 | 0 | } |
562 | | |
563 | 0 | if (!fr_cond_assert_msg(timer_insert_tail(&tl->deferred, ev) == 0, |
564 | 0 | "Failed inserting event into deferred list")) { |
565 | 0 | goto insert_failed; |
566 | 0 | } |
567 | 0 | } else { |
568 | 0 | int ret; |
569 | |
|
570 | 0 | ret = timer_funcs[tl->type].insert(tl, ev); |
571 | 0 | if (unlikely(ret < 0)) goto insert_failed; |
572 | | |
573 | | /* |
574 | | * We need to update the parent timer |
575 | | * to ensure it fires at the correct time. |
576 | | */ |
577 | 0 | if (unlikely(timer_list_parent_update(tl) < 0)) return -1; |
578 | 0 | } |
579 | | |
580 | 0 | *ev_p = ev; |
581 | |
|
582 | 0 | return 0; |
583 | 0 | } |
584 | | |
585 | | /** Insert a timer event into an event list |
586 | | * |
587 | | * @note The talloc parent of the memory returned in ev_p must not be changed. |
588 | | * If the lifetime of the event needs to be bound to another context |
589 | | * this function should be called with the existing event pointed to by |
590 | | * ev_p. |
591 | | * |
592 | | * @param[in] ctx to bind lifetime of the event to. |
593 | | * @param[in] tl to insert event into. |
594 | | * @param[in,out] ev_p If not NULL modify this event instead of creating a new one. This is a parent |
595 | | * in a temporal sense, not in a memory structure or dependency sense. |
596 | | * @param[in] delta In how many nanoseconds to wait before should we execute the event. |
597 | | * @param[in] free_on_fire Whether event memory should be freed if the event fires. |
598 | | * @param[in] callback function to execute if the event fires. |
599 | | * @param[in] uctx user data to pass to the event. |
600 | | * @return |
601 | | * - 0 on success. |
602 | | * - -1 on failure. |
603 | | */ |
604 | | int _fr_timer_in(NDEBUG_LOCATION_ARGS |
605 | | TALLOC_CTX *ctx, fr_timer_list_t *tl, fr_timer_t **ev_p, |
606 | | fr_time_delta_t delta, |
607 | | bool free_on_fire, fr_timer_cb_t callback, void const *uctx) |
608 | 0 | { |
609 | 0 | return _fr_timer_at(NDEBUG_LOCATION_VALS |
610 | 0 | ctx, tl, ev_p, fr_time_add(tl->pub.time(), delta), |
611 | 0 | free_on_fire, callback, uctx); |
612 | 0 | } |
613 | | |
614 | | static int timer_lst_disarm(fr_timer_t *ev) |
615 | | { |
616 | | fr_timer_list_t *tl = ev->tl; |
617 | | |
618 | | if (timer_in_list(&tl->deferred,ev)) { |
619 | | (void)timer_remove(&tl->deferred, ev); |
620 | | } else { |
621 | | int ret = fr_lst_extract(tl->lst, ev); |
622 | | char const *err_file; |
623 | | int err_line; |
624 | | |
625 | | #ifndef NDEBUG |
626 | | err_file = ev->file; |
627 | | err_line = ev->line; |
628 | | #else |
629 | | err_file = "not-available"; |
630 | | err_line = 0; |
631 | | #endif |
632 | | |
633 | | |
634 | | /* |
635 | | * Events MUST be in the lst (or the insertion list). |
636 | | */ |
637 | | if (!fr_cond_assert_msg(ret == 0, |
638 | | "Event %p, lst_id %u, allocd %s[%d], was not found in the event lst or " |
639 | | "insertion list when freed: %s", ev, ev->lst_idx, err_file, err_line, |
640 | | fr_strerror())) return -1; |
641 | | } |
642 | | |
643 | | return 0; |
644 | | } |
645 | | |
646 | | /** Remove a timer from a timer list, but don't free it |
647 | | * |
648 | | * @param[in] ev to remove. |
649 | | */ |
650 | | static int timer_ordered_disarm(fr_timer_t *ev) |
651 | | { |
652 | | /* |
653 | | * Check the check is still valid (sanity check) |
654 | | */ |
655 | | (void)talloc_get_type_abort(ev, fr_timer_t); |
656 | | |
657 | | /* |
658 | | * Already dissassociated from a list, nothing to do. |
659 | | */ |
660 | | if (!ev->tl) return 0; |
661 | | |
662 | | /* |
663 | | * This *MUST* be in a timer list if it has a non-NULL tl pointer. |
664 | | */ |
665 | | if (unlikely(!fr_cond_assert(timer_in_list(&ev->tl->ordered, ev)))) return -1; |
666 | | |
667 | | (void)timer_remove(&ev->tl->ordered, ev); |
668 | | |
669 | | return 0; |
670 | | } |
671 | | |
672 | | /** Remove an event from the event list, but don't free the memory |
673 | | * |
674 | | * @param[in] ev to remove from the event list. |
675 | | */ |
676 | | int fr_timer_disarm(fr_timer_t *ev) |
677 | 0 | { |
678 | 0 | fr_timer_list_t *tl; |
679 | |
|
680 | 0 | if (!ev || !EVENT_ARMED(ev)) { |
681 | 0 | EVENT_DEBUG("Asked to disarm inactive timer %p (noop)", ev); |
682 | 0 | return 0; /* Noop */ |
683 | 0 | } |
684 | | |
685 | 0 | tl = ev->tl; |
686 | |
|
687 | 0 | EVENT_DEBUG("Disarming timer %p", ev); |
688 | |
|
689 | 0 | CHECK_PARENT(ev); |
690 | | |
691 | | /* |
692 | | * If the event is deferred, it's not in the event list proper |
693 | | * so just remove it, and set the tl pointer to NULL. |
694 | | */ |
695 | 0 | if (timer_in_list(&tl->deferred,ev)) { |
696 | 0 | (void)timer_remove(&tl->deferred, ev); |
697 | 0 | } else { |
698 | 0 | int ret = timer_funcs[ev->tl->type].disarm(ev); |
699 | 0 | if (ret < 0) return ret; |
700 | 0 | } |
701 | 0 | ev->tl = NULL; |
702 | |
|
703 | 0 | return timer_list_parent_update(tl); |
704 | 0 | } |
705 | | |
706 | | /** Delete a timer event and free its memory |
707 | | * |
708 | | * @param[in] ev_p of the event being deleted. |
709 | | * @return |
710 | | * - 0 on success. |
711 | | * - -1 on failure. |
712 | | */ |
713 | | int fr_timer_delete(fr_timer_t **ev_p) |
714 | 0 | { |
715 | 0 | fr_timer_t *ev; |
716 | 0 | int ret; |
717 | |
|
718 | 0 | if (unlikely(!*ev_p)) return 0; |
719 | | |
720 | 0 | ev = *ev_p; |
721 | 0 | ret = talloc_free(ev); /* Destructor removed event from any lists */ |
722 | | |
723 | | /* |
724 | | * Don't leave a garbage pointer value |
725 | | * if parent is not ev_p. |
726 | | */ |
727 | 0 | if (likely(ret == 0)) { |
728 | 0 | *ev_p = NULL; |
729 | 0 | return 0; |
730 | 0 | } |
731 | | |
732 | 0 | EVENT_DEBUG("Deleting timer %p failed: %s", ev, fr_strerror_peek()); |
733 | 0 | return -1; |
734 | 0 | } |
735 | | |
736 | | /** Internal timestamp representing when the timer should fire |
737 | | * |
738 | | * @return When the timestamp should fire. |
739 | | */ |
740 | | fr_time_t fr_timer_when(fr_timer_t *ev) |
741 | 0 | { |
742 | 0 | if (!fr_timer_armed(ev)) return fr_time_wrap(0); |
743 | 0 | return ev->when; |
744 | 0 | } |
745 | | |
746 | | /** Return time delta between now and when the timer should fire |
747 | | * |
748 | | * @param[in] ev to get the time delta for. |
749 | | */ |
750 | | fr_time_delta_t fr_timer_remaining(fr_timer_t *ev) |
751 | 0 | { |
752 | 0 | if (!fr_timer_armed(ev)) return fr_time_delta_wrap(0); |
753 | 0 | return fr_time_sub(ev->when, ev->tl->pub.time()); |
754 | 0 | } |
755 | | |
756 | | /** Check if a timer event is armed |
757 | | * |
758 | | * @param[in] ev to check. |
759 | | * @return |
760 | | * - true if the event is armed. |
761 | | * - false if the event is not armed. |
762 | | */ |
763 | | bool _fr_timer_armed(fr_timer_t *ev) |
764 | 0 | { |
765 | 0 | return EVENT_ARMED(ev); |
766 | 0 | } |
767 | | |
768 | | /** Run all scheduled timer events in a lst |
769 | | * |
770 | | * @param[in] tl containing the timer events. |
771 | | * @param[in] when Process events scheduled to run before or at this time. |
772 | | * - Set to 0 if no more events. |
773 | | * - Set to the next event time if there are more events. |
774 | | * @return |
775 | | * - 0 no timer events fired. |
776 | | * - 1 a timer event fired. |
777 | | */ |
778 | | CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private fr_timer_list_t trips --fsanitize=function*/ |
779 | | static int timer_list_lst_run(fr_timer_list_t *tl, fr_time_t *when) |
780 | 0 | { |
781 | 0 | fr_timer_cb_t callback; |
782 | 0 | void *uctx; |
783 | 0 | void *item; |
784 | 0 | fr_timer_t *ev; |
785 | 0 | int fired = 0; |
786 | |
|
787 | 0 | while (fr_lst_num_elements(tl->lst) > 0) { |
788 | 0 | fr_lst_peek(&item, tl->lst); |
789 | 0 | ev = talloc_get_type_abort(item, fr_timer_t); |
790 | |
|
791 | 0 | TIMER_STOP_IF_DONE(ev->when); |
792 | | |
793 | 0 | callback = ev->callback; |
794 | 0 | memcpy(&uctx, &ev->uctx, sizeof(uctx)); |
795 | |
|
796 | 0 | CHECK_PARENT(ev); |
797 | | |
798 | | /* |
799 | | * Disarm the event before calling it. |
800 | | * |
801 | | * This leaves the memory in place, |
802 | | * but dissassociates it from the list. |
803 | | * |
804 | | * We use the public function as it |
805 | | * handles more cases. |
806 | | */ |
807 | 0 | if (!fr_cond_assert(fr_timer_disarm(ev) == 0)) return -2; |
808 | 0 | EVENT_DEBUG("Running timer %p", ev); |
809 | 0 | if (ev->free_on_fire) talloc_free(ev); |
810 | |
|
811 | 0 | callback(tl, *when, uctx); |
812 | |
|
813 | 0 | fired++; |
814 | 0 | } |
815 | | |
816 | 0 | TIMER_RETURN_DONE; |
817 | 0 | } |
818 | | |
819 | | /** Run all scheduled events in an ordered list |
820 | | * |
821 | | * @param[in] tl containing the timer events. |
822 | | * @param[in] when Process events scheduled to run before or at this time. |
823 | | * - Set to 0 if no more events. |
824 | | * - Set to the next event time if there are more events. |
825 | | * @return |
826 | | * - < 0 if we failed to updated the parent list. |
827 | | * - 0 no timer events fired. |
828 | | * - >0 number of timer event fired. |
829 | | */ |
830 | | CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private fr_timer_list_t trips --fsanitize=function*/ |
831 | | static int timer_list_ordered_run(fr_timer_list_t *tl, fr_time_t *when) |
832 | 0 | { |
833 | 0 | fr_timer_cb_t callback; |
834 | 0 | void *uctx; |
835 | 0 | fr_timer_t *ev; |
836 | 0 | unsigned int fired = 0; |
837 | |
|
838 | 0 | while ((ev = timer_head(&tl->ordered))) { |
839 | 0 | (void)talloc_get_type_abort(ev, fr_timer_t); |
840 | |
|
841 | 0 | TIMER_STOP_IF_DONE(ev->when); |
842 | | |
843 | 0 | callback = ev->callback; |
844 | 0 | memcpy(&uctx, &ev->uctx, sizeof(uctx)); |
845 | |
|
846 | 0 | CHECK_PARENT(ev); |
847 | | |
848 | | /* |
849 | | * Disarm the event before calling it. |
850 | | * |
851 | | * This leaves the memory in place, |
852 | | * but dissassociates it from the list. |
853 | | * |
854 | | * We use the public function as it |
855 | | * handles more cases. |
856 | | */ |
857 | 0 | if (!fr_cond_assert(fr_timer_disarm(ev) == 0)) return -2; |
858 | 0 | EVENT_DEBUG("Running timer %p", ev); |
859 | 0 | if (ev->free_on_fire) talloc_free(ev); |
860 | |
|
861 | 0 | callback(tl, *when, uctx); |
862 | |
|
863 | 0 | fired++; |
864 | 0 | } |
865 | | |
866 | 0 | TIMER_RETURN_DONE; |
867 | 0 | } |
868 | | |
869 | | /** Run all scheduled events in an ordered list |
870 | | * |
871 | | * @param[in] tl containing the timer events. |
872 | | * @param[in] when Process events scheduled to run before or at this time. |
873 | | * - Set to 0 if no more events. |
874 | | * - Set to the next event time if there are more events. |
875 | | * @return |
876 | | * - < 0 if we failed to updated the parent list. |
877 | | * - 0 no timer events fired. |
878 | | * - >0 number of timer event fired. |
879 | | */ |
880 | | CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private fr_timer_list_t trips --fsanitize=function*/ |
881 | | static int timer_list_shared_run(fr_timer_list_t *tl, fr_time_t *when) |
882 | 0 | { |
883 | 0 | void *uctx; |
884 | 0 | unsigned int fired = 0; |
885 | |
|
886 | 0 | while ((uctx = fr_rb_first(tl->shared.rb)) != NULL) { |
887 | 0 | fr_time_t const *next; |
888 | |
|
889 | 0 | next = TIMER_UCTX_TO_TIME(tl, uctx); |
890 | |
|
891 | 0 | TIMER_STOP_IF_DONE(*next); |
892 | | |
893 | 0 | fr_rb_remove(NULL, tl->shared.rb, uctx); |
894 | |
|
895 | 0 | tl->shared.callback(tl, *when, uctx); |
896 | |
|
897 | 0 | fired++; |
898 | 0 | } |
899 | | |
900 | 0 | TIMER_RETURN_DONE; |
901 | 0 | } |
902 | | |
903 | | |
904 | | /** Forcibly run all events in an event loop. |
905 | | * |
906 | | * This is used to forcefully run every event in the event loop. |
907 | | * |
908 | | * We pass in the real time, which may theoretically cause issues if timer |
909 | | * callbacks are checking... But the uses of this function are very limited. |
910 | | * |
911 | | * @return |
912 | | * - < 0 if we failed to update the parent list. |
913 | | * - 0 no timer events fired. |
914 | | * - > 0 number of timer event fired. |
915 | | */ |
916 | | int fr_timer_list_force_run(fr_timer_list_t *tl) |
917 | 0 | { |
918 | 0 | fr_time_t when = fr_time_max(); |
919 | |
|
920 | 0 | return fr_timer_list_run(tl, &when); |
921 | 0 | } |
922 | | |
923 | | /** Execute any pending events in the event loop |
924 | | * |
925 | | * @param[in] tl to execute events in. |
926 | | * @param[in] when Process events scheduled to run before or at this time. |
927 | | * - Set to 0 if no more events. |
928 | | * - Set to the next event time if there are more events. |
929 | | * @return |
930 | | * - < 0 if we failed to update the parent list. |
931 | | * - 0 no timer events fired. |
932 | | * - >0 number of timer event fired. |
933 | | */ |
934 | | int fr_timer_list_run(fr_timer_list_t *tl, fr_time_t *when) |
935 | 0 | { |
936 | 0 | int ret; |
937 | 0 | bool in_handler = tl->in_handler; /* allow nested timer execution */ |
938 | |
|
939 | 0 | tl->in_handler = true; |
940 | 0 | ret = timer_funcs[tl->type].run(tl, when); |
941 | 0 | tl->in_handler = in_handler; |
942 | | |
943 | | /* |
944 | | * Now we've executed all the pending events, |
945 | | * now merge the deferred events into the main |
946 | | * event list. |
947 | | * |
948 | | * The events don't need to be modified as they |
949 | | * were initialised completely before being |
950 | | * placed in the deferred list. |
951 | | */ |
952 | 0 | if (timer_num_elements(&tl->deferred) > 0) { |
953 | 0 | if (unlikely(timer_funcs[tl->type].deferred(tl) < 0)) return -1; |
954 | 0 | if (unlikely(timer_list_parent_update(tl) < 0)) return -1; |
955 | | /* |
956 | | * We ran some events, and have no deferred |
957 | | * events to insert, so we need to forcefully |
958 | | * update the parent timer. |
959 | | */ |
960 | 0 | } else if(ret > 0) { |
961 | 0 | if (unlikely(timer_list_parent_update(tl) < 0)) return -1; |
962 | 0 | } |
963 | | |
964 | 0 | return ret; |
965 | 0 | } |
966 | | |
967 | | /** Return the head of the lst |
968 | | * |
969 | | * @param[in] tl to get the head of. |
970 | | * @return |
971 | | * - The head of the trie. |
972 | | * - NULL, if there's no head. |
973 | | */ |
974 | | static fr_timer_t *timer_list_lst_head(fr_timer_list_t *tl) |
975 | 0 | { |
976 | 0 | fr_timer_t *ev; |
977 | |
|
978 | 0 | fr_lst_peek((void **)&ev, tl->lst); |
979 | |
|
980 | 0 | return ev; |
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) != 0) return -1; |
1353 | | |
1354 | 0 | return 0; |
1355 | 0 | } |
1356 | | |
1357 | 0 | if (fr_rb_insert(tl->shared.rb, uctx) != 0) 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 fr_cmp_ret_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 | | fr_rb_find((void **)&counter, 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 |