/src/freeradius-server/src/lib/io/coord.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: 830857241dd0bf0523ddfea255dd6c2f6ce62bac $ |
19 | | * |
20 | | * @brief Coordination thread management |
21 | | * @file io/coord.c |
22 | | * |
23 | | * @copyright 2026 Network RADIUS SAS (legal@networkradius.com) |
24 | | */ |
25 | | RCSID("$Id: 830857241dd0bf0523ddfea255dd6c2f6ce62bac $") |
26 | | |
27 | | #include <freeradius-devel/io/listen.h> |
28 | | #include <freeradius-devel/io/schedule.h> |
29 | | #include <freeradius-devel/io/thread.h> |
30 | | #include <freeradius-devel/io/coord_priv.h> |
31 | | #include <freeradius-devel/unlang/base.h> |
32 | | #include <freeradius-devel/util/syserror.h> |
33 | | |
34 | | #include <stdalign.h> |
35 | | |
36 | 0 | #define FR_CONTROL_ID_COORD_WORKER_ATTACH (1) //!< Message sent from worker to attach to a coordinator |
37 | 0 | #define FR_CONTROL_ID_COORD_WORKER_DETACH (2) //!< Message sent from worker to detach from a coordinator |
38 | 0 | #define FR_CONTROL_ID_COORD_WORKER_ACK (3) //!< Message sent to worker to acknowledge attach / detach |
39 | 0 | #define FR_CONTROL_ID_COORD_DATA (4) //!< Worker <-> coordinator message to pass data to a callback |
40 | | |
41 | 0 | #define MIN_WORKER_ID -1 //!< The minimum value we expect as worker id. -1 is the main thread. |
42 | | |
43 | | static fr_dlist_head_t *coord_regs = NULL; |
44 | | static fr_dlist_head_t *coord_threads = NULL; |
45 | | static fr_rb_tree_t coords = (fr_rb_tree_t){ .num_elements = 0 }; |
46 | | |
47 | | /** A coordinator which receives messages from workers |
48 | | */ |
49 | | struct fr_coord_s { |
50 | | fr_coord_reg_t *coord_reg; //!< Coordinator registration details. |
51 | | fr_event_list_t *el; //!< Coordinator event list. |
52 | | fr_rb_node_t node; //!< Entry in the tree of coordinators. |
53 | | fr_coord_cb_reg_t *callbacks; //!< Array of callbacks for worker -> coordinator messages. |
54 | | uint32_t num_callbacks; //!< Number of callbacks defined. |
55 | | fr_coord_cb_inst_t **cb_inst; //!< Array of callback instance specific data. |
56 | | |
57 | | uint32_t max_workers; //!< Maximum number of workers we expect. |
58 | | uint32_t num_workers; //!< How many workers are attached. |
59 | | |
60 | | fr_control_t *coord_recv_control; //!< Control plane for worker -> coordinator messages. |
61 | | fr_atomic_queue_t *coord_recv_aq; //!< Atomic queue for worker -> coordinator |
62 | | fr_ring_buffer_t **coord_send_rb; //!< Ring buffers for coordinator -> worker control messages. |
63 | | fr_control_t **coord_send_control; //!< Control planes for coordinator -> worker messages. |
64 | | fr_message_set_t **coord_send_ms; //!< Message sets for coordinator -> worker data. |
65 | | fr_atomic_queue_t **coord_send_aq; //!< Atomic queues for coordinator -> worker data. |
66 | | |
67 | | bool exiting; //!< Is this coordinator shutting down. |
68 | | bool single_thread; //!< Are we in single thread mode. |
69 | | }; |
70 | | |
71 | | /** The worker end of worker <-> coordinator communication. |
72 | | */ |
73 | | struct fr_coord_worker_s { |
74 | | fr_coord_t *coord; //!< Coordinator this worker is related to |
75 | | fr_ring_buffer_t *worker_send_rb; //!< Ring buffer for worker -> coordinator control plane |
76 | | fr_message_set_t *worker_send_ms; //!< Message set for worker -> coordinator messages |
77 | | fr_control_t *worker_recv_control; //!< Coordinator -> worker control plane |
78 | | fr_atomic_queue_t *worker_recv_aq; //!< Atomic queue for coordinator -> worker messages |
79 | | fr_coord_worker_cb_reg_t *callbacks; //!< Callbacks for coordinator -> worker messages |
80 | | uint32_t num_callbacks; //!< Number of callbacks registered. |
81 | | }; |
82 | | |
83 | | /** A coordinator registration |
84 | | */ |
85 | | struct fr_coord_reg_s { |
86 | | char const *name; //!< Name for debugging. |
87 | | fr_dlist_t entry; //!< Entry in list of registrations. |
88 | | fr_coord_cb_reg_t *coord_cb; //!< Callbacks for worker -> coordinator messages. |
89 | | fr_coord_worker_cb_reg_t *worker_cb; //!< Callbacks for coordinator -> worker messages. |
90 | | size_t worker_send_size; //!< Initial size for worker -> coordinator ring buffer. |
91 | | size_t coord_send_size; //!< Initial size for coordinator -> worker ring buffer. |
92 | | module_instance_t const *mi; //!< Module instance which registered this coordinator. |
93 | | }; |
94 | | |
95 | | /** Scheduler specific information for coordinator threads |
96 | | */ |
97 | | typedef struct { |
98 | | fr_thread_t thread; //!< common thread information - must be first! |
99 | | |
100 | | uint32_t max_workers; //!< Maximum number of workers which will connect to this coordinator. |
101 | | fr_coord_reg_t *coord_reg; //!< Coordinator registration details. |
102 | | fr_coord_t *coord; //!< The coordinator data structure. |
103 | | fr_sem_t *sem; //!< For inter-thread signaling. |
104 | | } fr_schedule_coord_t; |
105 | | |
106 | | /** Control plane message used for workers attaching / detaching to coordinators |
107 | | */ |
108 | | typedef struct { |
109 | | int32_t worker; //!< Worker ID |
110 | | fr_control_t *worker_recv_control; //!< Control plane to send messages to this worker |
111 | | fr_atomic_queue_t *worker_recv_aq; //!< Atomic queue to send data to this worker |
112 | | } fr_coord_worker_attach_msg_t; |
113 | | |
114 | | typedef struct { |
115 | | int32_t worker; //!< Worker ID |
116 | | bool exiting; //!< Is the server exiting |
117 | | } fr_coord_worker_detach_msg_t; |
118 | | |
119 | | /** Compare coordinators by registration |
120 | | */ |
121 | | static int8_t coord_cmp(void const *one, void const *two) |
122 | 0 | { |
123 | 0 | fr_coord_t const *a = one, *b = two; |
124 | |
|
125 | 0 | return CMP(a->coord_reg, b->coord_reg); |
126 | 0 | } |
127 | | |
128 | | /** Register a coordinator |
129 | | * |
130 | | * To be called from mod_instantiate of a module which uses a coordinator |
131 | | * |
132 | | * @param reg_ctx Registration data |
133 | | * @return |
134 | | * - coordination registration on success |
135 | | * - NULL on failure |
136 | | */ |
137 | | fr_coord_reg_t *fr_coord_register(fr_coord_reg_ctx_t *reg_ctx) |
138 | 0 | { |
139 | 0 | fr_coord_reg_t *coord_reg; |
140 | |
|
141 | 0 | fr_assert(reg_ctx->coord_cb); |
142 | 0 | fr_assert(reg_ctx->worker_cb); |
143 | 0 | fr_assert(reg_ctx->mi); |
144 | | |
145 | | /* Allocate the list of registered coordinators if not already done */ |
146 | 0 | if (!coord_regs) { |
147 | 0 | MEM(coord_regs = talloc_zero(NULL, fr_dlist_head_t)); |
148 | 0 | fr_dlist_talloc_init(coord_regs, fr_coord_reg_t, entry); |
149 | 0 | } |
150 | |
|
151 | 0 | MEM(coord_reg = talloc(coord_regs, fr_coord_reg_t)); |
152 | 0 | *coord_reg = (fr_coord_reg_t) { |
153 | 0 | .name = reg_ctx->name, |
154 | 0 | .coord_cb = reg_ctx->coord_cb, |
155 | 0 | .worker_cb = reg_ctx->worker_cb, |
156 | 0 | .worker_send_size = reg_ctx->worker_send_size ? reg_ctx->worker_send_size : 4096, |
157 | 0 | .coord_send_size = reg_ctx->coord_send_size ? reg_ctx->coord_send_size : 4096, |
158 | 0 | .mi = reg_ctx->mi, |
159 | 0 | }; |
160 | |
|
161 | 0 | fr_dlist_insert_tail(coord_regs, coord_reg); |
162 | |
|
163 | 0 | return coord_reg; |
164 | 0 | } |
165 | | |
166 | | /** De-register a coordinator |
167 | | * |
168 | | * To be called from mod_detach of a module which uses a coordinator |
169 | | * |
170 | | * @param coord_reg to de-register |
171 | | */ |
172 | | void fr_coord_deregister(fr_coord_reg_t *coord_reg) |
173 | 0 | { |
174 | 0 | fr_dlist_remove(coord_regs, coord_reg); |
175 | |
|
176 | 0 | talloc_free(coord_reg); |
177 | |
|
178 | 0 | if (fr_dlist_num_elements(coord_regs) == 0) TALLOC_FREE(coord_regs); |
179 | 0 | } |
180 | | |
181 | | /** Wait for all the coordinator threads to exit |
182 | | * |
183 | | * To be called during the scheduler shutdown in multi-threaded mode. |
184 | | */ |
185 | | void fr_coord_thread_join(void) |
186 | 0 | { |
187 | 0 | int ret; |
188 | |
|
189 | 0 | if (!coord_threads) return; |
190 | | |
191 | 0 | fr_dlist_foreach(coord_threads, fr_schedule_coord_t, sc) { |
192 | 0 | if ((ret = pthread_join(sc->thread.pthread_id, NULL)) != 0) { |
193 | 0 | ERROR("Failed joining coordinator %s: %s", sc->coord_reg->name, fr_syserror(ret)); |
194 | 0 | } else { |
195 | 0 | DEBUG2("Coordinator %s joined (cleaned up)", sc->coord_reg->name); |
196 | 0 | } |
197 | |
|
198 | 0 | fr_dlist_remove(coord_threads, sc); |
199 | 0 | talloc_free(sc); |
200 | 0 | } |
201 | 0 | } |
202 | | |
203 | | /** Callback for a coordinator receiving data from a worker |
204 | | */ |
205 | | static void coord_data_recv(void *ctx, void const *data, size_t data_size, fr_time_t now) |
206 | 0 | { |
207 | 0 | fr_coord_t *coord = talloc_get_type_abort(ctx, fr_coord_t); |
208 | 0 | fr_coord_msg_t cm; |
209 | 0 | fr_coord_data_t *cd; |
210 | 0 | fr_dbuff_t dbuff; |
211 | |
|
212 | 0 | fr_assert(data_size == sizeof(cm)); |
213 | 0 | memcpy(&cm, data, data_size); |
214 | 0 | fr_assert((cm.worker >= MIN_WORKER_ID) && (cm.worker < (int32_t)coord->max_workers)); |
215 | |
|
216 | 0 | if (unlikely(!fr_atomic_queue_pop(coord->coord_recv_aq, (void **)&cd))) return; |
217 | | |
218 | 0 | DEBUG3("Coordinator %s got data from worker %d for callback %d", |
219 | 0 | coord->coord_reg->name, cm.worker, cd->coord_cb_id); |
220 | |
|
221 | 0 | if (cd->coord_cb_id >= coord->num_callbacks) { |
222 | 0 | ERROR("Received data for callback %d which is not defined", cd->coord_cb_id); |
223 | 0 | fr_message_done(&cd->m); |
224 | 0 | return; |
225 | 0 | } |
226 | | |
227 | 0 | fr_dbuff_init(&dbuff, (uint8_t const *)cd->m.data, cd->m.data_size); |
228 | 0 | coord->callbacks[cd->coord_cb_id].callback(coord, cm.worker, &dbuff, now, |
229 | 0 | coord->cb_inst[cd->coord_cb_id] ? |
230 | 0 | coord->cb_inst[cd->coord_cb_id]->inst_data : NULL, |
231 | 0 | coord->callbacks[cd->coord_cb_id].uctx); |
232 | 0 | fr_message_done(&cd->m); |
233 | 0 | } |
234 | | |
235 | | /** Callback for a worker receiving data from a coordinator |
236 | | */ |
237 | | static void coord_worker_data_recv(void *ctx, void const *data, size_t data_size, fr_time_t now) |
238 | 0 | { |
239 | 0 | fr_coord_worker_t *cw = talloc_get_type_abort(ctx, fr_coord_worker_t); |
240 | 0 | fr_coord_msg_t cm; |
241 | 0 | fr_coord_data_t *cd; |
242 | 0 | fr_dbuff_t dbuff; |
243 | |
|
244 | 0 | fr_assert(data_size == sizeof(cm)); |
245 | 0 | memcpy(&cm, data, data_size); |
246 | |
|
247 | 0 | if (unlikely(!fr_atomic_queue_pop(cw->worker_recv_aq, (void **)&cd))) return; |
248 | | |
249 | 0 | DEBUG3("Coordinator %s sent message for callback %d", cw->coord->coord_reg->name, cd->coord_cb_id); |
250 | |
|
251 | 0 | if (cd->coord_cb_id >= cw->num_callbacks) { |
252 | 0 | ERROR("Received message for callback %d which is not defined", cd->coord_cb_id); |
253 | 0 | fr_message_done(&cd->m); |
254 | 0 | return; |
255 | 0 | } |
256 | | |
257 | 0 | fr_dbuff_init(&dbuff, (uint8_t const *)cd->m.data, cd->m.data_size); |
258 | 0 | cw->callbacks[cd->coord_cb_id].callback(cw, &dbuff, now, |
259 | 0 | MODULE_CTX(cw->coord->coord_reg->mi, |
260 | 0 | module_thread(cw->coord->coord_reg->mi)->data, NULL, NULL), |
261 | 0 | cw->callbacks[cd->coord_cb_id].uctx); |
262 | 0 | fr_message_done(&cd->m); |
263 | 0 | } |
264 | | |
265 | | /** Callback run by a coordinator when a worker attaches |
266 | | */ |
267 | | static void coord_worker_attach(void *ctx, void const *data, NDEBUG_UNUSED size_t data_size, UNUSED fr_time_t now) |
268 | 0 | { |
269 | 0 | fr_coord_t *coord = talloc_get_type_abort(ctx, fr_coord_t); |
270 | 0 | fr_coord_worker_attach_msg_t const *msg = data; |
271 | 0 | fr_coord_msg_t ack; |
272 | 0 | uint32_t thread_id; |
273 | |
|
274 | 0 | fr_assert(data_size == sizeof(fr_coord_worker_attach_msg_t)); |
275 | 0 | fr_assert((msg->worker >= MIN_WORKER_ID) && (msg->worker < (int32_t)coord->max_workers)); |
276 | |
|
277 | 0 | DEBUG2("Worker %d attached to %s", msg->worker, coord->coord_reg->name); |
278 | 0 | coord->num_workers++; |
279 | 0 | thread_id = msg->worker - MIN_WORKER_ID; |
280 | 0 | coord->coord_send_control[thread_id] = msg->worker_recv_control; |
281 | 0 | coord->coord_send_aq[thread_id] = msg->worker_recv_aq; |
282 | |
|
283 | 0 | ack.worker = msg->worker; |
284 | 0 | fr_control_message_send(coord->coord_send_control[thread_id], coord->coord_send_rb[thread_id], |
285 | 0 | FR_CONTROL_ID_COORD_WORKER_ACK, &ack, sizeof(ack)); |
286 | 0 | } |
287 | | |
288 | | /** Callback run by a coordinator when a worker detaches |
289 | | */ |
290 | | static void coord_worker_detach(void *ctx, void const *data, NDEBUG_UNUSED size_t data_size, UNUSED fr_time_t now) |
291 | 0 | { |
292 | 0 | fr_coord_t *coord = talloc_get_type_abort(ctx, fr_coord_t); |
293 | 0 | fr_coord_worker_detach_msg_t const *msg = data; |
294 | 0 | fr_coord_msg_t ack; |
295 | 0 | uint32_t thread_id; |
296 | |
|
297 | 0 | fr_assert(data_size == sizeof(fr_coord_worker_detach_msg_t)); |
298 | 0 | fr_assert((msg->worker >= MIN_WORKER_ID) && (msg->worker < (int32_t)coord->max_workers)); |
299 | 0 | thread_id = msg->worker - MIN_WORKER_ID; |
300 | |
|
301 | 0 | DEBUG2("Worker %d detached from %s", msg->worker, coord->coord_reg->name); |
302 | 0 | coord->num_workers--; |
303 | |
|
304 | 0 | ack.worker = msg->worker; |
305 | 0 | fr_control_message_send(coord->coord_send_control[thread_id], coord->coord_send_rb[thread_id], |
306 | 0 | FR_CONTROL_ID_COORD_WORKER_ACK, &ack, sizeof(fr_coord_msg_t)); |
307 | |
|
308 | 0 | coord->coord_send_control[thread_id] = NULL; |
309 | 0 | coord->coord_send_aq[thread_id] = NULL; |
310 | 0 | if (msg->exiting) coord->exiting = true; |
311 | 0 | } |
312 | | |
313 | | /** Create a coordinator from its registration |
314 | | * |
315 | | * @param ctx to allocate the coordinator in |
316 | | * @param el Event list to run this coordinator |
317 | | * @param coord_reg Registration to configure this coordinator |
318 | | * @param single_thread Is the server in single thread mode |
319 | | * @param max_workers The maximum number of workers which will attach |
320 | | * @return |
321 | | * - the coordinator on success |
322 | | * - NULL on failure |
323 | | */ |
324 | | static fr_coord_t *fr_coord_create(TALLOC_CTX *ctx, fr_event_list_t *el, fr_coord_reg_t *coord_reg, |
325 | | bool single_thread, uint32_t max_workers) |
326 | 0 | { |
327 | 0 | fr_coord_t *coord; |
328 | 0 | uint32_t i, num_threads = max_workers - MIN_WORKER_ID; |
329 | 0 | fr_coord_cb_reg_t *cb = coord_reg->coord_cb; |
330 | 0 | fr_atomic_queue_t *aq; |
331 | |
|
332 | 0 | MEM(coord = talloc(ctx, fr_coord_t)); |
333 | 0 | *coord = (fr_coord_t) { |
334 | 0 | .el = el, |
335 | 0 | .coord_reg = coord_reg, |
336 | 0 | .single_thread = single_thread, |
337 | 0 | .max_workers = max_workers |
338 | 0 | }; |
339 | | |
340 | | /* Allocate atomic queue / control for receiving messages from workers */ |
341 | 0 | aq = fr_atomic_queue_talloc(coord, FR_CONTROL_MAX_MESSAGES); |
342 | 0 | if (!aq) { |
343 | 0 | fr_strerror_const("Failed creating worker -> coordinator atomic queue"); |
344 | 0 | fail: |
345 | 0 | talloc_free(coord); |
346 | 0 | return NULL; |
347 | 0 | } |
348 | 0 | coord->coord_recv_control = fr_control_create(coord, el, aq, 5); |
349 | 0 | if (!coord->coord_recv_control) { |
350 | 0 | fr_strerror_const("Failed creating worker -> coordinator control plane"); |
351 | 0 | goto fail; |
352 | 0 | } |
353 | | |
354 | | /* Allocate atomic queue for workers sending data to coordinators */ |
355 | 0 | coord->coord_recv_aq = fr_atomic_queue_talloc(coord, FR_CONTROL_MAX_MESSAGES); |
356 | 0 | if (!coord->coord_recv_aq) { |
357 | 0 | fr_strerror_const("Failed creating worker -> coordinator data atomic queue"); |
358 | 0 | goto fail; |
359 | 0 | } |
360 | | |
361 | 0 | if (fr_control_callback_add(&coord->coord_recv_control, FR_CONTROL_ID_COORD_WORKER_ATTACH, |
362 | 0 | coord, coord_worker_attach) < 0) goto fail; |
363 | 0 | if (fr_control_callback_add(&coord->coord_recv_control, FR_CONTROL_ID_COORD_WORKER_DETACH, |
364 | 0 | coord, coord_worker_detach) < 0) goto fail; |
365 | 0 | if (fr_control_callback_add(&coord->coord_recv_control, FR_CONTROL_ID_COORD_DATA, |
366 | 0 | coord, coord_data_recv) < 0) goto fail; |
367 | | |
368 | | /* Count the number of callbacks defined, for sanity checking messages */ |
369 | 0 | while (cb->callback) { |
370 | 0 | coord->num_callbacks++; |
371 | 0 | cb++; |
372 | 0 | } |
373 | 0 | coord->callbacks = coord_reg->coord_cb; |
374 | |
|
375 | 0 | if (fr_control_open(coord->coord_recv_control) < 0) { |
376 | 0 | fr_strerror_const("Failed opening control plane"); |
377 | 0 | goto fail; |
378 | 0 | } |
379 | | |
380 | | /* |
381 | | * Coordinator side arrays for holding pointers to worker |
382 | | * specific communication structures. The array sizes are the |
383 | | * number of threads expected to attach which is the number of |
384 | | * workers plus any additional threads, currently just the main |
385 | | * thread (worker id -1) |
386 | | */ |
387 | 0 | MEM(coord->coord_send_rb = talloc_array(coord, fr_ring_buffer_t *, num_threads)); |
388 | 0 | MEM(coord->coord_send_ms = talloc_array(coord, fr_message_set_t *, num_threads)); |
389 | 0 | for (i = 0; i < num_threads; i++) { |
390 | 0 | coord->coord_send_rb[i] = fr_ring_buffer_create(coord, FR_CONTROL_MAX_MESSAGES * FR_CONTROL_MAX_SIZE); |
391 | 0 | if (!coord->coord_send_rb[i]) goto fail; |
392 | | |
393 | 0 | coord->coord_send_ms[i] = fr_message_set_create(coord, FR_CONTROL_MAX_MESSAGES, sizeof(fr_coord_data_t), |
394 | 0 | coord_reg->coord_send_size, true); |
395 | 0 | if (!coord->coord_send_ms[i]) goto fail; |
396 | 0 | } |
397 | 0 | MEM(coord->coord_send_control = talloc_zero_array(coord, fr_control_t *, num_threads)); |
398 | 0 | MEM(coord->coord_send_aq = talloc_zero_array(coord, fr_atomic_queue_t *, num_threads)); |
399 | |
|
400 | 0 | MEM(coord->cb_inst = talloc_zero_array(coord, fr_coord_cb_inst_t *, coord->num_callbacks)); |
401 | |
|
402 | 0 | for (i = 0; i < coord->num_callbacks; i++) { |
403 | 0 | if (!coord->callbacks[i].inst_create) continue; |
404 | 0 | coord->cb_inst[i] = coord->callbacks[i].inst_create(coord, coord, coord->el, coord->single_thread, |
405 | 0 | coord->callbacks[i].uctx); |
406 | 0 | if (!coord->cb_inst[i]) goto fail; |
407 | 0 | } |
408 | | |
409 | 0 | return coord; |
410 | 0 | } |
411 | | |
412 | 0 | static void fr_coord_destroy(fr_coord_t *coord){ |
413 | 0 | uint32_t i; |
414 | |
|
415 | 0 | for (i = 0; i < coord->num_callbacks; i++) { |
416 | 0 | if (!coord->callbacks[i].inst_destroy) continue; |
417 | 0 | coord->callbacks[i].inst_destroy(coord, coord->cb_inst[i], coord->single_thread, |
418 | 0 | coord->callbacks[i].uctx); |
419 | 0 | } |
420 | 0 | } |
421 | | |
422 | | /** Run the event loop for a coordinator thread when in multi-threaded mode |
423 | | */ |
424 | | static void fr_coordinate(fr_coord_t *coord) |
425 | 0 | { |
426 | 0 | uint32_t i; |
427 | 0 | fr_coord_cb_inst_t *cb_inst; |
428 | | |
429 | | /* |
430 | | * Run until we're told to exit AND the number of |
431 | | * workers has dropped to zero. |
432 | | * |
433 | | * Whenever a worker detaches, coord->num_workers |
434 | | * is decremented, so when coord->num_workers == 0, |
435 | | * all workers have detached and are no longer using |
436 | | * the channel. |
437 | | */ |
438 | 0 | while (likely(!(coord->exiting && (coord->num_workers == 0)))) { |
439 | 0 | int num_events; |
440 | | |
441 | | /* |
442 | | * Check the event list. If there's an error |
443 | | * (e.g. exit), we stop looping and clean up. |
444 | | */ |
445 | 0 | DEBUG4("Gathering events"); |
446 | 0 | num_events = fr_event_corral(coord->el, fr_time(), true); |
447 | 0 | DEBUG4("%u event(s) pending%s", |
448 | 0 | num_events == -1 ? 0 : num_events, num_events == -1 ? " - event loop exiting" : ""); |
449 | 0 | if (num_events < 0) break; |
450 | | |
451 | | /* |
452 | | * Service outstanding events. |
453 | | */ |
454 | 0 | if (num_events > 0) { |
455 | 0 | DEBUG4("Servicing event(s)"); |
456 | 0 | fr_event_service(coord->el); |
457 | 0 | } |
458 | | |
459 | | /* |
460 | | * Run any registered instance specific event callbacks |
461 | | */ |
462 | 0 | for (i = 0; i < coord->num_callbacks; i++) { |
463 | 0 | cb_inst = coord->cb_inst[i]; |
464 | 0 | if (cb_inst && cb_inst->event_cb) cb_inst->event_cb(coord->el, cb_inst->inst_data); |
465 | 0 | } |
466 | 0 | } |
467 | |
|
468 | 0 | fr_coord_destroy(coord); |
469 | |
|
470 | 0 | return; |
471 | 0 | } |
472 | | |
473 | | /** Entry point for a coordinator thread |
474 | | */ |
475 | | static void *fr_coordinate_thread(void *arg) |
476 | 0 | { |
477 | 0 | fr_schedule_coord_t *sc = talloc_get_type_abort(arg, fr_schedule_coord_t); |
478 | 0 | fr_coord_reg_t *coord_reg = sc->coord_reg; |
479 | 0 | fr_thread_status_t status = FR_THREAD_FAIL; |
480 | 0 | char coordinate_name[64]; |
481 | |
|
482 | 0 | snprintf(coordinate_name, sizeof(coordinate_name), "Coordinate %s", coord_reg->name); |
483 | |
|
484 | 0 | if (fr_thread_setup(&sc->thread, coordinate_name) < 0) goto fail; |
485 | | |
486 | 0 | sc->coord = fr_coord_create(sc->thread.ctx, sc->thread.el, coord_reg, false, sc->max_workers); |
487 | 0 | if (!sc->coord) { |
488 | 0 | PERROR("%s - Failed creating coordinator thread", coordinate_name); |
489 | 0 | goto fail; |
490 | 0 | } |
491 | | |
492 | | /* |
493 | | * Create all the thread specific data for the coordinator thread |
494 | | */ |
495 | 0 | if (fr_thread_instantiate(sc->thread.ctx, sc->thread.el) < 0) goto fail; |
496 | | |
497 | | /* |
498 | | * Tell the originator that the thread has started. |
499 | | */ |
500 | 0 | fr_thread_start(&sc->thread, sc->sem); |
501 | |
|
502 | 0 | fr_coordinate(sc->coord); |
503 | |
|
504 | 0 | status = FR_THREAD_EXITED; |
505 | |
|
506 | 0 | fail: |
507 | 0 | fr_thread_detach(); |
508 | |
|
509 | 0 | fr_thread_exit(&sc->thread, status, sc->sem); |
510 | |
|
511 | 0 | return NULL; |
512 | 0 | } |
513 | | |
514 | | /** Start all registered coordinator threads in multi-threaded mode |
515 | | * |
516 | | * @param num_workers The number of workers which will be attaching |
517 | | * @param sem Semaphore to use signalling the threads are ready |
518 | | * @return |
519 | | * - 0 on success |
520 | | * - -1 on failure |
521 | | */ |
522 | | int fr_coord_start(uint32_t num_workers, fr_sem_t *sem) |
523 | 0 | { |
524 | 0 | int num = 0; |
525 | |
|
526 | 0 | if (!coord_regs) return 0; |
527 | | |
528 | 0 | MEM(coord_threads = talloc(NULL, fr_dlist_head_t)); |
529 | 0 | fr_dlist_init(coord_threads, fr_schedule_coord_t, thread.entry); |
530 | 0 | fr_rb_inline_talloc_init(&coords, fr_coord_t, node, coord_cmp, NULL); |
531 | |
|
532 | 0 | fr_dlist_foreach(coord_regs, fr_coord_reg_t, coord_reg) { |
533 | 0 | fr_schedule_coord_t *sc; |
534 | |
|
535 | 0 | MEM(sc = talloc_zero(coord_threads, fr_schedule_coord_t)); |
536 | |
|
537 | 0 | sc->thread.id = num++; |
538 | 0 | sc->coord_reg = coord_reg; |
539 | 0 | sc->max_workers = num_workers; |
540 | 0 | sc->sem = sem; |
541 | |
|
542 | 0 | if (fr_thread_create(&sc->thread.pthread_id, fr_coordinate_thread, sc) < 0) { |
543 | 0 | talloc_free(sc); |
544 | 0 | PERROR("Failed creating coordinator %s", coord_reg->name); |
545 | 0 | return -1; |
546 | 0 | }; |
547 | |
|
548 | 0 | fr_dlist_insert_tail(coord_threads, sc); |
549 | 0 | } |
550 | | |
551 | | /* |
552 | | * Wait for all the coordinators to start. |
553 | | */ |
554 | 0 | if (fr_thread_wait_list(sem, coord_threads) < 0) { |
555 | 0 | ERROR("Failed creating coordinator threads"); |
556 | 0 | return -1; |
557 | 0 | } |
558 | | |
559 | | /* |
560 | | * Insert the coordinators in the tree |
561 | | */ |
562 | 0 | fr_dlist_foreach(coord_threads, fr_schedule_coord_t, sc) { |
563 | 0 | fr_assert(sc->coord); |
564 | 0 | fr_rb_insert(&coords, sc->coord); |
565 | 0 | } |
566 | |
|
567 | 0 | return 0; |
568 | 0 | } |
569 | | |
570 | | /** Clean up coordinators in single threaded mode |
571 | | */ |
572 | | void fr_coords_destroy(void) |
573 | 0 | { |
574 | 0 | fr_coord_t *coord; |
575 | 0 | fr_rb_iter_inorder_t iter; |
576 | |
|
577 | 0 | if (fr_rb_num_elements(&coords) == 0) return; |
578 | | |
579 | 0 | for (coord = fr_rb_iter_init_inorder(&coords, &iter); |
580 | 0 | coord; |
581 | 0 | coord = fr_rb_iter_next_inorder(&coords, &iter)) { |
582 | 0 | fr_rb_iter_delete_inorder(&coords, &iter); |
583 | 0 | fr_coord_destroy(coord); |
584 | 0 | talloc_free(coord); |
585 | 0 | } |
586 | 0 | } |
587 | | |
588 | | /** Start coordinators in single threaded mode |
589 | | */ |
590 | | int fr_coords_create(TALLOC_CTX *ctx, fr_event_list_t *el) |
591 | 0 | { |
592 | 0 | if (!coord_regs) return 0; |
593 | | |
594 | 0 | fr_rb_inline_talloc_init(&coords, fr_coord_t, node, coord_cmp, NULL); |
595 | |
|
596 | 0 | fr_dlist_foreach(coord_regs, fr_coord_reg_t, coord_reg) { |
597 | 0 | char coordinate_name[64]; |
598 | 0 | fr_coord_t *coord; |
599 | |
|
600 | 0 | snprintf(coordinate_name, sizeof(coordinate_name), "Coordinator %s", coord_reg->name); |
601 | |
|
602 | 0 | INFO("%s - Starting", coordinate_name); |
603 | |
|
604 | 0 | coord = fr_coord_create(ctx, el, coord_reg, true, 1); |
605 | 0 | if (!coord) { |
606 | 0 | PERROR("%s - Failed creating coordinator thread", coordinate_name); |
607 | 0 | return -1; |
608 | 0 | } |
609 | | |
610 | 0 | fr_rb_insert(&coords, coord); |
611 | 0 | } |
612 | | |
613 | 0 | return 0; |
614 | 0 | } |
615 | | |
616 | | /** Signal a coordinator that a worker wants to detach |
617 | | * |
618 | | * @param cw Worker which is detaching. |
619 | | * @param exiting Is the server exiting. |
620 | | */ |
621 | | int fr_coord_detach(fr_coord_worker_t *cw, bool exiting) |
622 | 0 | { |
623 | 0 | fr_coord_worker_detach_msg_t *msg; |
624 | |
|
625 | 0 | msg = talloc(cw, fr_coord_worker_detach_msg_t); |
626 | 0 | msg->worker = fr_schedule_worker_id(); |
627 | 0 | msg->exiting = exiting; |
628 | |
|
629 | 0 | if (fr_control_message_send(cw->coord->coord_recv_control, cw->worker_send_rb, |
630 | 0 | FR_CONTROL_ID_COORD_WORKER_DETACH, |
631 | 0 | msg, sizeof(fr_coord_worker_detach_msg_t)) < 0) return -1; |
632 | | |
633 | 0 | if (!cw->coord->single_thread) fr_control_wait(cw->worker_recv_control); |
634 | |
|
635 | 0 | return 0; |
636 | 0 | } |
637 | | |
638 | | /** A worker got an ack from a coordinator in response to attach / detach |
639 | | */ |
640 | | static void coordinate_worker_ack(UNUSED void *ctx, NDEBUG_UNUSED void const *data, NDEBUG_UNUSED size_t data_size, |
641 | | UNUSED fr_time_t now) |
642 | 0 | { |
643 | 0 | #ifndef NDEBUG |
644 | 0 | fr_coord_msg_t const *cm = data; |
645 | |
|
646 | 0 | fr_assert(data_size == sizeof(fr_coord_msg_t)); |
647 | 0 | fr_assert(cm->worker == fr_schedule_worker_id()); |
648 | 0 | #endif |
649 | 0 | } |
650 | | |
651 | | /** Attach a worker to a coordinator |
652 | | * |
653 | | * @param ctx To allocate worker structure in |
654 | | * @param el Event list for control messages |
655 | | * @param coord_reg Coordinator registration to attach to. |
656 | | * @return |
657 | | * - Worker structure for coordinator use on success |
658 | | * - NULL on failure |
659 | | */ |
660 | | fr_coord_worker_t *fr_coord_attach(TALLOC_CTX *ctx, fr_event_list_t *el, fr_coord_reg_t *coord_reg) |
661 | 0 | { |
662 | 0 | fr_coord_worker_t *cw; |
663 | 0 | fr_coord_worker_cb_reg_t *cb_reg = coord_reg->worker_cb; |
664 | 0 | fr_coord_worker_attach_msg_t msg; |
665 | 0 | fr_coord_t find; |
666 | 0 | fr_atomic_queue_t *aq; |
667 | |
|
668 | 0 | cw = talloc_zero(ctx, fr_coord_worker_t); |
669 | |
|
670 | 0 | find = (fr_coord_t) { |
671 | 0 | .coord_reg = coord_reg |
672 | 0 | }; |
673 | 0 | cw->coord = fr_rb_find(&coords, &find); |
674 | 0 | if (!cw->coord) { |
675 | 0 | fail: |
676 | 0 | talloc_free(cw); |
677 | 0 | return NULL; |
678 | 0 | } |
679 | | |
680 | 0 | aq = fr_atomic_queue_talloc(cw, 1024); |
681 | 0 | cw->worker_recv_aq = fr_atomic_queue_talloc(cw, FR_CONTROL_MAX_MESSAGES); |
682 | 0 | cw->worker_recv_control = fr_control_create(cw, el, aq, 0); |
683 | 0 | cw->worker_send_rb = fr_ring_buffer_create(cw, FR_CONTROL_MAX_MESSAGES * FR_CONTROL_MAX_SIZE); |
684 | 0 | cw->worker_send_ms = fr_message_set_create(cw, FR_CONTROL_MAX_MESSAGES, sizeof(fr_coord_data_t), |
685 | 0 | coord_reg->worker_send_size, true); |
686 | |
|
687 | 0 | while (cb_reg->callback) { |
688 | 0 | cw->num_callbacks++; |
689 | 0 | cb_reg++; |
690 | 0 | } |
691 | 0 | cw->callbacks = coord_reg->worker_cb; |
692 | |
|
693 | 0 | if (fr_control_callback_add(&cw->worker_recv_control, FR_CONTROL_ID_COORD_WORKER_ACK, |
694 | 0 | cw, coordinate_worker_ack) < 0) goto fail; |
695 | 0 | if (fr_control_callback_add(&cw->worker_recv_control, FR_CONTROL_ID_COORD_DATA, |
696 | 0 | cw, coord_worker_data_recv) < 0) goto fail; |
697 | | |
698 | 0 | if (fr_control_open(cw->worker_recv_control) < 0) goto fail; |
699 | | |
700 | 0 | msg.worker_recv_control = cw->worker_recv_control; |
701 | 0 | msg.worker_recv_aq = cw->worker_recv_aq; |
702 | 0 | msg.worker = fr_schedule_worker_id(); |
703 | |
|
704 | 0 | if (fr_control_message_send(cw->coord->coord_recv_control, cw->worker_send_rb, |
705 | 0 | FR_CONTROL_ID_COORD_WORKER_ATTACH, |
706 | 0 | &msg, sizeof(fr_coord_worker_attach_msg_t)) < 0) goto fail; |
707 | | |
708 | 0 | if (!cw->coord->single_thread) fr_control_wait(cw->worker_recv_control); |
709 | |
|
710 | 0 | return cw; |
711 | 0 | } |
712 | | |
713 | | /** Send generic data from a coordinator to a worker |
714 | | * |
715 | | * @param coord Coordinator which is sending the data. |
716 | | * @param worker_id Worker to send data to. |
717 | | * @param cb_id Callback ID for the worker to run. |
718 | | * @param dbuff Buffer containing data to send. |
719 | | * @return |
720 | | * - 0 on success |
721 | | * - <0 on failure |
722 | | */ |
723 | | int fr_coord_to_worker_send(fr_coord_t *coord, int32_t worker_id, uint32_t cb_id, fr_dbuff_t *dbuff) |
724 | 0 | { |
725 | 0 | fr_coord_msg_t cm; |
726 | 0 | fr_coord_data_t *cd = NULL; |
727 | 0 | uint32_t thread_id = worker_id - MIN_WORKER_ID; |
728 | |
|
729 | 0 | fr_assert((worker_id >= MIN_WORKER_ID) && (worker_id < (int32_t)coord->max_workers)); |
730 | |
|
731 | 0 | cm = (fr_coord_msg_t) { |
732 | 0 | .worker = worker_id |
733 | 0 | }; |
734 | |
|
735 | 0 | cd = (fr_coord_data_t *)fr_message_and_data_alloc(coord->coord_send_ms[thread_id], fr_dbuff_used(dbuff)); |
736 | 0 | if (!cd) return -1; |
737 | | |
738 | 0 | memcpy(cd->m.data, fr_dbuff_buff(dbuff), fr_dbuff_used(dbuff)); |
739 | 0 | cd->coord_cb_id = cb_id; |
740 | 0 | if (!fr_atomic_queue_push(coord->coord_send_aq[thread_id], cd)) { |
741 | 0 | fr_message_done((fr_message_t *)cd); |
742 | 0 | return -1; |
743 | 0 | } |
744 | 0 | return fr_control_message_send(coord->coord_send_control[thread_id], coord->coord_send_rb[thread_id], |
745 | 0 | FR_CONTROL_ID_COORD_DATA, |
746 | 0 | &cm, sizeof(fr_coord_msg_t)); |
747 | 0 | } |
748 | | |
749 | | /** Broadcast data from a coordinator to all workers |
750 | | * |
751 | | * @param coord Coordinator which is sending the data. |
752 | | * @param cb_id Callback ID for the workers to run. |
753 | | * @param dbuff Buffer containing data to send. |
754 | | * @return |
755 | | * - 0 on success |
756 | | * - <0 on failure - indicating the number of sends which failed. |
757 | | */ |
758 | | int fr_coord_to_worker_broadcast(fr_coord_t *coord, uint32_t cb_id, fr_dbuff_t *dbuff) |
759 | 0 | { |
760 | 0 | uint32_t i; |
761 | 0 | int failed = 0; |
762 | |
|
763 | 0 | for (i = 0; i < coord->max_workers; i++) { |
764 | 0 | if (!coord->coord_send_control[i - MIN_WORKER_ID]) continue; |
765 | 0 | if (fr_coord_to_worker_send(coord, i, cb_id, dbuff) < 0) failed++; |
766 | 0 | } |
767 | |
|
768 | 0 | return 0 - failed; |
769 | 0 | } |
770 | | |
771 | | /** Send data from a worker to a coordinator |
772 | | * |
773 | | * @param cw Worker side of coordinator sending the data. |
774 | | * @param cb_id Callback ID for the coordinator to run. |
775 | | * @param dbuff Buffer containing data to send. |
776 | | * @return |
777 | | * - 0 on success |
778 | | * - < 0 on failure |
779 | | */ |
780 | | int fr_worker_to_coord_send(fr_coord_worker_t *cw, uint32_t cb_id, fr_dbuff_t *dbuff) |
781 | 0 | { |
782 | 0 | fr_coord_msg_t cm; |
783 | 0 | fr_coord_data_t *cd = NULL; |
784 | |
|
785 | 0 | cm = (fr_coord_msg_t) { |
786 | 0 | .worker = fr_schedule_worker_id() |
787 | 0 | }; |
788 | |
|
789 | 0 | cd = (fr_coord_data_t *) fr_message_and_data_commit(cw->worker_send_ms, (fr_message_t *)cd, fr_dbuff_used(dbuff)); |
790 | 0 | if (!cd) return -1; |
791 | | |
792 | 0 | memcpy(cd->m.data, fr_dbuff_buff(dbuff), fr_dbuff_used(dbuff)); |
793 | 0 | cd->coord_cb_id = cb_id; |
794 | 0 | if (!fr_atomic_queue_push(cw->coord->coord_recv_aq, cd)) { |
795 | 0 | fr_message_done((fr_message_t *)cd); |
796 | 0 | return -1; |
797 | 0 | } |
798 | | |
799 | 0 | return fr_control_message_send(cw->coord->coord_recv_control, cw->worker_send_rb, |
800 | 0 | FR_CONTROL_ID_COORD_DATA, &cm, sizeof(fr_coord_msg_t)); |
801 | 0 | } |
802 | | |
803 | | /** Insert instance specific pre-event callbacks |
804 | | */ |
805 | | int fr_coord_pre_event_insert(fr_event_list_t *el) |
806 | 0 | { |
807 | 0 | fr_coord_t *coord; |
808 | 0 | fr_rb_iter_inorder_t iter; |
809 | 0 | fr_coord_cb_inst_t *cb_inst; |
810 | 0 | uint32_t i; |
811 | |
|
812 | 0 | if (!coord_regs) return 0; |
813 | | |
814 | 0 | for (coord = fr_rb_iter_init_inorder(&coords, &iter); |
815 | 0 | coord != NULL; |
816 | 0 | coord = fr_rb_iter_next_inorder(&coords, &iter)) { |
817 | 0 | for (i = 0; i < coord->num_callbacks; i++) { |
818 | 0 | cb_inst = coord->cb_inst[i]; |
819 | 0 | if (cb_inst && cb_inst->event_pre_cb && |
820 | 0 | fr_event_pre_insert(el, cb_inst->event_pre_cb, cb_inst->inst_data) < 0) { |
821 | 0 | return -1; |
822 | 0 | } |
823 | 0 | } |
824 | 0 | } |
825 | 0 | return 0; |
826 | 0 | } |
827 | | |
828 | | /** Insert instance specific post-event callbacks |
829 | | */ |
830 | | int fr_coord_post_event_insert(fr_event_list_t *el) |
831 | 0 | { |
832 | 0 | fr_coord_t *coord; |
833 | 0 | fr_rb_iter_inorder_t iter; |
834 | 0 | fr_coord_cb_inst_t *cb_inst; |
835 | 0 | uint32_t i; |
836 | |
|
837 | 0 | if (!coord_regs) return 0; |
838 | | |
839 | 0 | for (coord = fr_rb_iter_init_inorder(&coords, &iter); |
840 | 0 | coord != NULL; |
841 | 0 | coord = fr_rb_iter_next_inorder(&coords, &iter)) { |
842 | 0 | for (i = 0; i < coord->num_callbacks; i++) { |
843 | 0 | cb_inst = coord->cb_inst[i]; |
844 | 0 | if (cb_inst && cb_inst->event_post_cb && |
845 | 0 | fr_event_post_insert(el, cb_inst->event_post_cb, cb_inst->inst_data) < 0) { |
846 | 0 | return -1; |
847 | 0 | } |
848 | 0 | } |
849 | 0 | } |
850 | 0 | return 0; |
851 | 0 | } |