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/atomic_queue.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: bab0f8ce24029c297095d6f8f641a0a4eb1710b8 $
19
 *
20
 * @brief Thread-safe queues.
21
 * @file io/atomic_queue.c
22
 *
23
 * This is an implementation of a bounded MPMC ring buffer with per-slot
24
 * sequence numbers, as described by Dmitry Vyukov, and independently
25
 * discovered/implemented by Alister Winfield.
26
 *
27
 * @copyright 2026 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
28
 * @copyright 2016 Alan DeKok (aland@freeradius.org)
29
 * @copyright 2016 Alister Winfield
30
 */
31
32
RCSID("$Id: bab0f8ce24029c297095d6f8f641a0a4eb1710b8 $")
33
34
#include <stdint.h>
35
#include <stdalign.h>
36
#include <inttypes.h>
37
#include <stdlib.h>
38
39
#include <freeradius-devel/autoconf.h>
40
#include <freeradius-devel/io/atomic_queue.h>
41
#include <freeradius-devel/util/math.h>
42
43
/*
44
 *  Some macros to make our life easier.  Not every macro is used.
45
 */
46
DIAG_OFF(unused-macros)
47
#define atomic_int64_t _Atomic(int64_t)
48
#define atomic_uint32_t _Atomic(uint32_t)
49
#define atomic_uint64_t _Atomic(uint64_t)
50
51
0
#define cas_incr(_store, _var)    atomic_compare_exchange_strong_explicit(&_store, &_var, _var + 1, memory_order_release, memory_order_relaxed)
52
#define cas_decr(_store, _var)    atomic_compare_exchange_strong_explicit(&_store, &_var, _var - 1, memory_order_release, memory_order_relaxed)
53
0
#define load(_var)            atomic_load_explicit(&_var, memory_order_relaxed)
54
0
#define acquire(_var)         atomic_load_explicit(&_var, memory_order_acquire)
55
0
#define store(_store, _var)   atomic_store_explicit(&_store, _var, memory_order_release)
56
DIAG_ON(unused-macros)
57
58
/*
59
 *  The distance two addresses need to be apart so that one core
60
 *  writing does not slow another core reading. On Apple silicon
61
 *      the hardware line is 128 bytes.  On x86 the hardware line is
62
 *      64 bytes but the adjacent line prefetcher fetches lines in
63
 *      128 byte pairs, so neighbouring 64 byte lines still appear to
64
 *      contend in benchmarks.
65
 */
66
0
#define CACHE_LINE_SIZE 128
67
68
/** One slot in the queue, holding one queued pointer
69
 *
70
 * Must not be packed.  Packing drops the struct's alignment to 1
71
 * (same as an array of uint8_t), so the compiler has to treat `seq`
72
 * as possibly unaligned.  The native atomic instructions require an
73
 * aligned address, so clang stops emitting them and calls the
74
 * libatomic fallback instead, which takes a mutex.  That turns every
75
 * push and pop on this lock free queue into a locked operation.  gcc
76
 * emits the native instruction regardless, and relies on the address
77
 * being aligned at runtime.  Packing saves nothing here anyway, as the
78
 * two 8 byte fields have no padding between them.
79
 */
80
typedef struct {
81
  atomic_int64_t          seq;    //!< Must be seq then data to ensure
82
                  ///< seq is 64bit aligned for 32bit address
83
                  ///< spaces.
84
  void            *data;
85
} fr_atomic_queue_slot_t;
86
87
/** How many stripes a line contains
88
 *
89
 */
