Coverage Report

Created: 2026-10-18 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/io/thread.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
/**
18
 * $Id: 2e34607bb921e495aec9bcfbadf19f754b9eb41b $
19
 *
20
 * @brief Common thread instantiation and detach for worker and coordinator threads
21
 * @file io/thread.c
22
 *
23
 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
24
 */
25
RCSID("$Id: 2e34607bb921e495aec9bcfbadf19f754b9eb41b $")
26
27
#include <freeradius-devel/io/thread.h>
28
#include <freeradius-devel/server/module_rlm.h>
29
#include <freeradius-devel/server/virtual_servers.h>
30
#include <freeradius-devel/server/main_config.h>
31
#include <freeradius-devel/tls/base.h>
32
#include <freeradius-devel/unlang/base.h>
33
#include <freeradius-devel/util/syserror.h>
34
35
#include <signal.h>
36
37
/** Create a joinable thread
38
 *
39
 * @param[out] thread   handle that was created by pthread_create.
40
 * @param[in] func    entry point for the thread.
41
 * @param[in] arg   Argument to pass to func.
42
 * @return
43
 *  - 0 on success.
44
 *  - -1 on failure.
45
 */
46
int fr_thread_create(pthread_t *thread, fr_thread_entry_t func, void *arg)
47
0
{
48
0
  pthread_attr_t      attr;
49
0
  int       rcode;
50
51
  /*
52
   *  Set the thread to wait around after it's exited so it
53
   *  can be joined.  This is more of a useful mechanism for
54
   *  the parent to determine if all the threads have exited
55
   *  so it can continue with a graceful shutdown.
56
   */
57
0
  pthread_attr_init(&attr);
58
0
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
59
60
0
  rcode = pthread_create(thread, &attr, func, arg);
61
0
  if (rcode != 0) {
62
0
    fr_strerror_printf("Failed creating thread: %s", fr_syserror(rcode));
63
0
    pthread_attr_destroy(&attr);
64
0
    return -1;
65
0
  }
66
0
  pthread_attr_destroy(&attr);
67
68
0
  return 0;
69
0
}
70
71
/** Wait for multiple threads to signal readiness via a semaphore
72
 *
73
 * @param[in] sem   semaphore to wait on.
74
 * @param[in] head    head of the list to wait for
75
 * @return
76
 *  - 0 for success
77
 *  - <0 negative number of threads which failed to start
78
 */
79
int fr_thread_wait_list(fr_sem_t *sem, fr_dlist_head_t *head)
80
0
{
81
0
  unsigned int i, count;
82
0
  int rcode = 0;
83
84
0
  count = fr_dlist_num_elements(head);
85
86
0
  for (i = 0; i < count; i++) {
87
0
    SEM_WAIT_INTR(sem);
88
0
  }
89
90
  /*
91
   *  See if all of the threads have started.  If a thread fails, it cleans up any context that was
92
   *  allocated for it.
93
   */
94
0
  fr_dlist_foreach(head, fr_thread_t, thread) {
95
0
    if (thread->status != FR_THREAD_RUNNING) {
96
0
      fr_dlist_remove(head, thread);
97
0
      rcode--;
98
0
    }
99
0
  }
100
101
0
  return rcode;
102
0
}
103
104
/** Common setup for child threads: block signals, allocate a talloc context, and create an event list
105
 *
106
 * @param[out] out  structure describing the thread
107
 * @param[in]  name Human-readable name used for the talloc context and error messages.
108
 * @return
109
 *  - 0 on success.
110
 *  - <0 on failure
111
 */
