The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
atomic_queue.c
Go to the documentation of this file.
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
32RCSID("$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 */
46DIAG_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#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#define load(_var) atomic_load_explicit(&_var, memory_order_relaxed)
54#define acquire(_var) atomic_load_explicit(&_var, memory_order_acquire)
55#define store(_store, _var) atomic_store_explicit(&_store, _var, memory_order_release)
56DIAG_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#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 */
80typedef 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;
86
87/** How many stripes a line contains
88 *
89 */
90#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 */
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 */
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 */
184static inline CC_HINT(always_inline) size_t atomic_queue_num_lines(size_t size)
185{
186 if (size < STRIPES_PER_LINE) return 1;
187
188 return size / STRIPES_PER_LINE;
189}
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 */
195static inline CC_HINT(always_inline) size_t atomic_queue_bytes(size_t size)
196{
197 return sizeof(fr_atomic_queue_t) + (atomic_queue_num_lines(size) * sizeof(fr_atomic_queue_line_t));
198}
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 */
209static inline CC_HINT(always_inline) fr_atomic_queue_slot_t *atomic_queue_slot(fr_atomic_queue_t *aq, int64_t pos)
210{
211 size_t idx = (size_t)pos & (aq->size - 1);
212
213 return &aq->line[idx & aq->line_mask].stripe[idx >> aq->line_shift];
214}
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 */
224static void atomic_queue_init(fr_atomic_queue_t *aq, size_t size)
225{
226 size_t i, num_lines;
227
228 num_lines = atomic_queue_num_lines(size);
229
230 aq->size = size;
231 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 if (num_lines > 1) {
238 aq->line_shift = fr_high_bit_pos(num_lines) - 1;
239 } else {
240 aq->line_shift = 0;
241 }
242
243 /*
244 * Initialize the slots. Data is NULL, and the sequence
245 * number is the position of the slot.
246 */
247 for (i = 0; i < size; i++) {
248 fr_atomic_queue_slot_t *slot = atomic_queue_slot(aq, (int64_t)i);
249
250 slot->data = NULL;
251 store(slot->seq, (int64_t)i);
252 }
253
254 store(aq->head, 0);
255 store(aq->tail, 0);
257}
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 */
270fr_atomic_queue_t *fr_atomic_queue_talloc(TALLOC_CTX *ctx, size_t size)
271{
273 TALLOC_CTX *chunk;
274
275 if (size == 0) return NULL;
276
277 /*
278 * Roundup to the next power of 2 so we don't need modulo.
279 */
280 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 chunk = talloc_aligned_array(ctx, (void **)&aq, CACHE_LINE_SIZE, atomic_queue_bytes(size));
290 if (!chunk) return NULL;
291 aq->chunk = chunk;
292
293 talloc_set_name_const(chunk, "fr_atomic_queue_t");
294
295 atomic_queue_init(aq, size);
296
297 return aq;
298}
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 */
314{
316
317 if (size == 0) return NULL;
318
319 size = (size_t)fr_roundup_pow2_uint64((uint64_t)size);
320
321 if (posix_memalign((void **)&aq, CACHE_LINE_SIZE, atomic_queue_bytes(size)) != 0) return NULL;
322
323 aq->chunk = NULL;
324 atomic_queue_init(aq, size);
325
326 return aq;
327}
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 */
335typedef struct {
336 fr_atomic_queue_t *aq; //!< The raw allocation to release.
338
340{
341 free(owner->aq);
342
343 return 0;
344}
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 */
360fr_atomic_queue_t *fr_atomic_queue_malloc(TALLOC_CTX *ctx, size_t size)
361{
364
366 if (!aq) return NULL;
367
368 owner = talloc(ctx, fr_atomic_queue_owner_t);
369 if (!owner) {
370 free(aq);
371 return NULL;
372 }
373 owner->aq = aq;
374 talloc_set_destructor(owner, _atomic_queue_owner_free);
375 aq->chunk = owner;
376
377 return aq;
378}
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 */
389{
390 if (!*aq) return;
391
392 if ((*aq)->chunk) {
393 talloc_free((*aq)->chunk);
394 } else {
395 free(*aq);
396 }
397 *aq = NULL;
398}
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 */
409{
410 int64_t head;
412
413 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 head = load(aq->head);
438
439 /*
440 * Try to find the current head.
441 */
442 for (;;) {
443 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 slot = atomic_queue_slot(aq, head);
451 seq = acquire(slot->seq);
452 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 if (diff < 0) {
462#if 0
463 fr_atomic_queue_debug(stderr, aq);
464#endif
465 return false;
466 }
467
468 /*
469 * Someone else has already written to this slot
470 * we lost the race, try again.
471 */
472 if (diff > 0) {
473 head = load(aq->head);
474 continue;
475 }
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 if (cas_incr(aq->head, head)) {
491 break;
492 }
493 }
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 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 store(slot->seq, head + 1);
519 return true;
520}
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 */
532{
533 int64_t tail, seq;
535
536 if (!p_data) return false;
537
538 tail = load(aq->tail);
539
540 for (;;) {
541 int64_t diff;
542
543 slot = atomic_queue_slot(aq, tail);
544 seq = acquire(slot->seq);
545
546 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 if (diff < 0) {
555 return false;
556 }
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 if (diff > 0) {
564 tail = load(aq->tail);
565 continue;
566 }
567
568 /*
569 * Same deal as push.
570 * After this point we own the slot.
571 */
572 if (cas_incr(aq->tail, tail)) {
573 break;
574 }
575 }
576
577 /*
578 * Copy the pointer to the caller BEFORE updating the
579 * queue slot.
580 */
581 *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 store(slot->seq, tail + aq->size);
589
590 return true;
591}
592
594{
595 return aq->size;
596}
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
611
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
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 */
633{
635
636 s = malloc(sizeof(*s));
637 if (!s) return NULL;
638
639 s->q = atomic_queue_malloc_raw(seg_size);
640 if (!s->q) {
641 free(s);
642 return NULL;
643 }
644 atomic_init(&s->next, NULL);
645
646 return s;
647}
648
654
655/** talloc destructor for #fr_atomic_ring_t: walk the chain and free segments */
657{
658 fr_atomic_ring_segment_t *s = ring->tail;
659
660 while (s) {
662
664 s = next;
665 }
666
667 return 0;
668}
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 */
680fr_atomic_ring_t *fr_atomic_ring_alloc(TALLOC_CTX *ctx, size_t seg_size)
681{
682 fr_atomic_ring_t *ring;
684
685 if (seg_size == 0) return NULL;
686
687 ring = talloc(ctx, fr_atomic_ring_t);
688 if (!ring) return NULL;
689
690 seg = atomic_ring_segment_alloc(seg_size);
691 if (!seg) {
692 talloc_free(ring);
693 return NULL;
694 }
695
696 ring->seg_size = seg_size;
697 ring->tail = seg;
698 atomic_init(&ring->head, seg);
699 talloc_set_destructor(ring, _atomic_ring_free);
700
701 return ring;
702}
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 */
710{
711 if (!*ring_p) return;
712
713 talloc_free(*ring_p);
714 *ring_p = NULL;
715}
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 */
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 */
749{
752
754
755 if (likely(fr_atomic_queue_push(h->q, data))) return true;
756
757 n = atomic_ring_segment_alloc(ring->seg_size);
758 if (unlikely(!n)) return false;
759
761
762 return fr_atomic_queue_push(n->q, data);
763}
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 */
775bool fr_atomic_ring_pop(fr_atomic_ring_t *ring, void **p_data)
776{
780
781 for (;;) {
782 cur = ring->tail;
783
784 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 */
792 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 if (fr_atomic_queue_pop(cur->q, p_data)) return true;
802
803 old = cur;
804 ring->tail = n;
806 /* loop to pop from the new tail */
807 }
808}
809
810#ifdef WITH_VERIFY_PTR
811/** Check the talloc chunk is still valid
812 *
813 */
814void fr_atomic_queue_verify(fr_atomic_queue_t *aq)
815{
816 (void)talloc_get_type_abort(aq->chunk, fr_atomic_queue_t);
817}
818#endif
819
820#ifndef NDEBUG
821
822#if 0
823typedef 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
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 */
842{
843 size_t i;
844 int64_t head, tail;
845
846 head = load(aq->head);
847 tail = load(aq->tail);
848
849 fprintf(fp, "AQ %p size %zu, head %" PRId64 ", tail %" PRId64 "\n",
850 aq, aq->size, head, tail);
851
852 for (i = 0; i < aq->size; i++) {
854
855 slot = atomic_queue_slot(aq, (int64_t)i);
856
857 fprintf(fp, "\t[%zu] = { %p, %" PRId64 " }",
858 i, slot->data, load(slot->seq));
859#if 0
860 if (slot->data) {
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 fprintf(fp, "\n");
870 }
871}
872#endif
int n
Definition acutest.h:642
static fr_atomic_queue_slot_t * atomic_queue_slot(fr_atomic_queue_t *aq, int64_t pos)
Map a queue position to its slot.
static size_t atomic_queue_bytes(size_t size)
Bytes needed for a queue of size slots, header included.
fr_atomic_queue_slot_t stripe[STRIPES_PER_LINE]
atomic_int64_t head
Position of the producer.
fr_atomic_queue_t * fr_atomic_queue_malloc(TALLOC_CTX *ctx, size_t size)
Create fixed-size atomic queue outside the talloc hierarchy, owned by a talloc context.
uint8_t line_shift
High bits of a slot index give its stripe within that line: .stripe[slot >> line_shift].
bool fr_atomic_queue_pop(fr_atomic_queue_t *aq, void **p_data)
Pop a pointer from the atomic queue.
#define atomic_int64_t
void fr_atomic_queue_debug(FILE *fp, fr_atomic_queue_t *aq)
Dump an atomic queue.
void * chunk
The start of the talloc chunk to pass to free, or NULL if this queue was allocated raw via fr_atomic_...
bool fr_atomic_ring_push(fr_atomic_ring_t *ring, void *data)
Push a pointer into the ring; allocate a new segment on overflow.
atomic_int64_t tail
Position of the consumer.
#define cas_incr(_store, _var)
void fr_atomic_ring_free(fr_atomic_ring_t **ring_p)
Free the ring and all remaining segments.
static void atomic_ring_segment_free(fr_atomic_ring_segment_t *s)
fr_atomic_queue_line_t line[]
The line array, aligned with cache lines to ensure producer and consumers don't conflict.
static int _atomic_ring_free(fr_atomic_ring_t *ring)
talloc destructor for fr_atomic_ring_t: walk the chain and free segments
static int _atomic_queue_owner_free(fr_atomic_queue_owner_t *owner)
fr_atomic_ring_t * fr_atomic_ring_alloc(TALLOC_CTX *ctx, size_t seg_size)
Allocate an empty SPSC ring.
static fr_atomic_ring_segment_t * atomic_ring_segment_alloc(size_t seg_size)
Allocate a fresh segment and its embedded queue.
bool fr_atomic_ring_pop(fr_atomic_ring_t *ring, void **p_data)
Pop a pointer from the ring, advancing past drained segments.
size_t line_mask
Low bits of a slot index give its line: line[slot & line_mask].
atomic_int64_t seq
Must be seq then data to ensure seq is 64bit aligned for 32bit address spaces.
static size_t atomic_queue_num_lines(size_t size)
Number of cache lines needed to hold size slots.
size_t size
The length of the queue.
#define STRIPES_PER_LINE
How many stripes a line contains.
size_t fr_atomic_queue_size(fr_atomic_queue_t *aq)
#define load(_var)
bool fr_atomic_queue_push(fr_atomic_queue_t *aq, void *data)
Push a pointer into the atomic queue.
static void atomic_ring_segment_publish(fr_atomic_ring_t *ring, fr_atomic_ring_segment_t *h, fr_atomic_ring_segment_t *n)
Link a new segment after the current head and make it the head.
void fr_atomic_queue_free(fr_atomic_queue_t **aq)
Free an atomic queue if it's not freed by ctx.
#define store(_store, _var)
fr_atomic_queue_t * aq
The raw allocation to release.
fr_atomic_queue_t * q
Per-segment MPMC ring (used SPSC here).
static void atomic_queue_init(fr_atomic_queue_t *aq, size_t size)
Initialise the sequence numbers and head/tail on a fresh queue buffer.
#define acquire(_var)
fr_atomic_queue_t * fr_atomic_queue_talloc(TALLOC_CTX *ctx, size_t size)
Create fixed-size atomic queue.
static fr_atomic_queue_t * atomic_queue_malloc_raw(size_t size)
Create fixed-size atomic queue outside any talloc hierarchy.
size_t seg_size
Capacity of each segment.
#define CACHE_LINE_SIZE
One cache line of stripes.
Owner handle for a raw queue allocation.
Structure to hold the atomic queue.
One slot in the queue, holding one queued pointer.
struct fr_atomic_queue_s fr_atomic_queue_t
#define RCSID(id)
Definition build.h:603
#define DIAG_ON(_x)
Definition build.h:576
#define unlikely(_x)
Definition build.h:486
#define DIAG_OFF(_x)
Definition build.h:575
static fr_atomic_queue_t ** aq
free(array)
talloc_free(hp)
fr_control_message_status_t status
status of this message
Definition control.c:60
size_t data_size
size of the data we're sending
Definition control.c:62
The header for the control message.
Definition control.c:59
static uint64_t fr_roundup_pow2_uint64(uint64_t v)
Definition math.h:32
static uint8_t fr_high_bit_pos(uint64_t num)
Find the highest order high bit in an unsigned 64 bit integer.
Definition math.h:94
unsigned char uint8_t
unsigned long int size_t
@ memory_order_seq_cst
Definition stdatomic.h:132
@ memory_order_release
Definition stdatomic.h:130
@ memory_order_relaxed
Definition stdatomic.h:127
@ memory_order_acquire
Definition stdatomic.h:129
#define _Atomic(T)
Definition stdatomic.h:77
#define atomic_load_explicit(object, order)
Definition stdatomic.h:312
#define atomic_thread_fence(order)
Definition stdatomic.h:148
#define atomic_store_explicit(object, desired, order)
Definition stdatomic.h:314
#define atomic_init(obj, value)
Definition stdatomic.h:89
TALLOC_CTX * talloc_aligned_array(TALLOC_CTX *ctx, void **start, size_t alignment, size_t size)
Return a page aligned talloc memory array.
Definition talloc.c:197
static fr_slen_t head
Definition xlat.h:410
static fr_slen_t data
Definition value.h:1366