90
0
#define STRIPES_PER_LINE  (CACHE_LINE_SIZE / sizeof(fr_atomic_queue_slot_t))
91
92
/** One cache line of stripes
93
 *
94
 * A line is padded and aligned to span exactly ONE cache line.
95
 * A stripe is the set of slots at the same offset in every line, so
96
 * it runs the complete length of the queue memory.
97
 *
98
 * Stripes let a line hold several slots without the slots sharing a
99
 * cache line with their neighbours in queue order.  Padding each slot
100
 * out to a full cache line instead would waste the majority of queue
101
 * memory for no gain.
102
 *
103
 * Slot `i` translates to `line[i % num_lines].stripe[i / num_lines]`, i.e.
104
 * for stripe 0, slot positions match line positions (slot 0 at line 0),
105
 * and once the slot number exceeds the number of entries in the array we wrap,
106
 * and use the next stripe.
107
 *
108
 @verbatim
109
                 stripe[0]  stripe[1]  stripe[2]  ...  stripe[7]
110
               +----------+----------+----------+     +----------+
111
     line[0]   |  slot 0  |  slot 4  |  slot 8  | ... |  slot 28 |  <- one cache line
112
               +----------+----------+----------+     +----------+
113
     line[1]   |  slot 1  |  slot 5  |  slot 9  | ... |  slot 29 |
114
               +----------+----------+----------+     +----------+
115
     line[2]   |  slot 2  |  slot 6  |  slot 10 | ... |  slot 30 |
116
               +----------+----------+----------+     +----------+
117
     line[3]   |  slot 3  |  slot 7  |  slot 11 | ... |  slot 31 |
118
               +----------+----------+----------+     +----------+
119
                    ^
120
                    one stripe: slots 0..3, one per line
121
 @endverbatim
122
 *
123
 * A producer at position `p` and a consumer at position `c` share a
124
 * line only when `p` and `c` are congruent modulo the number of lines,
125
 * this happens rarely enough that there's not a noticeable impact on
126
 * performance.
127
 */
128
typedef struct CC_HINT(aligned(CACHE_LINE_SIZE)) {
129
  fr_atomic_queue_slot_t        stripe[STRIPES_PER_LINE];
130
} fr_atomic_queue_line_t;
131
132
/** Structure to hold the atomic queue
133
 *
134
 * @note DO NOT redorder these fields without understanding how alignas works
135
 * and maintaining separation. The head and tail must be in different cache lines
136
 * to reduce contention between producers and consumers. Cold data (size, chunk)
137
 * can share a cache line, but must be separated from head, tail and the line array.
138
 */
139
struct fr_atomic_queue_s {
140
  alignas(CACHE_LINE_SIZE) atomic_int64_t   head;   //!< Position of the producer.
141
                  ///< Cache aligned bytes to ensure it's in a
142
                  ///< different cache line to tail to reduce
143
                  ///< memory contention.
144
145
  alignas(CACHE_LINE_SIZE) atomic_int64_t   tail;   //!< Position of the consumer.
146
                  ///< Cache aligned bytes to ensure it's in a
147
                  ///< different cache line to tail to reduce
148
                  ///< memory contention.
149
                  ///< Reads may still need to occur from size
150
                  ///< whilst the producer is writing to tail.
151
152
  alignas(CACHE_LINE_SIZE) size_t     size;   //!< The length of the queue.  This is static.
153
                  ///< Also needs to be cache aligned, otherwise
154
                  ///< it can end up directly after tail in memory
155
                  ///< and share a cache line.
156
157
158
  size_t            line_mask;  //!< Low bits of a slot index give its line:
159
                  ///< `line[slot & line_mask]`.  Set at init to
160
                  ///< num_lines - 1.
161
162
  uint8_t           line_shift; //!< High bits of a slot index give its stripe
163
                  ///< within that line:
164
                  ///< `.stripe[slot >> line_shift]`.  Equal to
165
                  ///< log2(num_lines).  Set at init,
166
                  ///< so a lookup is a mask and a shift, no
167
                  ///< division and no modulo!
168
169
  void            *chunk;   //!< The start of the talloc chunk to pass to free,
170
                  ///< or NULL if this queue was allocated raw via
171
                  ///< #fr_atomic_queue_malloc.  We need to play
172
                  ///< tricks to get aligned memory with talloc.
173
174
  alignas(CACHE_LINE_SIZE) fr_atomic_queue_line_t line[];         //!< The line array, aligned with cache lines
175
                  ///< to ensure producer and consumers don't conflict.
176
};
177
178
/** Number of cache lines needed to hold `size` slots
179
 *
180
 * A queue smaller than one line uses a single, partly filled line.
181
 *
182
 * @param[in] size  Slot count, already rounded up to a power of 2.
183
 */
184
static inline CC_HINT(always_inline) size_t atomic_queue_num_lines(size_t size)
185
0
{
186
0
  if (size < STRIPES_PER_LINE) return 1;
187
188
0
  return size / STRIPES_PER_LINE;
189
0
}
190
191
/** Bytes needed for a queue of `size` slots, header included
192
 *
193
 * @param[in] size  Slot count, already rounded up to a power of 2.
194
 */
