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