/src/freeradius-server/src/lib/util/atexit.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 | | /** Macros to abstract Thread Local Storage |
18 | | * |
19 | | * Simplifies calling thread local destructors (called when the thread exits). |
20 | | * |
21 | | * @file lib/util/atexit.c |
22 | | * |
23 | | * @copyright 2020-2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
24 | | * @copyright 2020 The FreeRADIUS server project |
25 | | */ |
26 | | |
27 | | RCSID("$Id: 725c94ff00ea2c69d0031b064024a2766c0a4231 $") |
28 | | |
29 | | #include <freeradius-devel/util/debug.h> |
30 | | #include <freeradius-devel/util/dlist.h> |
31 | | #include <freeradius-devel/util/atexit.h> |
32 | | |
33 | | #ifdef HAVE_PTHREADS |
34 | | #include <pthread.h> |
35 | | #endif |
36 | | |
37 | | #if defined(DEBUG_ATEXIT) && !defined(NDEBUG) |
38 | | # define ATEXIT_DEBUG FR_FAULT_LOG |
39 | | #else |
40 | | # define ATEXIT_DEBUG(...) |
41 | | #endif |
42 | | |
43 | | typedef struct fr_exit_handler_list_s fr_atexit_list_t; |
44 | | |
45 | | /** Entry in exit handler list |
46 | | * |
47 | | */ |
48 | | typedef struct { |
49 | | fr_dlist_t entry; //!< Entry in the handler dlist. |
50 | | fr_atexit_list_t *list; //!< List this entry is in. |
51 | | |
52 | | fr_atexit_t func; //!< Function to call. |
53 | | void *uctx; //!< uctx to pass. |
54 | | |
55 | | char const *file; //!< File where this exit handler was added. |
56 | | int line; //!< Line where this exit handler was added. |
57 | | } fr_atexit_entry_t; |
58 | | |
59 | | /** Head of a list of exit handlers |
60 | | * |
61 | | */ |
62 | | struct fr_exit_handler_list_s { |
63 | | fr_dlist_head_t head; //!< Head of the list of destructors |
64 | | |
65 | | pthread_key_t key; //!< Key used to trigger thread local destructors. |
66 | | fr_atexit_entry_t *e; //!< Inserted into the global exit handler list |
67 | | ///< to ensure this memory is cleaned up. |
68 | | }; |
69 | | |
70 | | #ifdef HAVE_PTHREADS |
71 | | static _Thread_local fr_atexit_list_t *fr_atexit_thread_local = NULL; |
72 | | static fr_atexit_list_t *fr_atexit_threads = NULL; |
73 | | static pthread_mutex_t fr_atexit_global_mutex = PTHREAD_MUTEX_INITIALIZER; |
74 | | #endif |
75 | | |
76 | | static fr_atexit_list_t *fr_atexit_global = NULL; |
77 | | static bool is_exiting; |
78 | | static _Thread_local bool thread_is_exiting; |
79 | | |
80 | | /** Call the exit handler |
81 | | * |
82 | | */ |
83 | | static int _atexit_entry_free(fr_atexit_entry_t *e) |
84 | 164 | { |
85 | 164 | ATEXIT_DEBUG("%s - Thread %u freeing %p/%p func=%p, uctx=%p (alloced %s:%d)", |
86 | 164 | __FUNCTION__, (unsigned int)pthread_self(), |
87 | 164 | e->list, e, e->func, e->uctx, e->file, e->line); |
88 | | |
89 | 164 | if (fr_dlist_entry_in_list(&e->entry)) fr_dlist_remove(&e->list->head, e); |
90 | | |
91 | | /* |
92 | | * If the exit handler wasn't disarmed, call it... |
93 | | */ |
94 | 164 | if (e->func) e->func(e->uctx); |
95 | | |
96 | 164 | return 0; |
97 | 164 | } |
98 | | |
99 | | /** Allocate a new exit handler entry |
100 | | * |
101 | | */ |
102 | | static fr_atexit_entry_t *atexit_entry_alloc(char const *file, int line, |
103 | | fr_atexit_list_t *list, |
104 | | fr_atexit_t func, void const *uctx) |
105 | 180 | { |
106 | 180 | fr_atexit_entry_t *e; |
107 | | |
108 | 180 | e = talloc_zero(list, fr_atexit_entry_t); |
109 | 180 | if (unlikely(!e)) return NULL; |
110 | | |
111 | 180 | e->list = list; |
112 | 180 | e->func = func; |
113 | 180 | e->uctx = UNCONST(void *, uctx); |
114 | 180 | e->file = file; |
115 | 180 | e->line = line; |
116 | | |
117 | 180 | ATEXIT_DEBUG("%s - Thread %u arming %p/%p func=%p, uctx=%p (alloced %s:%d)", |
118 | 180 | __FUNCTION__, (unsigned int)pthread_self(), |
119 | 180 | list, e, e->func, e->uctx, e->file, e->line); |
120 | | |
121 | 180 | fr_dlist_insert_head(&list->head, e); |
122 | 180 | talloc_set_destructor(e, _atexit_entry_free); |
123 | | |
124 | 180 | return e; |
125 | 180 | } |
126 | | |
127 | | /** Talloc destructor for freeing list elements in order |
128 | | * |
129 | | */ |
130 | | static int _destructor_list_free(fr_atexit_list_t *list) |
131 | 76 | { |
132 | 76 | ATEXIT_DEBUG("%s - Freeing destructor list %p", __FUNCTION__, list); |
133 | | |
134 | 76 | fr_dlist_talloc_free(&list->head); /* Free in order */ |
135 | 76 | return 0; |
136 | 76 | } |
137 | | |
138 | | /** Free any thread-local exit handler lists that pthread_key failed to fre |
139 | | * |
140 | | */ |
141 | | static void _global_free(void) |
142 | 38 | { |
143 | 38 | #ifdef HAVE_PTHREADS |
144 | 38 | pthread_mutex_lock(&fr_atexit_global_mutex); |
145 | 38 | #endif |
146 | | |
147 | 38 | fr_cond_assert_msg(!is_exiting, "Global free function called multiple times"); |
148 | 38 | is_exiting = true; |
149 | | |
150 | 38 | #ifdef HAVE_PTHREADS |
151 | 38 | pthread_mutex_unlock(&fr_atexit_global_mutex); |
152 | 38 | TALLOC_FREE(fr_atexit_threads); /* Forcefully cleanup any thread-specific memory */ |
153 | 38 | #endif |
154 | 38 | TALLOC_FREE(fr_atexit_global); |
155 | 38 | } |
156 | | |
157 | | /** Setup the atexit handler, should be called at the start of a program's execution |
158 | | * |
159 | | */ |
160 | | int fr_atexit_global_setup(void) |
161 | 38 | { |
162 | 38 | if (fr_atexit_global) return 0; |
163 | | |
164 | 38 | fr_atexit_global = talloc_zero(NULL, fr_atexit_list_t); |
165 | 38 | if (unlikely(!fr_atexit_global)) return -1; |
166 | | |
167 | 38 | ATEXIT_DEBUG("%s - Alloced global destructor list %p", __FUNCTION__, fr_atexit_global); |
168 | | |
169 | 38 | fr_dlist_talloc_init(&fr_atexit_global->head, fr_atexit_entry_t, entry); |
170 | 38 | talloc_set_destructor(fr_atexit_global, _destructor_list_free); |
171 | | |
172 | 38 | #ifdef HAVE_PTHREADS |
173 | 38 | fr_atexit_threads = talloc_zero(NULL, fr_atexit_list_t); |
174 | 38 | if (unlikely(!fr_atexit_threads)) return -1; |
175 | | |
176 | 38 | ATEXIT_DEBUG("%s - Alloced threads destructor list %p", __FUNCTION__, fr_atexit_threads); |
177 | | |
178 | 38 | fr_dlist_talloc_init(&fr_atexit_threads->head, fr_atexit_entry_t, entry); |
179 | 38 | talloc_set_destructor(fr_atexit_threads, _destructor_list_free); |
180 | 38 | #endif |
181 | | |
182 | 38 | atexit(_global_free); /* Call all remaining destructors at process exit */ |
183 | | |
184 | 38 | return 0; |
185 | 38 | } |
186 | | |
187 | | #ifdef HAVE_PTHREADS |
188 | 142 | #define CHECK_GLOBAL_SETUP() \ |
189 | 142 | do { \ |
190 | 142 | int _ret = 0; \ |
191 | 142 | pthread_mutex_lock(&fr_atexit_global_mutex); \ |
192 | 142 | fr_cond_assert_msg(!is_exiting, "New atexit handlers should not be allocated whilst exiting"); \ |
193 | 142 | if (!fr_atexit_global) _ret = fr_atexit_global_setup(); \ |
194 | 142 | pthread_mutex_unlock(&fr_atexit_global_mutex); \ |
195 | 142 | if (_ret < 0) return _ret; \ |
196 | 142 | } while(0) |
197 | | #else |
198 | | #define CHECK_GLOBAL_SETUP() \ |
199 | | do { \ |
200 | | int _ret = 0; \ |
201 | | fr_cond_assert_msg(!is_exiting, "New atexit handlers should not be allocated whilst exiting"); \ |
202 | | if (!fr_atexit_global) _ret = fr_atexit_global_setup(); \ |
203 | | if (_ret < 0) return _ret; \ |
204 | | } while(0) |
205 | | #endif |
206 | | |
207 | | /** Add a free function to be called when the process exits |
208 | | * |
209 | | */ |
210 | | int _atexit_global(char const *file, int line, fr_atexit_t func, void const *uctx) |
211 | 70 | { |
212 | 70 | CHECK_GLOBAL_SETUP(); |
213 | | |
214 | 70 | if (unlikely(atexit_entry_alloc(file, line, fr_atexit_global, func, uctx) == NULL)) return -1; |
215 | | |
216 | 70 | return 0; |
217 | 70 | } |
218 | | |
219 | | /** Remove a specific global destructor (without executing it) |
220 | | * |
221 | | * @note This function's primary purpose is to help diagnose issues with destructors |
222 | | * from within a debugger. |
223 | | * |
224 | | * @param[in] uctx_scope Only process entries where the func and scope both match. |
225 | | * @param[in] func Entries matching this function will be disarmed. |
226 | | * @param[in] uctx associated with the entry. |
227 | | * @return How many global destructors were disarmed. |
228 | | */ |
229 | | unsigned int fr_atexit_global_disarm(bool uctx_scope, fr_atexit_t func, void const *uctx) |
230 | 32 | { |
231 | 32 | fr_atexit_entry_t *e = NULL; |
232 | 32 | unsigned int count = 0; |
233 | | |
234 | 96 | while ((e = fr_dlist_next(&fr_atexit_global->head, e))) { |
235 | 64 | fr_atexit_entry_t *disarm; |
236 | | |
237 | 64 | if ((e->func != func) || ((e->uctx != uctx) && uctx_scope)) continue; |
238 | | |
239 | 16 | ATEXIT_DEBUG("%s - Disarming %p/%p func=%p, uctx=%p (alloced %s:%d)", |
240 | 16 | __FUNCTION__, |
241 | 16 | fr_atexit_global, e, e->func, e->uctx, e->file, e->line); |
242 | | |
243 | 16 | disarm = e; |
244 | 16 | e = fr_dlist_remove(&fr_atexit_global->head, e); |
245 | 16 | talloc_set_destructor(disarm, NULL); |
246 | 16 | talloc_free(disarm); |
247 | | |
248 | 16 | count++; |
249 | 16 | } |
250 | | |
251 | 32 | return count; |
252 | 32 | } |
253 | | |
254 | | /** Remove all global destructors (without executing them) |
255 | | * |
256 | | * @note This function's primary purpose is to help diagnose issues with destructors |
257 | | * from within a debugger. |
258 | | */ |
259 | | void fr_atexit_global_disarm_all(void) |
260 | 0 | { |
261 | 0 | fr_atexit_entry_t *e = NULL; |
262 | |
|
263 | 0 | if (!fr_atexit_global) return; |
264 | | |
265 | 0 | while ((e = fr_dlist_pop_head(&fr_atexit_global->head))) { |
266 | 0 | ATEXIT_DEBUG("%s - Disarming %p/%p func=%p, uctx=%p (alloced %s:%d)", |
267 | 0 | __FUNCTION__, |
268 | 0 | fr_atexit_global, e, e->func, e->uctx, e->file, e->line); |
269 | |
|
270 | 0 | talloc_set_destructor(e, NULL); |
271 | 0 | talloc_free(e); |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | /** Cause all global free triggers to fire |
276 | | * |
277 | | * This is necessary when libraries (perl) register their own |
278 | | * atexit handlers using the normal POSIX mechanism, and we need |
279 | | * to ensure all our atexit handlers fire before so any global |
280 | | * deinit is done explicitly by us. |
281 | | * |
282 | | * @return |
283 | | * - >= 0 The number of atexit handlers triggered on success. |
284 | | * - <0 the return code from any atexit handlers that returned an error. |
285 | | */ |
286 | | int fr_atexit_global_trigger_all(void) |
287 | 38 | { |
288 | 38 | fr_atexit_entry_t *e = NULL, *to_free; |
289 | 38 | unsigned int count = 0; |
290 | | |
291 | | /* |
292 | | * Iterate over the list of thread local |
293 | | * destructor lists running the |
294 | | * destructors. |
295 | | */ |
296 | 92 | while ((e = fr_dlist_next(&fr_atexit_global->head, e))) { |
297 | 54 | ATEXIT_DEBUG("%s - Triggering %p/%p func=%p, uctx=%p (alloced %s:%d)", |
298 | 54 | __FUNCTION__, |
299 | 54 | fr_atexit_global, e, e->func, e->uctx, e->file, e->line); |
300 | | |
301 | 54 | count++; |
302 | 54 | to_free = e; |
303 | 54 | e = fr_dlist_remove(&fr_atexit_global->head, e); |
304 | 54 | if (talloc_free(to_free) < 0) { |
305 | 0 | fr_strerror_printf_push("atexit handler failed %p/%p func=%p, uctx=%p" |
306 | 0 | " (alloced %s:%d)", |
307 | 0 | fr_atexit_global, to_free, |
308 | 0 | to_free->func, to_free->uctx, |
309 | 0 | to_free->file, to_free->line); |
310 | 0 | return -1; |
311 | 0 | } |
312 | 54 | } |
313 | | |
314 | 38 | return count; |
315 | 38 | } |
316 | | |
317 | | /** Iterates through all thread local destructor lists, causing destructor to be triggered |
318 | | * |
319 | | * This should only be called by the main process not by threads. |
320 | | * |
321 | | * The main purpose of the function is to force cleanups at a specific time for problematic |
322 | | * destructors. |
323 | | * |
324 | | * @param[in] uctx_scope Only process entries where the func and scope both match. |
325 | | * @param[in] func Entries matching this function will be triggered. |
326 | | * @param[in] uctx associated with the entry. |
327 | | * @return |
328 | | * - >= 0 The number of atexit handlers triggered on success. |
329 | | * - <0 the return code from any atexit handlers that returned an error. |
330 | | */ |
331 | | int fr_atexit_trigger(bool uctx_scope, fr_atexit_t func, void const *uctx) |
332 | 0 | { |
333 | 0 | fr_atexit_entry_t *e = NULL, *to_free; |
334 | 0 | #ifdef HAVE_PTHREADS |
335 | 0 | fr_atexit_entry_t *ee; |
336 | 0 | fr_atexit_list_t *list; |
337 | 0 | #endif |
338 | 0 | unsigned int count = 0; |
339 | |
|
340 | 0 | if (!fr_atexit_global) goto do_threads; |
341 | | |
342 | | /* |
343 | | * Iterate over the global destructors |
344 | | */ |
345 | 0 | while ((e = fr_dlist_next(&fr_atexit_global->head, e))) { |
346 | 0 | if ((e->func != func) || ((e->uctx != uctx) && uctx_scope)) continue; |
347 | | |
348 | 0 | ATEXIT_DEBUG("%s - Triggering %p/%p func=%p, uctx=%p (alloced %s:%d)", |
349 | 0 | __FUNCTION__, |
350 | 0 | fr_atexit_global, e, e->func, e->uctx, e->file, e->line); |
351 | |
|
352 | 0 | count++; |
353 | 0 | to_free = e; |
354 | 0 | e = fr_dlist_remove(&fr_atexit_global->head, e); |
355 | 0 | if (talloc_free(to_free) < 0) { |
356 | 0 | fr_strerror_printf_push("atexit handler failed %p/%p func=%p, uctx=%p" |
357 | 0 | " (alloced %s:%d)", |
358 | 0 | fr_atexit_global, to_free, |
359 | 0 | to_free->func, to_free->uctx, |
360 | 0 | to_free->file, to_free->line); |
361 | 0 | return -1; |
362 | 0 | } |
363 | 0 | } |
364 | 0 | e = NULL; |
365 | |
|
366 | 0 | do_threads: |
367 | 0 | #ifdef HAVE_PTHREADS |
368 | 0 | if (!fr_atexit_threads) return 0; |
369 | | |
370 | | /* |
371 | | * Iterate over the list of thread local |
372 | | * destructor lists running the |
373 | | * destructors. |
374 | | */ |
375 | 0 | while ((e = fr_dlist_next(&fr_atexit_threads->head, e))) { |
376 | 0 | if (!e->func) continue; /* thread already joined */ |
377 | | |
378 | 0 | list = talloc_get_type_abort(e->uctx, fr_atexit_list_t); |
379 | 0 | ee = NULL; |
380 | 0 | while ((ee = fr_dlist_next(&list->head, ee))) { |
381 | 0 | if ((ee->func != func) || ((ee->uctx != uctx) && uctx_scope)) continue; |
382 | | |
383 | 0 | ATEXIT_DEBUG("%s - Thread %u triggering %p/%p func=%p, uctx=%p (alloced %s:%d)", |
384 | 0 | __FUNCTION__, |
385 | 0 | (unsigned int)pthread_self(), |
386 | 0 | list, ee, ee->func, ee->uctx, ee->file, ee->line); |
387 | |
|
388 | 0 | count++; |
389 | 0 | to_free = ee; |
390 | 0 | ee = fr_dlist_remove(&list->head, ee); |
391 | 0 | if (talloc_free(to_free) < 0) { |
392 | 0 | fr_strerror_printf_push("atexit handler failed %p/%p func=%p, uctx=%p" |
393 | 0 | " (alloced %s:%d)", |
394 | 0 | list, to_free, |
395 | 0 | to_free->func, to_free->uctx, |
396 | 0 | to_free->file, to_free->line); |
397 | 0 | return -1; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | } |
401 | 0 | #endif |
402 | | |
403 | 0 | return count; |
404 | 0 | } |
405 | | |
406 | | |
407 | | /** Return whether we're currently in the teardown phase |
408 | | * |
409 | | * When this function returns true no more thread local or global |
410 | | * destructors can be added. |
411 | | */ |
412 | | bool fr_atexit_is_exiting(void) |
413 | 0 | { |
414 | 0 | #ifdef HAVE_PTHREADS |
415 | 0 | bool save_is_exiting; |
416 | |
|
417 | 0 | pthread_mutex_lock(&fr_atexit_global_mutex); |
418 | 0 | save_is_exiting = is_exiting; |
419 | 0 | pthread_mutex_unlock(&fr_atexit_global_mutex); |
420 | |
|
421 | 0 | return save_is_exiting; |
422 | | #else |
423 | | return is_exiting; |
424 | | #endif |
425 | 0 | } |
426 | | |
427 | | #ifdef HAVE_PTHREADS |
428 | | /** Talloc destructor for freeing list elements in order |
429 | | * |
430 | | */ |
431 | | static int _thread_local_list_free(fr_atexit_list_t *list) |
432 | 38 | { |
433 | 38 | ATEXIT_DEBUG("%s - Freeing _Thread_local destructor list %p", __FUNCTION__, list); |
434 | | |
435 | 38 | fr_dlist_talloc_free(&list->head); /* Free in order */ |
436 | 38 | pthread_mutex_lock(&fr_atexit_global_mutex); |
437 | 38 | list->e->func = NULL; /* Disarm the global entry that'd free the thread-specific list */ |
438 | 38 | pthread_mutex_unlock(&fr_atexit_global_mutex); |
439 | 38 | return 0; |
440 | 38 | } |
441 | | |
442 | | /** Run all the thread local destructors |
443 | | * |
444 | | * @param[in] list The thread-specific exit handler list. |
445 | | */ |
446 | | static void _thread_local_pthread_free(void *list) |
447 | 0 | { |
448 | 0 | talloc_free(list); |
449 | 0 | } |
450 | | |
451 | | /** Run all the thread local destructors |
452 | | * |
453 | | * @param[in] list The thread-specific exit handler list. |
454 | | */ |
455 | | static int _thread_local_free(void *list) |
456 | 38 | { |
457 | 38 | thread_is_exiting = true; |
458 | 38 | return talloc_free(list); |
459 | 38 | } |
460 | | |
461 | | /** Add a new destructor |
462 | | * |
463 | | * @return |
464 | | * - 0 on success. |
465 | | * - -1 on memory allocation failure; |
466 | | */ |
467 | | int _fr_atexit_thread_local(char const *file, int line, |
468 | | fr_atexit_t func, void const *uctx) |
469 | 72 | { |
470 | 72 | CHECK_GLOBAL_SETUP(); |
471 | | |
472 | | /* |
473 | | * Initialise the thread local list, just for pthread_exit(). |
474 | | */ |
475 | 72 | if (!fr_atexit_thread_local) { |
476 | 38 | fr_atexit_list_t *list; |
477 | | |
478 | | /* |
479 | | * Must be heap allocated, because thread local |
480 | | * structures can be freed before the key |
481 | | * destructor is run (depending on platform). |
482 | | */ |
483 | 38 | list = talloc_zero(NULL, fr_atexit_list_t); |
484 | 38 | if (unlikely(!list)) return -1; |
485 | | |
486 | 38 | ATEXIT_DEBUG("%s - Thread %u alloced _Thread_local destructor list %p", |
487 | 38 | __FUNCTION__, |
488 | 38 | (unsigned int)pthread_self(), list); |
489 | | |
490 | 38 | fr_dlist_talloc_init(&list->head, fr_atexit_entry_t, entry); |
491 | 38 | (void) pthread_key_create(&list->key, _thread_local_pthread_free); |
492 | | |
493 | | /* |
494 | | * We need to pass in a pointer to the heap |
495 | | * memory because, again, the thread local |
496 | | * indirection table may have disappeared |
497 | | * by the time the thread destructor is |
498 | | * called. |
499 | | */ |
500 | 38 | (void) pthread_setspecific(list->key, list); |
501 | 38 | talloc_set_destructor(list, _thread_local_list_free); |
502 | | |
503 | | /* |
504 | | * Add a destructor for the thread-local list |
505 | | * The pthread based destructor will disarm |
506 | | * this if it fires, but leave it enabled if |
507 | | * it doesn't, thus ensuring the memory is |
508 | | * *always* freed one way or another. |
509 | | */ |
510 | 38 | pthread_mutex_lock(&fr_atexit_global_mutex); |
511 | 38 | list->e = atexit_entry_alloc(file, line, |
512 | 38 | fr_atexit_threads, |
513 | 38 | _thread_local_free, |
514 | 38 | list); |
515 | | |
516 | 38 | pthread_mutex_unlock(&fr_atexit_global_mutex); |
517 | | |
518 | 38 | fr_atexit_thread_local = list; |
519 | 38 | } |
520 | | |
521 | | /* |
522 | | * Now allocate the actual atexit handler entry |
523 | | */ |
524 | 72 | if (atexit_entry_alloc(file, line, fr_atexit_thread_local, func, uctx) == NULL) return -1; |
525 | | |
526 | 72 | return 0; |
527 | 72 | } |
528 | | |
529 | | /** Remove a specific destructor for this thread (without executing them) |
530 | | * |
531 | | * @note This function's primary purpose is to help diagnose issues with destructors |
532 | | * from within a debugger. |
533 | | * |
534 | | * @param[in] uctx_scope Only process entries where the func and scope both match. |
535 | | * @param[in] func Entries matching this function will be disarmed. |
536 | | * @param[in] uctx associated with the entry. |
537 | | * @return How many destructors were disarmed. |
538 | | */ |
539 | | unsigned int fr_atexit_thread_local_disarm(bool uctx_scope, fr_atexit_t func, void const *uctx) |
540 | 0 | { |
541 | 0 | fr_atexit_entry_t *e = NULL; |
542 | 0 | unsigned int count = 0; |
543 | |
|
544 | 0 | if (!fr_atexit_thread_local) return -1; |
545 | | |
546 | 0 | while ((e = fr_dlist_next(&fr_atexit_thread_local->head, e))) { |
547 | 0 | fr_atexit_entry_t *disarm; |
548 | |
|
549 | 0 | if ((e->func != func) || ((e->uctx != uctx) && uctx_scope)) continue; |
550 | | |
551 | 0 | ATEXIT_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%d)", |
552 | 0 | __FUNCTION__, |
553 | 0 | (unsigned int)pthread_self(), |
554 | 0 | fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line); |
555 | 0 | disarm = e; |
556 | 0 | e = fr_dlist_remove(&fr_atexit_thread_local->head, e); |
557 | 0 | talloc_set_destructor(disarm, NULL); |
558 | 0 | talloc_free(disarm); |
559 | |
|
560 | 0 | count++; |
561 | 0 | } |
562 | |
|
563 | 0 | return count; |
564 | 0 | } |
565 | | |
566 | | /** Remove all destructors for this thread (without executing them) |
567 | | * |
568 | | * @note This function's primary purpose is to help diagnose issues with destructors |
569 | | * from within a debugger. |
570 | | */ |
571 | | void fr_atexit_thread_local_disarm_all(void) |
572 | 0 | { |
573 | 0 | fr_atexit_entry_t *e = NULL; |
574 | |
|
575 | 0 | if (!fr_atexit_thread_local) return; |
576 | | |
577 | 0 | while ((e = fr_dlist_pop_head(&fr_atexit_thread_local->head))) { |
578 | 0 | ATEXIT_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%d)", |
579 | 0 | __FUNCTION__, |
580 | 0 | (unsigned int)pthread_self(), |
581 | 0 | fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line); |
582 | 0 | talloc_set_destructor(e, NULL); |
583 | 0 | talloc_free(e); |
584 | 0 | } |
585 | 0 | } |
586 | | |
587 | | /** Cause all thread local free triggers to fire |
588 | | * |
589 | | * This is necessary when we're running in single threaded mode |
590 | | * to ensure all "thread-local" memory (which isn't actually thread local) |
591 | | * is cleaned up. |
592 | | * |
593 | | * One example is the OpenSSL log BIOs which must be cleaned up |
594 | | * before fr_openssl_free is called. |
595 | | * |
596 | | * @return |
597 | | * - >= 0 The number of atexit handlers triggered on success. |
598 | | * - <0 the return code from any atexit handlers that returned an error. |
599 | | */ |
600 | | int fr_atexit_thread_trigger_all(void) |
601 | 0 | { |
602 | 0 | fr_atexit_entry_t *e = NULL, *ee, *to_free; |
603 | 0 | fr_atexit_list_t *list; |
604 | 0 | unsigned int count = 0; |
605 | | |
606 | | /* |
607 | | * Iterate over the list of thread local |
608 | | * destructor lists running the |
609 | | * destructors. |
610 | | */ |
611 | 0 | while ((e = fr_dlist_next(&fr_atexit_threads->head, e))) { |
612 | 0 | if (!e->func) continue; /* thread already joined */ |
613 | | |
614 | 0 | list = talloc_get_type_abort(e->uctx, fr_atexit_list_t); |
615 | 0 | ee = NULL; |
616 | 0 | while ((ee = fr_dlist_next(&list->head, ee))) { |
617 | 0 | ATEXIT_DEBUG("%s - Thread %u triggering %p/%p func=%p, uctx=%p (alloced %s:%d)", |
618 | 0 | __FUNCTION__, |
619 | 0 | (unsigned int)pthread_self(), |
620 | 0 | list, ee, ee->func, ee->uctx, ee->file, ee->line); |
621 | |
|
622 | 0 | count++; |
623 | 0 | to_free = ee; |
624 | 0 | ee = fr_dlist_remove(&list->head, ee); |
625 | 0 | if (talloc_free(to_free) < 0) { |
626 | 0 | fr_strerror_printf_push("atexit handler failed %p/%p func=%p, uctx=%p" |
627 | 0 | " (alloced %s:%d)", |
628 | 0 | list, to_free, |
629 | 0 | to_free->func, to_free->uctx, |
630 | 0 | to_free->file, to_free->line |
631 | 0 | ); |
632 | 0 | return -1; |
633 | 0 | } |
634 | 0 | } |
635 | 0 | } |
636 | | |
637 | 0 | return count; |
638 | 0 | } |
639 | | |
640 | | /** Return whether the thread is currently being cleaned up |
641 | | * |
642 | | */ |
643 | | bool fr_atexit_thread_is_exiting(void) |
644 | 0 | { |
645 | 0 | return thread_is_exiting; |
646 | 0 | } |
647 | | #endif |