195
static inline CC_HINT(always_inline) size_t atomic_queue_bytes(size_t size)
196
0
{
197
0
  return sizeof(fr_atomic_queue_t) + (atomic_queue_num_lines(size) * sizeof(fr_atomic_queue_line_t));
198
0
}
199
200
/** Map a queue position to its slot
201
 *
202
 * Positions wrap at `size`.  Within one stripe, consecutive positions increment
203
 * one line at a time, and move to the next stripe once every line has been
204
 * visited.
205
 *
206
 * @param[in] aq  The queue.
207
 * @param[in] pos The head or tail position, unmasked.
208
 */
209
static inline CC_HINT(always_inline) fr_atomic_queue_slot_t *atomic_queue_slot(fr_atomic_queue_t *aq, int64_t pos)
210
0
{
211
0
  size_t  idx = (size_t)pos & (aq->size - 1);
212
213
0
  return &aq->line[idx & aq->line_mask].stripe[idx >> aq->line_shift];
214
0
}
215
216
/** Initialise the sequence numbers and head/tail on a fresh queue buffer
217
 *
218
 * Shared between the talloc and raw allocators.  The buffer must already
219
 * be cache-line aligned and sized to hold `size` slots.
220
 *
221
 * @param[in] aq  The queue buffer to initialise.
222
 * @param[in] size  Slot count, already rounded up to a power of 2.
223
 */
224
static void atomic_queue_init(fr_atomic_queue_t *aq, size_t size)
225
0
{
226
0
  size_t  i, num_lines;
227
228
0
  num_lines = atomic_queue_num_lines(size);
229
230
0
  aq->size = size;
231
0
  aq->line_mask = num_lines - 1;
232
  /*
233
   *  num_lines is a power of two, so the shift is its log2.
234
   *  The check here is mostly to quiet clang scan, which
235
   *  complained about the potential underflow.
236
   */
237
0
  if (num_lines > 1) {
238
0
    aq->line_shift = fr_high_bit_pos(num_lines) - 1;
239
0
  } else {
240
0
    aq->line_shift = 0;
241
0
  }
242
243
  /*
244
   *  Initialize the slots.  Data is NULL, and the sequence
245
   *  number is the position of the slot.
246
   */
247
0
  for (i = 0; i < size; i++) {
248
0
    fr_atomic_queue_slot_t  *slot = atomic_queue_slot(aq, (int64_t)i);
249
250
0
    slot->data = NULL;
251
0
    store(slot->seq, (int64_t)i);
252
0
  }
253
254
0
  store(aq->head, 0);
255
0
  store(aq->tail, 0);
256
0
  atomic_thread_fence(memory_order_seq_cst);
257
0
}
258
259
/** Create fixed-size atomic queue
260
 *
261
 * @note the queue must be freed explicitly by the ctx being freed, or by using
262
 * the #fr_atomic_queue_free function.
263
 *
264
 * @param[in] ctx The talloc ctx to allocate the queue in.
265
 * @param[in] size  The number of entries in the queue.
266
 * @return
267
 *     - NULL on error.
268
 *     - fr_atomic_queue_t *, a pointer to the allocated and initialized queue.
269
 */
270
fr_atomic_queue_t *fr_atomic_queue_talloc(TALLOC_CTX *ctx, size_t size)
271
0
{
272
0
  fr_atomic_queue_t *aq;
273
0
  TALLOC_CTX    *chunk;
274
275
0
  if (size == 0) return NULL;
276
277
  /*
278
   *  Roundup to the next power of 2 so we don't need modulo.
279
   */
280
0
  size = (size_t)fr_roundup_pow2_uint64((uint64_t)size);
281
282
  /*
283
   *  Allocate a contiguous blob for the header and queue.
284
   *  This helps with memory locality.
285
   *
286
   *  Since we're allocating a blob, we should also set the
287
   *  name of the data, too.
288
   */
289
0
  chunk = talloc_aligned_array(ctx, (void **)&aq, CACHE_LINE_SIZE, atomic_queue_bytes(size));
290
0
  if (!chunk) return NULL;
291
0
  aq->chunk = chunk;
292
293
0
  talloc_set_name_const(chunk, "fr_atomic_queue_t");
294
295
0
  atomic_queue_init(aq, size);
296
297
0
  return aq;
298
0
}
299
300
/** Create fixed-size atomic queue outside any talloc hierarchy
301
 *
302
 * Backed by `posix_memalign`.  The queue has no owner: `chunk` is NULL
303
 * and the caller releases it with #fr_atomic_queue_free or plain `free()`.
304
 *
305
 * For callers that allocate from threads where talloc is not safe (for
306
 * example, library-owned callback threads).
307
 *
308
 * @param[in] size  The number of entries in the queue.
309
 * @return
310
 *  - NULL on error.
311
 *  - fr_atomic_queue_t *, a pointer to the allocated and initialized queue.
312
 */
