Coverage Report

Created: 2026-06-30 07:16

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: 2ef38705fdabec2e013e515ea5048cd3a7a13210 $
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: 2ef38705fdabec2e013e515ea5048cd3a7a13210 $")
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
0
    pthread_sigmask(SIG_BLOCK, &sigset, NULL);
129
0
  }
130
0
#endif
131
132
0
  ctx = talloc_init("%s", name);
133
0
  if (!ctx) {
134
0
    ERROR("%s - Failed allocating memory", name);
135
0
    return -1;
136
0
  }
137
138
0
  el = fr_event_list_alloc(ctx, NULL, NULL);
139
0
  if (!el) {
140
0
    PERROR("%s - Failed creating event list", name);
141
0
    talloc_free(ctx);
142
0
    return -1;
143
0
  }
144
145
  /*
146
   *  Do NOT initialize the entire structure.  Fields like "id" and "entry" have already been
147
   *  initialize and used by the main thread coordinator.
148
   */
149
0
  out->name = talloc_strdup(ctx, name);
150
0
  out->ctx = ctx;
151
0
  out->el = el;
152
0
  out->status = FR_THREAD_INITIALIZING;
153
154
0
  return 0;
155
0
}
156
157
/** Instantiate thread-specific data for modules, virtual servers, xlats, unlang, and TLS
158
 *
159
 * @param[in] ctx to allocate thread-specific data in.
160
 * @param[in] el  event list for this thread.
161
 * @return
162
 *  - 0 on success.
163
 *  - -1 on failure.
164
 */
165
int fr_thread_instantiate(TALLOC_CTX *ctx, fr_event_list_t *el)
166
0
{
167
0
#ifdef WITH_TLS
168
  /*
169
   *  Modules may use thread-specific OpenSSL contexts, so initialize this first.
170
   */
171
0
  if (fr_openssl_thread_init(main_config->openssl_async_pool_init,
172
0
           main_config->openssl_async_pool_max) < 0) return -1;
173
0
#endif
174
175
0
  if (modules_rlm_thread_instantiate(ctx, el) < 0) return -1;
176
177
0
  if (virtual_servers_thread_instantiate(ctx, el) < 0) return -1;
178
  
179
0
  if (xlat_thread_instantiate(ctx, el) < 0) return -1;
180
181
0
  if (unlang_thread_instantiate(ctx) < 0) return -1;
182
183
0
  return 0;
184
0
}
185
186
/** Detach thread-specific data for modules, virtual servers, xlats
187
 *
188
 * Calls detach in reverse order of instantiation.
189
 */
190
void fr_thread_detach(void)
191
0
{
192
0
  xlat_thread_detach();
193
0
  virtual_servers_thread_detach();
194
0
  modules_rlm_thread_detach();
195
0
}
196
197
198
/** Signal the parent that we're done.
199
 *
200
 * @param[out]  thread which is starting
201
 * @param[in] sem semaphore to signal.
202
 */
203
void fr_thread_start(fr_thread_t *thread, fr_sem_t *sem)
204
0
{
205
0
  thread->status = FR_THREAD_RUNNING;
206
207
0
  INFO("%s - Starting", thread->name);
208
209
0
  sem_post(sem);
210
0
}
211
212
/** Signal the parent that we're done.
213
 *
214
 * @param[out]  thread which is exiting
215
 * @param[in] status to write
216
 * @param[in] sem semaphore to signal.
217
 */
218
void fr_thread_exit(fr_thread_t *thread, fr_thread_status_t status, fr_sem_t *sem)
219
0
{
220
  /*
221
   *  Maybe nothing was initialized, so we don't need to do anything.
222
   */
223
0
  if (thread->status == FR_THREAD_FREE) return;
224
225
0
  INFO("%s - Exiting", thread->name);
226
227
0
  thread->status = status;
228
229
  /*
230
   *  Not looping at this point, but may catch timer/fd
231
   *  insertions being done after the thread should have
232
   *  exited.
233
   */
234
0
  if (thread->el) fr_event_loop_exit(thread->el, 1);
235
236
0
  TALLOC_FREE(thread->ctx);
237
238
0
  sem_post(sem);
239
0
}