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