313
static fr_atomic_queue_t *atomic_queue_malloc_raw(size_t size)
314
0
{
315
0
  fr_atomic_queue_t *aq;
316
317
0
  if (size == 0) return NULL;
318
319
0
  size = (size_t)fr_roundup_pow2_uint64((uint64_t)size);
320
321
0
  if (posix_memalign((void **)&aq, CACHE_LINE_SIZE, atomic_queue_bytes(size)) != 0) return NULL;
322
323
0
  aq->chunk = NULL;
324
0
  atomic_queue_init(aq, size);
325
326
0
  return aq;
327
0
}
328
329
/** Owner handle for a raw queue allocation
330
 *
331
 * The queue memory stays outside the talloc hierarchy.  This handle is
332
 * the only talloc'd part, so freeing the context the handle lives in
333
 * frees the queue.
334
 */
335
typedef struct {
336
  fr_atomic_queue_t *aq;    //!< The raw allocation to release.
337
} fr_atomic_queue_owner_t;
338
339
static int _atomic_queue_owner_free(fr_atomic_queue_owner_t *owner)
340
0
{
341
0
  free(owner->aq);
342
343
0
  return 0;
344
0
}
345
346
/** Create fixed-size atomic queue outside the talloc hierarchy, owned by a talloc context
347
 *
348
 * The queue itself is a raw `posix_memalign` allocation, so it can be
349
 * pushed to and popped from by threads where talloc is not safe, and
350
 * can later be placed in memory talloc does not manage.  A small owner
351
 * handle allocated in `ctx` frees the queue when `ctx` is freed, or
352
 * when the caller calls #fr_atomic_queue_free.
353
 *
354
 * @param[in] ctx The talloc ctx which owns the queue.
355
 * @param[in] size  The number of entries in the queue.
356
 * @return
357
 *  - NULL on error.
358
 *  - fr_atomic_queue_t *, a pointer to the allocated and initialized queue.
359
 */
360
fr_atomic_queue_t *fr_atomic_queue_malloc(TALLOC_CTX *ctx, size_t size)
361
0
{
362
0
  fr_atomic_queue_t *aq;
363
0
  fr_atomic_queue_owner_t *owner;
364
365
0
  aq = atomic_queue_malloc_raw(size);
366
0
  if (!aq) return NULL;
367
368
0
  owner = talloc(ctx, fr_atomic_queue_owner_t);
369
0
  if (!owner) {
370
0
    free(aq);
371
0
    return NULL;
372
0
  }
373
0
  owner->aq = aq;
374
0
  talloc_set_destructor(owner, _atomic_queue_owner_free);
375
0
  aq->chunk = owner;
376
377
0
  return aq;
378
0
}
379
380
/** Free an atomic queue if it's not freed by ctx
381
 *
382
 * The queue memory must be cache line aligned, so it lives in one of
383
 * three places: a talloc aligned array (`chunk` is the array), a raw
384
 * allocation owned by a talloc handle (`chunk` is the handle, whose
385
 * destructor frees the queue), or a raw allocation with no owner
386
 * (`chunk` is NULL).
387
 */
388
void fr_atomic_queue_free(fr_atomic_queue_t **aq)
389
0
{
390
0
  if (!*aq) return;
391
392
0
  if ((*aq)->chunk) {
393
0
    talloc_free((*aq)->chunk);
394
0
  } else {
395
0
    free(*aq);
396
0
  }
397
0
  *aq = NULL;
398
0
}
399
400
/** Push a pointer into the atomic queue
401
 *
402
 * @param[in] aq  The atomic queue to add data to.
403
 * @param[in] data  to push.
404
 * @return
405
 *  - true on successful push
406
 *  - false on queue full
407
 */
