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: 274dcdfe7595fbb61bd6e651dd7e2a1212048fe1 $
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: 274dcdfe7595fbb61bd6e651dd7e2a1212048fe1 $")
33
34#include <stdint.h>
35#include <stdalign.h>
36#include <inttypes.h>
37#include <stdlib.h>
38#include <errno.h>
39#include <unistd.h>
40#include <sys/mman.h>
41
42#include <freeradius-devel/autoconf.h>
43#include <freeradius-devel/io/atomic_queue.h>
44#include <freeradius-devel/util/debug.h>
45#include <freeradius-devel/util/math.h>
46#include <freeradius-devel/util/strerror.h>
47#include <freeradius-devel/util/syserror.h>
48
49/*
50 * Some macros to make our life easier. Not every macro is used.
51 */
52DIAG_OFF(unused-macros)
53#define atomic_int64_t _Atomic(int64_t)
54#define atomic_uint32_t _Atomic(uint32_t)
55#define atomic_uint64_t _Atomic(uint64_t)
56
57#define cas_incr(_store, _var) atomic_compare_exchange_strong_explicit(&_store, &_var, _var + 1, memory_order_release, memory_order_relaxed)
58#define cas_decr(_store, _var) atomic_compare_exchange_strong_explicit(&_store, &_var, _var - 1, memory_order_release, memory_order_relaxed)
59#define load(_var) atomic_load_explicit(&_var, memory_order_relaxed)
60#define acquire(_var) atomic_load_explicit(&_var, memory_order_acquire)
61#define store(_store, _var) atomic_store_explicit(&_store, _var, memory_order_release)
62DIAG_ON(unused-macros)
63
64/*
65 * The distance two addresses need to be apart so that one core
66 * writing does not slow another core reading. On Apple silicon
67 * the hardware line is 128 bytes. On x86 the hardware line is
68 * 64 bytes but the adjacent line prefetcher fetches lines in
69 * 128 byte pairs, so neighbouring 64 byte lines still appear to
70 * contend in benchmarks.
71 */
72#define CACHE_LINE_SIZE 128
73
74/** One slot in the queue
75 *
76 * The sequence number, then the element: `elem_len` of data, padded
77 * out to the slot size. For the pointer API the element is the pointer
78 * itself.
79 *
80 * Must not be packed. Packing drops the struct's alignment to 1
81 * (same as an array of uint8_t), so the compiler has to treat `seq`
82 * as possibly unaligned. The native atomic instructions require an
83 * aligned address, so clang stops emitting them and calls the
84 * libatomic fallback instead, which takes a mutex. That turns every
85 * push and pop on this lock free queue into a locked operation. gcc
86 * emits the native instruction regardless, and relies on the address
87 * being aligned at runtime.
88 */
89typedef struct {
90 atomic_int64_t seq; //!< The position this slot was last
91 ///< written or freed at.
92 uint8_t data[]; //!< The element, 8 byte aligned as
93 ///< slots are a power of two bytes from
94 ///< a line boundary.
96
97static_assert(offsetof(fr_atomic_queue_slot_t, data) == 8, "the element must follow the sequence number");
98
99/*
100 * Slots are striped across cache lines.
101 *
102 * A line is exactly one cache line. A stripe is the set of slots at
103 * the same offset in every line, so it runs the complete length of the
104 * queue memory. Stripes let a line hold several slots without the
105 * slots sharing a cache line with their neighbours in queue order.
106 * Padding each slot out to a full cache line instead would waste the
107 * majority of queue memory for no gain.
108 *
109 * Slot `i` translates to `line[i % num_lines].stripe[i / num_lines]`, i.e.
110 * for stripe 0, slot positions match line positions (slot 0 at line 0),
111 * and once the slot number exceeds the number of lines we wrap, and use
112 * the next stripe.
113 *
114 @verbatim
115 stripe[0] stripe[1] stripe[2] ... stripe[7]
116 +----------+----------+----------+ +----------+
117 line[0] | slot 0 | slot 4 | slot 8 | ... | slot 28 | <- one cache line
118 +----------+----------+----------+ +----------+
119 line[1] | slot 1 | slot 5 | slot 9 | ... | slot 29 |
120 +----------+----------+----------+ +----------+
121 line[2] | slot 2 | slot 6 | slot 10 | ... | slot 30 |
122 +----------+----------+----------+ +----------+
123 line[3] | slot 3 | slot 7 | slot 11 | ... | slot 31 |
124 +----------+----------+----------+ +----------+
125 @endverbatim
126 *
127 * How many stripes a line holds depends on the element size, so it is
128 * decided when the queue is created and kept in the queue.
129 */
130
131/** Structure to hold the atomic queue
132 *
133 * @note DO NOT redorder these fields without understanding how alignas works
134 * and maintaining separation. The head and tail must be in different cache lines
135 * to reduce contention between producers and consumers. Cold data (num, chunk)
136 * can share a cache line, but must be separated from head, tail and the line array.
137 */
139 alignas(CACHE_LINE_SIZE) atomic_int64_t head; //!< Position of the producer.
140 ///< Cache aligned bytes to ensure it's in a
141 ///< different cache line to tail to reduce
142 ///< memory contention.
143
144 alignas(CACHE_LINE_SIZE) atomic_int64_t tail; //!< Position of the consumer.
145 ///< Cache aligned bytes to ensure it's in a
146 ///< different cache line to tail to reduce
147 ///< memory contention.
148 ///< Reads may still need to occur from num
149 ///< whilst the producer is writing to tail.
150
151 alignas(CACHE_LINE_SIZE) size_t num; //!< Elements the queue holds. This is static.
152 ///< Also needs to be cache aligned, otherwise
153 ///< it can end up directly after tail in memory
154 ///< and share a cache line.
155
156
157 size_t elem_len; //!< Size of one element.
158 size_t slot_len; //!< The line divided by the stripes:
159 ///< header, element, padding.
160 size_t stripes; //!< Slots per line. The largest power
161 ///< of two the element allows.
162 size_t line_mask; //!< Low bits of a slot index give its line:
163 ///< `line[slot & line_mask]`. Set at init to
164 ///< num_lines - 1.
165
166 uint8_t line_shift; //!< High bits of a slot index give its stripe
167 ///< within that line:
168 ///< `.stripe[slot >> line_shift]`. Equal to
169 ///< log2(num_lines). Set at init,
170 ///< so a lookup is a mask and a shift, no
171 ///< division and no modulo!
172
173 void *chunk; //!< The start of the talloc chunk to pass to free,
174 ///< or NULL if this queue was allocated raw via
175 ///< #fr_atomic_queue_malloc. We need to play
176 ///< tricks to get aligned memory with talloc.
177
178 alignas(CACHE_LINE_SIZE) uint8_t lines[]; //!< The lines, each exactly one cache line,
179 ///< aligned with cache lines to ensure
180 ///< producers and consumers don't conflict.
181};
182
183/** How many slots a line holds for an element size
184 *
185 * The largest power of two such that the line divided by it still
186 * holds the slot header and the element. A power of two keeps the
187 * slot size a power of two and the line count a power of two, which
188 * the mask and shift lookup depends on.
189 *
190 * @param[in] elem_len Size of the element.
191 * @return 0 if the element does not fit in a line with its header.
192 */
193static inline CC_HINT(always_inline) size_t atomic_queue_stripes(size_t elem_len)
194{
195 size_t need = offsetof(fr_atomic_queue_slot_t, data) + elem_len;
196 size_t stripes;
197
198 if (need > CACHE_LINE_SIZE) return 0;
199
200 stripes = CACHE_LINE_SIZE / need;
201 while (stripes & (stripes - 1)) stripes &= stripes - 1;
202
203 return stripes;
204}
205
206/** Number of cache lines needed to hold `num` slots
207 *
208 * A queue smaller than one line uses a single, partly filled line.
209 *
210 * @param[in] num Slot count, already rounded up to a power of 2.
211 */
212static inline CC_HINT(always_inline) size_t atomic_queue_num_lines(size_t num, size_t stripes)
213{
214 if (num < stripes) return 1;
215
216 return num / stripes;
217}
218
219/** Bytes needed for a queue of `num` slots, header included
220 *
221 * @param[in] num Slot count, already rounded up to a power of 2.
222 */
223static inline CC_HINT(always_inline) size_t atomic_queue_bytes(size_t num, size_t stripes)
224{
225 return sizeof(fr_atomic_queue_t) + (atomic_queue_num_lines(num, stripes) * CACHE_LINE_SIZE);
226}
227
228/** Map a queue position to its slot
229 *
230 * Positions wrap at `num`. Within one stripe, consecutive positions increment
231 * one line at a time, and move to the next stripe once every line has been
232 * visited.
233 *
234 * @param[in] aq The queue.
235 * @param[in] pos The head or tail position, unmasked.
236 */
237static inline CC_HINT(always_inline) fr_atomic_queue_slot_t *atomic_queue_slot(fr_atomic_queue_t *aq, int64_t pos)
238{
239 size_t idx = (size_t)pos & (aq->num - 1);
240
241 return (fr_atomic_queue_slot_t *)(aq->lines + ((idx & aq->line_mask) * CACHE_LINE_SIZE) +
242 ((idx >> aq->line_shift) * aq->slot_len));
243}
244
245/** Initialise the sequence numbers and head/tail on a fresh queue buffer
246 *
247 * Shared between the talloc and raw allocators. The buffer must already
248 * be cache-line aligned and sized to hold `num` slots.
249 *
250 * @param[in] aq The queue buffer to initialise.
251 * @param[in] num Slot count, already rounded up to a power of 2.
252 * @param[in] elem_len Size of one element, known to fit.
253 */
254static void atomic_queue_init(fr_atomic_queue_t *aq, size_t num, size_t elem_len)
255{
256 size_t i, num_lines;
257
258 aq->elem_len = elem_len;
259 aq->stripes = atomic_queue_stripes(elem_len);
260 fr_assert(aq->stripes > 0);
261 aq->slot_len = CACHE_LINE_SIZE / aq->stripes;
262 num_lines = atomic_queue_num_lines(num, aq->stripes);
263 memset(aq->lines, 0, num_lines * CACHE_LINE_SIZE);
264
265 aq->num = num;
266 aq->line_mask = num_lines - 1;
267 /*
268 * num_lines is a power of two, so the shift is its log2.
269 * The check here is mostly to quiet clang scan, which
270 * complained about the potential underflow.
271 */
272 if (num_lines > 1) {
273 aq->line_shift = fr_high_bit_pos(num_lines) - 1;
274 } else {
275 aq->line_shift = 0;
276 }
277
278 /*
279 * Initialize the slots. Data is NULL, and the sequence
280 * number is the position of the slot.
281 */
282 for (i = 0; i < num; i++) {
283 fr_atomic_queue_slot_t *slot = atomic_queue_slot(aq, (int64_t)i);
284
285 store(slot->seq, (int64_t)i);
286 }
287
288 store(aq->head, 0);
289 store(aq->tail, 0);
291}
292
293/** Create a fixed-size atomic queue
294 *
295 * Push and pop copy `elem_len` bytes in and out, so a queue of pointers
296 * is a queue of `sizeof(uintptr_t)` elements whose callers pass the
297 * address of the pointer. Nothing is allocated per element, and
298 * nothing has to be freed by the consumer.
299 *
300 * @note the queue must be freed explicitly by the ctx being freed, or by using
301 * the #fr_atomic_queue_free function.
302 *
303 * @param[in] ctx The talloc ctx to allocate the queue in.
304 * @param[in] num The number of entries in the queue.
305 * @param[in] elem_len The size of one element. Elements are 8 byte aligned.
306 * @return
307 * - NULL on error, including an element which does not fit in a cache line.
308 * - fr_atomic_queue_t *, a pointer to the allocated and initialized queue.
309 */
310fr_atomic_queue_t *fr_atomic_queue_talloc(TALLOC_CTX *ctx, size_t num, size_t elem_len)
311{
313 TALLOC_CTX *chunk;
314 size_t stripes;
315
316 if ((num == 0) || (elem_len == 0)) return NULL;
317 stripes = atomic_queue_stripes(elem_len);
318 if (stripes == 0) return NULL;
319
320 /*
321 * Roundup to the next power of 2 so we don't need modulo.
322 */
323 num = (size_t)fr_roundup_pow2_uint64((uint64_t)num);
324
325 /*
326 * Allocate a contiguous blob for the header and queue.
327 * This helps with memory locality.
328 *
329 * Since we're allocating a blob, we should also set the
330 * name of the data, too.
331 */
332 chunk = talloc_aligned_array(ctx, (void **)&aq, CACHE_LINE_SIZE, atomic_queue_bytes(num, stripes));
333 if (!chunk) return NULL;
334 aq->chunk = chunk;
335
336 talloc_set_name_const(chunk, "fr_atomic_queue_t");
337
338 atomic_queue_init(aq, num, elem_len);
339
340 return aq;
341}
342
343/** Create fixed-size atomic queue outside any talloc hierarchy
344 *
345 * Backed by `posix_memalign`. The queue has no owner: `chunk` is NULL
346 * and the caller releases it with #fr_atomic_queue_free or plain `free()`.
347 *
348 * For callers that allocate from threads where talloc is not safe (for
349 * example, library-owned callback threads).
350 *
351 * @param[in] num The number of entries in the queue.
352 * @param[in] elem_len The size of one element.
353 * @return
354 * - NULL on error.
355 * - fr_atomic_queue_t *, a pointer to the allocated and initialized queue.
356 */
357static fr_atomic_queue_t *atomic_queue_malloc_raw(size_t num, size_t elem_len)
358{
360 size_t stripes;
361
362 if ((num == 0) || (elem_len == 0)) return NULL;
363 stripes = atomic_queue_stripes(elem_len);
364 if (stripes == 0) return NULL;
365
366 num = (size_t)fr_roundup_pow2_uint64((uint64_t)num);
367
368 if (posix_memalign((void **)&aq, CACHE_LINE_SIZE, atomic_queue_bytes(num, stripes)) != 0) return NULL;
369
370 aq->chunk = NULL;
371 atomic_queue_init(aq, num, elem_len);
372
373 return aq;
374}
375
376/** Owner handle for a queue outside the talloc hierarchy
377 *
378 * Raw and mapped queues keep their memory outside talloc. This handle
379 * is the only talloc'd part, so freeing the context the handle lives in
380 * releases the queue.
381 */
382typedef struct {
383 fr_atomic_queue_t *aq; //!< The memory to release.
384 size_t len; //!< Length of a mapping.
385 int fd; //!< The shared memory object behind a mapping,
386 ///< or -1 for a raw allocation.
388
390{
391 if (owner->fd < 0) {
392 free(owner->aq);
393 } else {
394 (void) munmap(owner->aq, owner->len);
395 (void) close(owner->fd);
396 }
397
398 return 0;
399}
400
401/** Create fixed-size atomic queue outside the talloc hierarchy, owned by a talloc context
402 *
403 * The queue itself is a raw `posix_memalign` allocation, so it can be
404 * pushed to and popped from by threads where talloc is not safe, and
405 * can later be placed in memory talloc does not manage. A small owner
406 * handle allocated in `ctx` frees the queue when `ctx` is freed, or
407 * when the caller calls #fr_atomic_queue_free.
408 *
409 * @param[in] ctx The talloc ctx which owns the queue.
410 * @param[in] num The number of entries in the queue.
411 * @param[in] elem_len The size of one element.
412 * @return
413 * - NULL on error.
414 * - fr_atomic_queue_t *, a pointer to the allocated and initialized queue.
415 */
416fr_atomic_queue_t *fr_atomic_queue_malloc(TALLOC_CTX *ctx, size_t num, size_t elem_len)
417{
420
421 aq = atomic_queue_malloc_raw(num, elem_len);
422 if (!aq) return NULL;
423
424 owner = talloc(ctx, fr_atomic_queue_owner_t);
425 if (!owner) {
426 free(aq);
427 return NULL;
428 }
429 owner->aq = aq;
430 owner->len = 0;
431 owner->fd = -1;
432 talloc_set_destructor(owner, _atomic_queue_owner_free);
433 aq->chunk = owner;
434
435 return aq;
436}
437
438/** Open an anonymous shared memory object of a given length
439 *
440 * @param[in] len bytes the object must hold.
441 * @return
442 * - The descriptor, close on exec.
443 * - -1 on error, with the error in fr_strerror.
444 */
445static int atomic_queue_shm_open(size_t len)
446{
447 int fd;
448
449 fd = memfd_create("fr_aq", MFD_CLOEXEC);
450 if (fd < 0) {
451 fr_strerror_printf("Failed creating shared memory object: %s", fr_syserror(errno));
452 return -1;
453 }
454
455 if (ftruncate(fd, (off_t) len) < 0) {
456 fr_strerror_printf("Failed sizing shared memory object: %s", fr_syserror(errno));
457 close(fd);
458 return -1;
459 }
460
461 return fd;
462}
463
464/** Create or attach to a fixed-size atomic queue in shared memory, owned by a talloc context
465 *
466 * With *fd negative, a new anonymous shared memory object is created,
467 * the queue initialised in it, and its descriptor written back for the
468 * caller to pass to another process. With a descriptor, that object is
469 * mapped and must already hold a queue of this size; the mapping is not
470 * reinitialised. Either way the handle owns the descriptor and closes
471 * it with the mapping. Pages are aligned well beyond a cache line.
472 *
473 * @param[in] ctx The talloc ctx which owns the queue.
474 * @param[in,out] fd A shared memory object holding a queue to attach to, or -1 to
475 * create one, in which case the new descriptor is written here.
476 * @param[in] num The number of entries in the queue.
477 * @param[in] elem_len The size of one element.
478 * @return
479 * - NULL on error, with the error in fr_strerror.
480 * - fr_atomic_queue_t *, a pointer to the mapped queue.
481 */
482fr_atomic_queue_t *fr_atomic_queue_mmap(TALLOC_CTX *ctx, int *fd, size_t num, size_t elem_len)
483{
486 size_t stripes, len;
487 bool attach = (*fd >= 0);
488
489 if ((num == 0) || (elem_len == 0)) return NULL;
490 stripes = atomic_queue_stripes(elem_len);
491 if (stripes == 0) return NULL;
492
493 num = (size_t)fr_roundup_pow2_uint64((uint64_t)num);
494 len = atomic_queue_bytes(num, stripes);
495
496 if (!attach) {
498 if (*fd < 0) return NULL;
499 }
500
501 aq = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0);
502 if (aq == MAP_FAILED) {
503 fr_strerror_printf("Failed mapping shared memory object: %s", fr_syserror(errno));
504 if (!attach) {
505 close(*fd);
506 *fd = -1;
507 }
508 return NULL;
509 }
510
511 /*
512 * The creator laid the queue out for its own size and element.
513 * We can only share it if ours agree.
514 */
515 if (attach && ((aq->num != num) || (aq->elem_len != elem_len))) {
516 fr_strerror_printf("Shared queue holds %zu elements of %zu bytes, expected %zu of %zu",
517 aq->num, aq->elem_len, num, elem_len);
518 (void) munmap(aq, len);
519 return NULL;
520 }
521
522 owner = talloc(ctx, fr_atomic_queue_owner_t);
523 if (!owner) {
524 (void) munmap(aq, len);
525 if (!attach) {
526 close(*fd);
527 *fd = -1;
528 }
529 return NULL;
530 }
531 owner->aq = aq;
532 owner->len = len;
533 owner->fd = *fd;
534 talloc_set_destructor(owner, _atomic_queue_owner_free);
535
536 if (!attach) atomic_queue_init(aq, num, elem_len);
537
538 /*
539 * Each process has its own handle, and the chunk pointer is only
540 * meaningful in the process which set it. The last writer wins,
541 * which is fine as nothing reads it across processes.
542 */
543 aq->chunk = owner;
544
545 return aq;
546}
547
548
549/** Free an atomic queue if it's not freed by ctx
550 *
551 * The queue memory must be cache line aligned, so it lives in one of
552 * three places: a talloc aligned array (`chunk` is the array), a raw
553 * allocation owned by a talloc handle (`chunk` is the handle, whose
554 * destructor frees the queue), or a raw allocation with no owner
555 * (`chunk` is NULL).
556 */
558{
559 if (!*aq) return;
560
561 if ((*aq)->chunk) {
562 talloc_free((*aq)->chunk);
563 } else {
564 free(*aq);
565 }
566 *aq = NULL;
567}
568
569/** Copy an element into the atomic queue, any number of producers
570 *
571 * For a queue of pointers, data is the address of the pointer.
572 *
573 * @param[in] aq The atomic queue to add data to.
574 * @param[in] data the element, `elem_len` long.
575 * @return
576 * - true on successful push
577 * - false on queue full
578 */
580{
581 int64_t head;
583
584 /*
585 * Here we're essentially racing with other producers
586 * to find the current head of the queue.
587 *
588 * 1. Load the current head (which may be incremented
589 * by another producer before we enter the loop).
590 * 2. Find the head slot, which is head modulo the
591 * queue size (keeps head looping through the queue).
592 * 3. Read the sequence number of the slot.
593 * The sequence numbers are initialised to the index
594 * of the slots in the queue. Each pass of the
595 * producer increments the sequence number by one.
596 * 4.
597 * a. If the sequence number is equal to the head,
598 * then we can use the slot. Increment the head
599 * so other producers know we've used it.
600 * b. If it's greater than head, the producer has
601 * already written to this slot, so we need to re-load
602 * the head and race other producers again.
603 * c. If it's less than the head, the slot has not yet
604 * been consumed, and the queue is full.
605 */
606 head = load(aq->head);
607
608 /*
609 * Try to find the current head.
610 */
611 for (;;) {
612 int64_t seq, diff;
613
614 /*
615 * Alloc function guarantees size is a power
616 * of 2, so we can use this hack to avoid
617 * modulo.
618 */
619 slot = atomic_queue_slot(aq, head);
620 seq = acquire(slot->seq);
621 diff = (seq - head);
622
623 /*
624 * head is larger than the current slot, the
625 * queue is full.
626 * The consumer will set slot seq to slot +
627 * queue size, marking it as free for the
628 * producer to use.
629 */
630 if (diff < 0) {
631#if 0
632 fr_atomic_queue_debug(stderr, aq);
633#endif
634 return false;
635 }
636
637 /*
638 * Someone else has already written to this slot
639 * we lost the race, try again.
640 */
641 if (diff > 0) {
642 head = load(aq->head);
643 continue;
644 }
645
646 /*
647 * See if we can increment the head value
648 * (and check it's still at its old value).
649 *
650 * This means no two producers can have the same
651 * slot in the queue, because they can't exit
652 * the loop until they've incremented the head
653 * successfully.
654 *
655 * When we fail, we don't increment head before
656 * trying again, because we need to detect queue
657 * full conditions.
658 */
659 if (cas_incr(aq->head, head)) {
660 break;
661 }
662 }
663
664 /*
665 * Store the data in the queue, and increment the slot
666 * with the new index, and make the write visible to
667 * other CPUs.
668 */
669 memcpy(slot->data, data, aq->elem_len);
670
671 /*
672 * Technically head can overflow. Practically, with a
673 * 3GHz CPU, doing nothing but incrementing head
674 * uncontended it'd take about 100 years for this to
675 * happen. But hey, maybe someone invents an optical
676 * CPU with a significantly higher clock speed, it's ok
677 * for us to exit every 9 quintillion packets.
678 */
679#ifdef __clang_analyzer__
680 if (unlikely((head + 1) == INT64_MAX)) exit(1);
681#endif
682
683 /*
684 * Mark up the slot as written to. Any other producer
685 * attempting to write will see (diff > 0) and retry.
686 */
687 store(slot->seq, head + 1);
688 return true;
689}
690
691/** Copy an element out of the atomic queue, any number of consumers
692 *
693 * For a queue of pointers, data is the address of a pointer to fill.
694 *
695 * @param[in] aq the atomic queue to retrieve data from.
696 * @param[out] data where to write the element, `elem_len` long.
697 * @return
698 * - true on successful pop
699 * - false on queue empty
700 */
702{
703 int64_t tail, seq;
705
706 tail = load(aq->tail);
707
708 for (;;) {
709 int64_t diff;
710
711 slot = atomic_queue_slot(aq, tail);
712 seq = acquire(slot->seq);
713
714 diff = (seq - (tail + 1));
715
716 /*
717 * Tail is smaller than the current slot,
718 * the queue is empty.
719 *
720 * Tail should now be equal to the head.
721 */
722 if (diff < 0) {
723 return false;
724 }
725
726 /*
727 * Tail is now ahead of us.
728 * Something else has consumed it.
729 * We lost the race with another consumer.
730 */
731 if (diff > 0) {
732 tail = load(aq->tail);
733 continue;
734 }
735
736 /*
737 * Same deal as push.
738 * After this point we own the slot.
739 */
740 if (cas_incr(aq->tail, tail)) {
741 break;
742 }
743 }
744
745 /*
746 * Copy the element to the caller BEFORE updating the
747 * queue slot.
748 */
749 memcpy(data, slot->data, aq->elem_len);
750
751 /*
752 * Set the current slot to past the end of the queue.
753 * This is equal to what head will be on its next pass
754 * through the queue. This marks the slot as free.
755 */
756 store(slot->seq, tail + aq->num);
757
758 return true;
759}
760
761/** Copy an element into the atomic queue, one producer only
762 *
763 * The same slots and sequence numbers as the mpmc functions, so the
764 * consumer may be either flavour. With one producer nobody else
765 * advances head, so there is nothing to race and no compare-and-swap:
766 * the slot is free when its sequence number equals head.
767 *
768 * @param[in] aq The atomic queue to add data to.
769 * @param[in] data the element, `elem_len` long.
770 * @return
771 * - true on successful push
772 * - false on queue full
773 */
775{
776 int64_t head = load(aq->head);
778
779 if (acquire(slot->seq) != head) return false;
780
781 memcpy(slot->data, data, aq->elem_len);
782 store(slot->seq, head + 1);
783 store(aq->head, head + 1);
784
785 return true;
786}
787
788/** Copy an element out of the atomic queue, one consumer only
789 *
790 * With one consumer nobody else advances tail, so no compare-and-swap:
791 * the slot is full when its sequence number is one past tail.
792 *
793 * @param[in] aq the atomic queue to retrieve data from.
794 * @param[out] data where to write the element, `elem_len` long.
795 * @return
796 * - true on successful pop
797 * - false on queue empty
798 */
800{
801 int64_t tail = load(aq->tail);
803
804 if (acquire(slot->seq) != (tail + 1)) return false;
805
806 memcpy(data, slot->data, aq->elem_len);
807 store(slot->seq, tail + aq->num);
808 store(aq->tail, tail + 1);
809
810 return true;
811}
812
814{
815 return aq->num;
816}
817
818/*
819 * Segmented single-producer / single-consumer ring.
820 *
821 * Safety argument: one producer owns `head` and writes `seg->next`
822 * exactly once (release). One consumer owns `tail` and reads
823 * `tail->next` with acquire; by the release/acquire pair plus the
824 * invariant "no push into s after s->next is set" the consumer
825 * cannot advance past a segment that still has an in-flight push
826 * as long as it retries `pop(tail->q)` once more after observing
827 * `tail->next != NULL`.
828 */
829
831
833 fr_atomic_queue_t *q; //!< Per-segment queue, driven with the spsc functions.
834 _Atomic(fr_atomic_ring_segment_t *) next; //!< NULL until the producer seals this segment
835 ///< because it filled up and moved on to a
836 ///< fresh one.
837};
838
840 size_t seg_size; //!< Capacity of each segment.
841 _Atomic(fr_atomic_ring_segment_t *) head; //!< Producer end. Writer is the single producer,
842 ///< reader is also the producer - the consumer
843 ///< never loads this.
844 fr_atomic_ring_segment_t *tail; //!< Consumer end. Touched only by the consumer.
845};
846
847/** Allocate a fresh segment and its embedded queue
848 *
849 * Uses the raw (non-talloc) allocator so this function is safe to call
850 * from the producer thread even when that thread cannot safely use talloc.
851 */
853{
855
856 s = malloc(sizeof(*s));
857 if (!s) return NULL;
858
859 s->q = atomic_queue_malloc_raw(seg_size, sizeof(uintptr_t));
860 if (!s->q) {
861 free(s);
862 return NULL;
863 }
864 atomic_init(&s->next, NULL);
865
866 return s;
867}
868
874
875/** talloc destructor for #fr_atomic_ring_t: walk the chain and free segments */
877{
878 fr_atomic_ring_segment_t *s = ring->tail;
879
880 while (s) {
882
884 s = next;
885 }
886
887 return 0;
888}
889
890/** Allocate an empty SPSC ring
891 *
892 * @param[in] ctx talloc ctx that owns the ring handle (segments live
893 * outside talloc; they are freed by the ring's
894 * destructor).
895 * @param[in] seg_size Per-segment capacity. Rounded up to a power of 2.
896 * @return
897 * - NULL on error.
898 * - A ring containing one initial (empty) segment.
899 */
900fr_atomic_ring_t *fr_atomic_ring_alloc(TALLOC_CTX *ctx, size_t seg_size)
901{
902 fr_atomic_ring_t *ring;
904
905 if (seg_size == 0) return NULL;
906
907 ring = talloc(ctx, fr_atomic_ring_t);
908 if (!ring) return NULL;
909
910 seg = atomic_ring_segment_alloc(seg_size);
911 if (!seg) {
912 talloc_free(ring);
913 return NULL;
914 }
915
916 ring->seg_size = seg_size;
917 ring->tail = seg;
918 atomic_init(&ring->head, seg);
919 talloc_set_destructor(ring, _atomic_ring_free);
920
921 return ring;
922}
923
924/** Free the ring and all remaining segments
925 *
926 * Equivalent to `talloc_free()` on the ring, but nulls the caller's
927 * handle in the style of #fr_atomic_queue_free.
928 */
930{
931 if (!*ring_p) return;
932
933 talloc_free(*ring_p);
934 *ring_p = NULL;
935}
936
937/** Link a new segment after the current head and make it the head
938 *
939 * The consumer only inspects `h->next` and advances past `h` once it
940 * sees a non-NULL value there, so `h->next` is published with release
941 * ordering, which pairs with the acquire in fr_atomic_ring_pop().
942 *
943 * Modelled for Coverity in src/coverity-model/merged_model.c, as the
944 * analyser does not treat an atomic store as publishing the pointer.
945 *
946 * @param[in] ring the segment belongs to.
947 * @param[in] h Current head segment.
948 * @param[in] n New segment to publish.
949 */
956
957/** Push a pointer into the ring; allocate a new segment on overflow
958 *
959 * Single-producer only. Must not be called concurrently with itself.
960 *
961 * @param[in] ring Ring to push into.
962 * @param[in] data Value to push (must be non-NULL).
963 * @return
964 * - true on success.
965 * - false if both the current segment is full and a new segment
966 * could not be allocated.
967 */
969{
972
974
975 if (likely(fr_atomic_queue_spsc_push(h->q, &data))) return true;
976
977 n = atomic_ring_segment_alloc(ring->seg_size);
978 if (unlikely(!n)) return false;
979
981
982 return fr_atomic_queue_spsc_push(n->q, &data);
983}
984
985/** Pop a pointer from the ring, advancing past drained segments
986 *
987 * Single-consumer only. Must not be called concurrently with itself.
988 *
989 * @param[in] ring Ring to pop from.
990 * @param[out] p_data Where to write the popped value on success.
991 * @return
992 * - true if a value was popped.
993 * - false if the ring is currently empty.
994 */
995bool fr_atomic_ring_pop(fr_atomic_ring_t *ring, void **p_data)
996{
1000
1001 for (;;) {
1002 cur = ring->tail;
1003
1004 if (likely(fr_atomic_queue_spsc_pop(cur->q, p_data))) return true;
1005
1006 /*
1007 * Empty from our point of view. If the producer hasn't
1008 * sealed this segment there might be pushes in our
1009 * future - return and let the caller come back.
1010 */
1012 if (!n) return false;
1013
1014 /*
1015 * Sealed. One more pop to drain anything the producer
1016 * committed before sealing but after our first (empty)
1017 * pop observation. Without this re-check, late commits
1018 * in the (empty-observation, seal-observation) window
1019 * would be stranded when we advance past `cur`.
1020 */
1021 if (fr_atomic_queue_spsc_pop(cur->q, p_data)) return true;
1022
1023 old = cur;
1024 ring->tail = n;
1026 /* loop to pop from the new tail */
1027 }
1028}
1029
1030#ifdef WITH_VERIFY_PTR
1031/** Check the talloc chunk is still valid
1032 *
1033 */
1034void fr_atomic_queue_verify(fr_atomic_queue_t *aq)
1035{
1036 (void)talloc_get_type_abort(aq->chunk, fr_atomic_queue_t);
1037}
1038#endif
1039
1040#ifndef NDEBUG
1041
1042#if 0
1043typedef struct {
1044 int status; //!< status of this message
1045 size_t data_size; //!< size of the data we're sending
1046
1047 int signal; //!< the signal to send
1048 uint64_t ack; //!< or the endpoint..
1049 void *ch; //!< the channel
1050} fr_control_message_t;
1051#endif
1052
1053
1054/** Dump an atomic queue.
1055 *
1056 * Absolutely NOT thread-safe.
1057 *
1058 * @param[in] aq The atomic queue to debug.
1059 * @param[in] fp where the debugging information will be printed.
1060 */
1062{
1063 size_t i;
1064 int64_t head, tail;
1065
1066 head = load(aq->head);
1067 tail = load(aq->tail);
1068
1069 fprintf(fp, "AQ %p size %zu, head %" PRId64 ", tail %" PRId64 "\n",
1070 aq, aq->num, head, tail);
1071
1072 for (i = 0; i < aq->num; i++) {
1074
1075 slot = atomic_queue_slot(aq, (int64_t)i);
1076
1077 fprintf(fp, "\t[%zu] = { %p, %" PRId64 " }",
1078 i, (void *) slot->data, load(slot->seq));
1079#if 0
1080 if (slot->data) {
1081 fr_control_message_t *c;
1082
1083 c = slot->data;
1084
1085 fprintf(fp, "\tstatus %d, data_size %zd, signal %d, ack %zd, ch %p",
1086 c->status, c->data_size, c->signal, c->ack, c->ch);
1087 }
1088#endif
1089 fprintf(fp, "\n");
1090 }
1091}
1092#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.
atomic_int64_t head
Position of the producer.
size_t len
Length of a mapping.
uint8_t line_shift
High bits of a slot index give its stripe within that line: .stripe[slot >> line_shift].
#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_...
size_t elem_len
Size of one element.
bool fr_atomic_ring_push(fr_atomic_ring_t *ring, void *data)
Push a pointer into the ring; allocate a new segment on overflow.
int fd
The shared memory object behind a mapping, or -1 for a raw allocation.
atomic_int64_t tail
Position of the consumer.
fr_atomic_queue_t * fr_atomic_queue_malloc(TALLOC_CTX *ctx, size_t num, size_t elem_len)
Create fixed-size atomic queue outside the talloc hierarchy, owned by a talloc context.
#define cas_incr(_store, _var)
void fr_atomic_ring_free(fr_atomic_ring_t **ring_p)
Free the ring and all remaining segments.
static size_t atomic_queue_num_lines(size_t num, size_t stripes)
Number of cache lines needed to hold num slots.
static void atomic_ring_segment_free(fr_atomic_ring_segment_t *s)
uint8_t data[]
The element, 8 byte aligned as slots are a power of two bytes from a line boundary.
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.
bool fr_atomic_queue_mpmc_pop(fr_atomic_queue_t *aq, void *data)
Copy an element out of the atomic queue, any number of consumers.
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].
uint8_t lines[]
The lines, each exactly one cache line, aligned with cache lines to ensure producers and consumers do...
atomic_int64_t seq
The position this slot was last written or freed at.
size_t fr_atomic_queue_size(fr_atomic_queue_t *aq)
static fr_atomic_queue_t * atomic_queue_malloc_raw(size_t num, size_t elem_len)
Create fixed-size atomic queue outside any talloc hierarchy.
fr_atomic_queue_t * fr_atomic_queue_mmap(TALLOC_CTX *ctx, int *fd, size_t num, size_t elem_len)
Create or attach to a fixed-size atomic queue in shared memory, owned by a talloc context.
size_t slot_len
The line divided by the stripes: header, element, padding.
#define load(_var)
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.
bool fr_atomic_queue_mpmc_push(fr_atomic_queue_t *aq, void const *data)
Copy an element into the atomic queue, any number of producers.
void fr_atomic_queue_free(fr_atomic_queue_t **aq)
Free an atomic queue if it's not freed by ctx.
bool fr_atomic_queue_spsc_push(fr_atomic_queue_t *aq, void const *data)
Copy an element into the atomic queue, one producer only.
fr_atomic_queue_t * fr_atomic_queue_talloc(TALLOC_CTX *ctx, size_t num, size_t elem_len)
Create a fixed-size atomic queue.
#define store(_store, _var)
static void atomic_queue_init(fr_atomic_queue_t *aq, size_t num, size_t elem_len)
Initialise the sequence numbers and head/tail on a fresh queue buffer.
fr_atomic_queue_t * aq
The memory to release.
fr_atomic_queue_t * q
Per-segment queue, driven with the spsc functions.
#define acquire(_var)
static size_t atomic_queue_stripes(size_t elem_len)
How many slots a line holds for an element size.
bool fr_atomic_queue_spsc_pop(fr_atomic_queue_t *aq, void *data)
Copy an element out of the atomic queue, one consumer only.
static size_t atomic_queue_bytes(size_t num, size_t stripes)
Bytes needed for a queue of num slots, header included.
size_t num
Elements the queue holds.
static int atomic_queue_shm_open(size_t len)
Open an anonymous shared memory object of a given length.
size_t seg_size
Capacity of each segment.
#define CACHE_LINE_SIZE
size_t stripes
Slots per line.
Owner handle for a queue outside the talloc hierarchy.
Structure to hold the atomic queue.
One slot in the queue.
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
free(array)
talloc_free(hp)
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
int memfd_create(char const *name, unsigned int flags)
Emulates memfd_create with a named shared memory object.
Definition missing.c:597
#define fr_assert(_expr)
Definition rad_assert.h:37
@ 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
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
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
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
static fr_slen_t data
Definition value.h:1366
static fr_sbuff_err_t char size_t * len
Definition value.h:1061