Coverage Report

Created: 2026-08-14 07:17

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