408
bool fr_atomic_queue_push(fr_atomic_queue_t *aq, void *data)
409
0
{
410
0
  int64_t head;
411
0
  fr_atomic_queue_slot_t *slot;
412
413
0
  if (!data) return false;
414
415
  /*
416
   *  Here we're essentially racing with other producers
417
   *  to find the current head of the queue.
418
   *
419
   *  1. Load the current head (which may be incremented
420
   *     by another producer before we enter the loop).
421
   *  2. Find the head slot, which is head modulo the
422
   *     queue size (keeps head looping through the queue).
423
   *  3. Read the sequence number of the slot.
424
   *     The sequence numbers are initialised to the index
425
   *     of the slots in the queue.  Each pass of the
426
   *     producer increments the sequence number by one.
427
   *  4.
428
   *     a. If the sequence number is equal to the head,
429
   *     then we can use the slot. Increment the head
430
   *     so other producers know we've used it.
431
   *     b. If it's greater than head, the producer has
432
   *         already written to this slot, so we need to re-load
433
   *     the head and race other producers again.
434
   *     c. If it's less than the head, the slot has not yet
435
   *     been consumed, and the queue is full.
436
   */
437
0
  head = load(aq->head);
438
439
  /*
440
   *  Try to find the current head.
441
   */
442
0
  for (;;) {
443
0
    int64_t seq, diff;
444
445
    /*
446
     *  Alloc function guarantees size is a power
447
     *  of 2, so we can use this hack to avoid
448
     *  modulo.
449
     */
450
0
    slot = atomic_queue_slot(aq, head);
451
0
    seq = acquire(slot->seq);
452
0
    diff = (seq - head);
453
454
    /*
455
     *  head is larger than the current slot, the
456
     *  queue is full.
457
     *  The consumer will set slot seq to slot +
458
     *  queue size, marking it as free for the
459
     *  producer to use.
460
     */
461
0
    if (diff < 0) {
462
#if 0
463
      fr_atomic_queue_debug(stderr, aq);
464
#endif
465
0
      return false;
466
0
    }
467
468
    /*
469
     *  Someone else has already written to this slot
470
     *  we lost the race, try again.
471
     */
472
0
    if (diff > 0) {
473
0
      head = load(aq->head);
474
0
      continue;
475
0
    }
476
477
    /*
478
     *  See if we can increment the head value
479
     *  (and check it's still at its old value).
480
     *
481
     *  This means no two producers can have the same
482
     *  slot in the queue, because they can't exit
483
     *  the loop until they've incremented the head
484
     *  successfully.
485
     *
486
     *  When we fail, we don't increment head before
487
     *  trying again, because we need to detect queue
488
     *  full conditions.
489
     */
490
0
    if (cas_incr(aq->head, head)) {
491
0
      break;
492
0
    }
493
0
  }
494
495
  /*
496
   *  Store the data in the queue, and increment the slot
497
   *  with the new index, and make the write visible to
498
   *  other CPUs.
499
   */
500
0
  slot->data = data;
501
502
  /*
503
   *  Technically head can overflow.  Practically, with a
504
   *  3GHz CPU, doing nothing but incrementing head
505
   *  uncontended it'd take about 100 years for this to
506
   *  happen.  But hey, maybe someone invents an optical
507
   *  CPU with a significantly higher clock speed, it's ok
508
   *  for us to exit every 9 quintillion packets.
509
   */
510
#ifdef __clang_analyzer__
511
  if (unlikely((head + 1) == INT64_MAX)) exit(1);
512
#endif
513
514
  /*
515
   *  Mark up the slot as written to.  Any other producer
516
   *  attempting to write will see (diff > 0) and retry.
517
   */
518
0
  store(slot->seq, head + 1);
519
0
  return true;
520
0
}
521
522
523
/** Pop a pointer from the atomic queue
524
 *
525
 * @param[in] aq  the atomic queue to retrieve data from.
526
 * @param[out] p_data where to write the data.
527
 * @return
528
 *  - true on successful pop
529
 *  - false on queue empty
530
 */
