Coverage Report

Created: 2025-12-31 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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