/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 | } |