531
bool fr_atomic_queue_pop(fr_atomic_queue_t *aq, void **p_data)
532
0
{
533
0
  int64_t     tail, seq;
534
0
  fr_atomic_queue_slot_t  *slot;
535
536
0
  if (!p_data) return false;
537
538
0
  tail = load(aq->tail);
539
540
0
  for (;;) {
541
0
    int64_t diff;
542
543
0
    slot = atomic_queue_slot(aq, tail);
544
0
    seq = acquire(slot->seq);
545
546
0
    diff = (seq - (tail + 1));
547
548
    /*
549
     *  Tail is smaller than the current slot,
550
     *  the queue is empty.
551
     *
552
     *  Tail should now be equal to the head.
553
     */
554
0
    if (diff < 0) {
555
0
      return false;
556
0
    }
557
558
    /*
559
     *  Tail is now ahead of us.
560
     *  Something else has consumed it.
561
     *  We lost the race with another consumer.
562
     */
563
0
    if (diff > 0) {
564
0
      tail = load(aq->tail);
565
0
      continue;
566
0
    }
567
568
    /*
569
     *  Same deal as push.
570
     *  After this point we own the slot.
571
     */
572
0
    if (cas_incr(aq->tail, tail)) {
573
0
      break;
574
0
    }
575
0
  }
576
577
  /*
578
   *  Copy the pointer to the caller BEFORE updating the
579
   *  queue slot.
580
   */
581
0
  *p_data = slot->data;
582
583
  /*
584
   *  Set the current slot to past the end of the queue.
585
   *  This is equal to what head will be on its next pass
586
   *  through the queue.  This marks the slot as free.
587
   */
588
0
  store(slot->seq, tail + aq->size);
589
590
0
  return true;
591
0
}
592
593
size_t fr_atomic_queue_size(fr_atomic_queue_t *aq)
594
0
{
595
0
  return aq->size;
596
0
}
597
598
/*
599
 *  Segmented single-producer / single-consumer ring.
600
 *
601
 *  Safety argument: one producer owns `head` and writes `seg->next`
602
 *  exactly once (release).  One consumer owns `tail` and reads
603
 *  `tail->next` with acquire; by the release/acquire pair plus the
604
 *  invariant "no push into s after s->next is set" the consumer
605
 *  cannot advance past a segment that still has an in-flight push
606
 *  as long as it retries `pop(tail->q)` once more after observing
607
 *  `tail->next != NULL`.
608
 */
609
610
typedef struct fr_atomic_ring_segment_s fr_atomic_ring_segment_t;
611
612
struct fr_atomic_ring_segment_s {
613
  fr_atomic_queue_t   *q;   //!< Per-segment MPMC ring (used SPSC here).
614
  _Atomic(fr_atomic_ring_segment_t *) next;   //!< NULL until the producer seals this segment
615
              ///< because it filled up and moved on to a
616
              ///< fresh one.
617
};
618
619
struct fr_atomic_ring_s {
620
  size_t        seg_size; //!< Capacity of each segment.
621
  _Atomic(fr_atomic_ring_segment_t *) head;   //!< Producer end.  Writer is the single producer,
622
              ///< reader is also the producer - the consumer
623
              ///< never loads this.
624
  fr_atomic_ring_segment_t    *tail;    //!< Consumer end.  Touched only by the consumer.
625
};
626
627
/** Allocate a fresh segment and its embedded queue
628
 *
629
 * Uses the raw (non-talloc) allocator so this function is safe to call
630
 * from the producer thread even when that thread cannot safely use talloc.
631
 */