112
int fr_thread_setup(fr_thread_t *out, char const *name)
113
0
{
114
0
  TALLOC_CTX  *ctx;
115
0
  fr_event_list_t *el;
116
117
0
#ifndef __APPLE__
118
  /*
119
   *  OSX doesn't use pthread_signmask in its setcontext
120
   *  function, and seems to apply the signal mask of the
121
   *  thread to the entire process when setcontext is
122
   *  called.
123
   */
124
0
  {
125
0
    sigset_t sigset;
126
127
0
    sigfillset(&sigset);
128
129
    /*
130
     *  In debug builds, remove SIGPROF from the set
131
     *  if we're planning on using gperftools.
132
     *
133
     *  SIGPROF is using during profiling, and must
134
     *  reach the thread in order for profiling to
135
     *  work.
136
     */
137
#if !defined(NDEBUG) && defined(HAVE_GPERFTOOLS_PROFILER_H)
138
    sigdelset(&sigset, SIGPROF);
139
#endif
140
0
    pthread_sigmask(SIG_BLOCK, &sigset, NULL);
141
0
  }
142
0
#endif
143
144
0
  ctx = talloc_init("%s", name);
145
0
  if (!ctx) {
146
0
    ERROR("%s - Failed allocating memory", name);
147
0
    return -1;
148
0
  }
149
150
0
  el = fr_event_list_alloc(ctx, NULL, NULL);
151
0
  if (!el) {
152
0
    PERROR("%s - Failed creating event list", name);
153
0
    talloc_free(ctx);
154
0
    return -1;
155
0
  }
156
157
  /*
158
   *  Do NOT initialize the entire structure.  Fields like "id" and "entry" have already been
159
   *  initialize and used by the main thread coordinator.
160
   */
161
0
  out->name = talloc_strdup(ctx, name);
162
0
  out->ctx = ctx;
163
0
  out->el = el;
164
0
  out->status = FR_THREAD_INITIALIZING;
165
166
0
  return 0;
167
0
}
168
169
/** Instantiate thread-specific data for modules, virtual servers, xlats, unlang, and TLS
170
 *
171
 * @param[in] ctx to allocate thread-specific data in.
172
 * @param[in] el  event list for this thread.
173
 * @return
174
 *  - 0 on success.
175
 *  - -1 on failure.
176
 */
177
int fr_thread_instantiate(TALLOC_CTX *ctx, fr_event_list_t *el)
178
0
{
179
0
#ifdef WITH_TLS
180
  /*
181
   *  Modules may use thread-specific OpenSSL contexts, so initialize this first.
182
   */
183
0
  if (fr_openssl_thread_init(main_config->openssl_async_pool_init,
184
0
           main_config->openssl_async_pool_max) < 0) return -1;
185
0
#endif
186
187
0
  if (modules_rlm_thread_instantiate(ctx, el) < 0) return -1;
188
189
0
  if (virtual_servers_thread_instantiate(ctx, el) < 0) return -1;
190
191
0
  if (xlat_thread_instantiate(ctx, el) < 0) return -1;
192
193
0
  if (unlang_thread_instantiate(ctx) < 0) return -1;
194
195
0
  return 0;
196
0
}
197
198
/** Detach thread-specific data for modules, virtual servers, xlats
199
 *
200
 * Calls detach in reverse order of instantiation.
201
 */
202
void fr_thread_detach(void)
203
0
{
204
0
  xlat_thread_detach();
205
0
  virtual_servers_thread_detach();
206
0
  (void) modules_rlm_coord_detach();
207
0
  modules_rlm_thread_detach();
208
0
}
209
210
211
/** Signal the parent that we're done.
212
 *
213
 * @param[out]  thread which is starting
214
 * @param[in] sem semaphore to signal.
215
 */
216
void fr_thread_start(fr_thread_t *thread, fr_sem_t *sem)
217
0
{
218
0
  thread->status = FR_THREAD_RUNNING;
219
220
0
  INFO("%s - Starting", thread->name);
221
222
0
  sem_post(sem);
223
0
}
224
225
/** Signal the parent that we're done.
226
 *
227
 * @param[out]  thread which is exiting
228
 * @param[in] status to write
229
 * @param[in] sem semaphore to signal.
230
 */
231
void fr_thread_exit(fr_thread_t *thread, fr_thread_status_t status, fr_sem_t *sem)
232
0
{
233
  /*
234
   *  Maybe nothing was initialized, so we don't need to do anything.
235
   */
236
0
  if (thread->status == FR_THREAD_FREE) return;
237
238
0
  INFO("%s - Exiting", thread->name);
239
240
0
  thread->status = status;
241
242
  /*
243
   *  Not looping at this point, but may catch timer/fd
244
   *  insertions being done after the thread should have
245
   *  exited.
246
   */
247
0
  if (thread->el) fr_event_loop_exit(thread->el, 1);
248
249
0
  TALLOC_FREE(thread->ctx);
250
251
0
  sem_post(sem);
252
0
}