632
static fr_atomic_ring_segment_t *atomic_ring_segment_alloc(size_t seg_size)
633
0
{
634
0
  fr_atomic_ring_segment_t  *s;
635
636
0
  s = malloc(sizeof(*s));
637
0
  if (!s) return NULL;
638
639
0
  s->q = atomic_queue_malloc_raw(seg_size);
640
0
  if (!s->q) {
641
0
    free(s);
642
0
    return NULL;
643
0
  }
644
0
  atomic_init(&s->next, NULL);
645
646
0
  return s;
647
0
}
648
649
static void atomic_ring_segment_free(fr_atomic_ring_segment_t *s)
650
0
{
651
0
  fr_atomic_queue_free(&s->q);
652
0
  free(s);
653
0
}
654
655
/** talloc destructor for #fr_atomic_ring_t: walk the chain and free segments */
656
static int _atomic_ring_free(fr_atomic_ring_t *ring)
657
0
{
658
0
  fr_atomic_ring_segment_t  *s = ring->tail;
659
660
0
  while (s) {
661
0
    fr_atomic_ring_segment_t *next = atomic_load_explicit(&s->next, memory_order_acquire);
662
663
0
    atomic_ring_segment_free(s);
664
0
    s = next;
665
0
  }
666
667
0
  return 0;
668
0
}
669
670
/** Allocate an empty SPSC ring
671
 *
672
 * @param[in] ctx talloc ctx that owns the ring handle (segments live
673
 *      outside talloc; they are freed by the ring's
674
 *      destructor).
675
 * @param[in] seg_size  Per-segment capacity.  Rounded up to a power of 2.
676
 * @return
677
 *  - NULL on error.
678
 *  - A ring containing one initial (empty) segment.
679
 */
680
fr_atomic_ring_t *fr_atomic_ring_alloc(TALLOC_CTX *ctx, size_t seg_size)
681
0
{
682
0
  fr_atomic_ring_t  *ring;
683
0
  fr_atomic_ring_segment_t  *seg;
684
685
0
  if (seg_size == 0) return NULL;
686
687
0
  ring = talloc(ctx, fr_atomic_ring_t);
688
0
  if (!ring) return NULL;
689
690
0
  seg = atomic_ring_segment_alloc(seg_size);
691
0
  if (!seg) {
692
0
    talloc_free(ring);
693
0
    return NULL;
694
0
  }
695
696
0
  ring->seg_size = seg_size;
697
0
  ring->tail = seg;
698
0
  atomic_init(&ring->head, seg);
699
0
  talloc_set_destructor(ring, _atomic_ring_free);
700
701
0
  return ring;
702
0
}
703
704
/** Free the ring and all remaining segments
705
 *
706
 * Equivalent to `talloc_free()` on the ring, but nulls the caller's
707
 * handle in the style of #fr_atomic_queue_free.
708
 */
709
void fr_atomic_ring_free(fr_atomic_ring_t **ring_p)
710
0
{
711
0
  if (!*ring_p) return;
712
713
0
  talloc_free(*ring_p);
714
0
  *ring_p = NULL;
715
0
}
716
717
/** Link a new segment after the current head and make it the head
718
 *
719
 * The consumer only inspects `h->next` and advances past `h` once it
720
 * sees a non-NULL value there, so `h->next` is published with release
721
 * ordering, which pairs with the acquire in fr_atomic_ring_pop().
722
 *
723
 * Modelled for Coverity in src/coverity-model/merged_model.c, as the
724
 * analyser does not treat an atomic store as publishing the pointer.
725
 *
726
 * @param[in] ring  the segment belongs to.
727
 * @param[in] h   Current head segment.
728
 * @param[in] n   New segment to publish.
729
 */
730
static inline CC_HINT(always_inline) void atomic_ring_segment_publish(fr_atomic_ring_t *ring, fr_atomic_ring_segment_t *h,
731
          fr_atomic_ring_segment_t *n)
732
0
{
733
0
  atomic_store_explicit(&h->next, n, memory_order_release);
734
0
  atomic_store_explicit(&ring->head, n, memory_order_relaxed);
735
0
}
736
737
/** Push a pointer into the ring; allocate a new segment on overflow
738
 *
739
 * Single-producer only.  Must not be called concurrently with itself.
740
 *
741
 * @param[in] ring  Ring to push into.
742
 * @param[in] data  Value to push (must be non-NULL).
743
 * @return
744
 *  - true on success.
745
 *  - false if both the current segment is full and a new segment
746
 *    could not be allocated.
747
 */
748
bool fr_atomic_ring_push(fr_atomic_ring_t *ring, void *data)
749
0
{
750
0
  fr_atomic_ring_segment_t  *h;
751
0
  fr_atomic_ring_segment_t  *n;
752
753
0
  h = atomic_load_explicit(&ring->head, memory_order_relaxed);
754
755
0
  if (likely(fr_atomic_queue_push(h->q, data))) return true;
756
757
0
  n = atomic_ring_segment_alloc(ring->seg_size);
758
0
  if (unlikely(!n)) return false;
759
760
0
  atomic_ring_segment_publish(ring, h, n);
761
762
0
  return fr_atomic_queue_push(n->q, data);
763
0
}
764
765
/** Pop a pointer from the ring, advancing past drained segments
766
 *
767
 * Single-consumer only.  Must not be called concurrently with itself.
768
 *
769
 * @param[in] ring  Ring to pop from.
770
 * @param[out] p_data Where to write the popped value on success.
771
 * @return
772
 *  - true if a value was popped.
773
 *  - false if the ring is currently empty.
774
 */
775
bool fr_atomic_ring_pop(fr_atomic_ring_t *ring, void **p_data)
776
0
{
777
0
  fr_atomic_ring_segment_t  *cur;
778
0
  fr_atomic_ring_segment_t  *n;
779
0
  fr_atomic_ring_segment_t  *old;
780
781
0
  for (;;) {
782
0
    cur = ring->tail;
783
784
0
    if (likely(fr_atomic_queue_pop(cur->q, p_data))) return true;
785
786
    /*
787
     *  Empty from our point of view.  If the producer hasn't
788
     *  sealed this segment there might be pushes in our
789
     *  future - return and let the caller come back.
790
     */
791
0
    n = atomic_load_explicit(&cur->next, memory_order_acquire);
792
0
    if (!n) return false;
793
794
    /*
795
     *  Sealed.  One more pop to drain anything the producer
796
     *  committed before sealing but after our first (empty)
797
     *  pop observation.  Without this re-check, late commits
798
     *  in the (empty-observation, seal-observation) window
799
     *  would be stranded when we advance past `cur`.
800
     */
801
0
    if (fr_atomic_queue_pop(cur->q, p_data)) return true;
802
803
0
    old = cur;
804
0
    ring->tail = n;
805
0
    atomic_ring_segment_free(old);
806
    /* loop to pop from the new tail */
807
0
  }
808
0
}
809
810
#ifdef WITH_VERIFY_PTR
811
/** Check the talloc chunk is still valid
812
 *
813
 */
814
void fr_atomic_queue_verify(fr_atomic_queue_t *aq)
815
0
{
816
0
  (void)talloc_get_type_abort(aq->chunk, fr_atomic_queue_t);
817
0
}
818
#endif
819
820
#ifndef NDEBUG
821
822
#if 0
823
typedef struct {
824
  int     status;   //!< status of this message
825
  size_t      data_size;      //!< size of the data we're sending
826
827
  int     signal;   //!< the signal to send
828
  uint64_t    ack;    //!< or the endpoint..
829
  void      *ch;    //!< the channel
830
} fr_control_message_t;
831
#endif
832
833
834
/**  Dump an atomic queue.
835
 *
836
 * Absolutely NOT thread-safe.
837
 *
838
 * @param[in] aq  The atomic queue to debug.
839
 * @param[in] fp  where the debugging information will be printed.
840
 */
841
void fr_atomic_queue_debug(FILE * fp, fr_atomic_queue_t *aq)
842
0
{
843
0
  size_t i;
844
0
  int64_t head, tail;
845
846
0
  head = load(aq->head);
847
0
  tail = load(aq->tail);
848
849
0
  fprintf(fp, "AQ %p size %zu, head %" PRId64 ", tail %" PRId64 "\n",
850
0
    aq, aq->size, head, tail);
851
852
0
  for (i = 0; i < aq->size; i++) {
853
0
    fr_atomic_queue_slot_t *slot;
854
855
0
    slot = atomic_queue_slot(aq, (int64_t)i);
856
857
0
    fprintf(fp, "\t[%zu] = { %p, %" PRId64 " }",
858
0
      i, slot->data, load(slot->seq));
859
#if 0
860
    if (slot->data) {
861
      fr_control_message_t *c;
862
863
      c = slot->data;
864
865
      fprintf(fp, "\tstatus %d, data_size %zd, signal %d, ack %zd, ch %p",
866
        c->status, c->data_size, c->signal, c->ack, c->ch);
867
    }
868
#endif
869
0
    fprintf(fp, "\n");
870
0
  }
871
0
}
872
#endif