![]() |
The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
|
Thread-safe queues. More...
#include <stdint.h>#include <stdalign.h>#include <inttypes.h>#include <stdlib.h>#include <freeradius-devel/autoconf.h>#include <freeradius-devel/io/atomic_queue.h>#include <freeradius-devel/util/math.h>
Include dependency graph for atomic_queue.c:Go to the source code of this file.
Data Structures | |
| struct | fr_atomic_queue_line_t |
| One cache line of stripes. More... | |
| struct | fr_atomic_queue_owner_t |
| Owner handle for a raw queue allocation. More... | |
| struct | fr_atomic_queue_s |
| Structure to hold the atomic queue. More... | |
| struct | fr_atomic_queue_slot_t |
| One slot in the queue, holding one queued pointer. More... | |
| struct | fr_atomic_ring_s |
| struct | fr_atomic_ring_segment_s |
Macros | |
| #define | acquire(_var) atomic_load_explicit(&_var, memory_order_acquire) |
| #define | atomic_int64_t _Atomic(int64_t) |
| #define | atomic_uint32_t _Atomic(uint32_t) |
| #define | atomic_uint64_t _Atomic(uint64_t) |
| #define | CACHE_LINE_SIZE 128 |
| #define | cas_decr(_store, _var) atomic_compare_exchange_strong_explicit(&_store, &_var, _var - 1, memory_order_release, memory_order_relaxed) |
| #define | cas_incr(_store, _var) atomic_compare_exchange_strong_explicit(&_store, &_var, _var + 1, memory_order_release, memory_order_relaxed) |
| #define | load(_var) atomic_load_explicit(&_var, memory_order_relaxed) |
| #define | store(_store, _var) atomic_store_explicit(&_store, _var, memory_order_release) |
| #define | STRIPES_PER_LINE (CACHE_LINE_SIZE / sizeof(fr_atomic_queue_slot_t)) |
| How many stripes a line contains. | |
Typedefs | |
| typedef struct fr_atomic_ring_segment_s | fr_atomic_ring_segment_t |
Functions | |
| static int | _atomic_queue_owner_free (fr_atomic_queue_owner_t *owner) |
| static int | _atomic_ring_free (fr_atomic_ring_t *ring) |
| talloc destructor for fr_atomic_ring_t: walk the chain and free segments | |
| static size_t | atomic_queue_bytes (size_t size) |
Bytes needed for a queue of size slots, header included. | |
| 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. | |
| static fr_atomic_queue_t * | atomic_queue_malloc_raw (size_t size) |
| Create fixed-size atomic queue outside any talloc hierarchy. | |
| static size_t | atomic_queue_num_lines (size_t size) |
Number of cache lines needed to hold size slots. | |
| 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 fr_atomic_ring_segment_t * | atomic_ring_segment_alloc (size_t seg_size) |
| Allocate a fresh segment and its embedded queue. | |
| static void | atomic_ring_segment_free (fr_atomic_ring_segment_t *s) |
| 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_debug (FILE *fp, fr_atomic_queue_t *aq) |
| Dump an atomic queue. | |
| void | fr_atomic_queue_free (fr_atomic_queue_t **aq) |
| Free an atomic queue if it's not freed by ctx. | |
| 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. | |
| bool | fr_atomic_queue_pop (fr_atomic_queue_t *aq, void **p_data) |
| Pop a pointer from the atomic queue. | |
| bool | fr_atomic_queue_push (fr_atomic_queue_t *aq, void *data) |
| Push a pointer into the atomic queue. | |
| size_t | fr_atomic_queue_size (fr_atomic_queue_t *aq) |
| fr_atomic_queue_t * | fr_atomic_queue_talloc (TALLOC_CTX *ctx, size_t size) |
| Create fixed-size atomic queue. | |
| fr_atomic_ring_t * | fr_atomic_ring_alloc (TALLOC_CTX *ctx, size_t seg_size) |
| Allocate an empty SPSC ring. | |
| void | fr_atomic_ring_free (fr_atomic_ring_t **ring_p) |
| Free the ring and all remaining segments. | |
| bool | fr_atomic_ring_pop (fr_atomic_ring_t *ring, void **p_data) |
| Pop a pointer from the ring, advancing past drained segments. | |
| bool | fr_atomic_ring_push (fr_atomic_ring_t *ring, void *data) |
| Push a pointer into the ring; allocate a new segment on overflow. | |
Thread-safe queues.
This is an implementation of a bounded MPMC ring buffer with per-slot sequence numbers, as described by Dmitry Vyukov, and independently discovered/implemented by Alister Winfield.
Definition in file atomic_queue.c.
| struct fr_atomic_queue_line_t |
One cache line of stripes.
A line is padded and aligned to span exactly ONE cache line. A stripe is the set of slots at the same offset in every line, so it runs the complete length of the queue memory.
Stripes let a line hold several slots without the slots sharing a cache line with their neighbours in queue order. Padding each slot out to a full cache line instead would waste the majority of queue memory for no gain.
Slot i translates to line[i % num_lines].stripe[i / num_lines], i.e. for stripe 0, slot positions match line positions (slot 0 at line 0), and once the slot number exceeds the number of entries in the array we wrap, and use the next stripe.
stripe[0] stripe[1] stripe[2] ... stripe[7]
+----------+----------+----------+ +----------+
line[0] | slot 0 | slot 4 | slot 8 | ... | slot 28 | <- one cache line
+----------+----------+----------+ +----------+
line[1] | slot 1 | slot 5 | slot 9 | ... | slot 29 |
+----------+----------+----------+ +----------+
line[2] | slot 2 | slot 6 | slot 10 | ... | slot 30 |
+----------+----------+----------+ +----------+
line[3] | slot 3 | slot 7 | slot 11 | ... | slot 31 |
+----------+----------+----------+ +----------+
^
one stripe: slots 0..3, one per line
A producer at position p and a consumer at position c share a line only when p and c are congruent modulo the number of lines, this happens rarely enough that there's not a noticeable impact on performance.
Definition at line 126 of file atomic_queue.c.
Collaboration diagram for fr_atomic_queue_line_t:| Data Fields | ||
|---|---|---|
| fr_atomic_queue_slot_t | stripe[STRIPES_PER_LINE] | |
| struct fr_atomic_queue_owner_t |
Owner handle for a raw queue allocation.
The queue memory stays outside the talloc hierarchy. This handle is the only talloc'd part, so freeing the context the handle lives in frees the queue.
Definition at line 333 of file atomic_queue.c.
Collaboration diagram for fr_atomic_queue_owner_t:| Data Fields | ||
|---|---|---|
| fr_atomic_queue_t * | aq | The raw allocation to release. |
| struct fr_atomic_queue_s |
Structure to hold the atomic queue.
Definition at line 137 of file atomic_queue.c.
Collaboration diagram for fr_atomic_queue_s:| Data Fields | ||
|---|---|---|
| void * | chunk |
The start of the talloc chunk to pass to free, or NULL if this queue was allocated raw via fr_atomic_queue_malloc. We need to play tricks to get aligned memory with talloc. |
| atomic_int64_t | head |
Position of the producer. Cache aligned bytes to ensure it's in a different cache line to tail to reduce memory contention. |
| fr_atomic_queue_line_t | line[] | The line array, aligned with cache lines to ensure producer and consumers don't conflict. |
| size_t | line_mask |
Low bits of a slot index give its line: line[slot & line_mask]. Set at init to num_lines - 1. |
| uint8_t | line_shift |
High bits of a slot index give its stripe within that line: .stripe[slot >> line_shift]. Equal to log2(num_lines). Set at init, so a lookup is a mask and a shift, no division and no modulo! |
| size_t | size |
The length of the queue. This is static. Also needs to be cache aligned, otherwise it can end up directly after tail in memory and share a cache line. |
| atomic_int64_t | tail |
Position of the consumer. Cache aligned bytes to ensure it's in a different cache line to tail to reduce memory contention. Reads may still need to occur from size whilst the producer is writing to tail. |
| struct fr_atomic_queue_slot_t |
One slot in the queue, holding one queued pointer.
Must not be packed. Packing drops the struct's alignment to 1 (same as an array of uint8_t), so the compiler has to treat seq as possibly unaligned. The native atomic instructions require an aligned address, so clang stops emitting them and calls the libatomic fallback instead, which takes a mutex. That turns every push and pop on this lock free queue into a locked operation. gcc emits the native instruction regardless, and relies on the address being aligned at runtime. Packing saves nothing here anyway, as the two 8 byte fields have no padding between them.
Definition at line 78 of file atomic_queue.c.
| Data Fields | ||
|---|---|---|
| void * | data | |
| atomic_int64_t | seq | Must be seq then data to ensure seq is 64bit aligned for 32bit address spaces. |
| struct fr_atomic_ring_s |
Definition at line 617 of file atomic_queue.c.
| Data Fields | ||
|---|---|---|
| size_t | seg_size | Capacity of each segment. |
| struct fr_atomic_ring_segment_s |
Definition at line 610 of file atomic_queue.c.
Collaboration diagram for fr_atomic_ring_segment_s:| Data Fields | ||
|---|---|---|
| fr_atomic_queue_t * | q | Per-segment MPMC ring (used SPSC here). |
| #define acquire | ( | _var | ) | atomic_load_explicit(&_var, memory_order_acquire) |
Definition at line 53 of file atomic_queue.c.
Definition at line 46 of file atomic_queue.c.
Definition at line 47 of file atomic_queue.c.
Definition at line 48 of file atomic_queue.c.
| #define CACHE_LINE_SIZE 128 |
Definition at line 64 of file atomic_queue.c.
| #define cas_decr | ( | _store, | |
| _var | |||
| ) | atomic_compare_exchange_strong_explicit(&_store, &_var, _var - 1, memory_order_release, memory_order_relaxed) |
Definition at line 51 of file atomic_queue.c.
| #define cas_incr | ( | _store, | |
| _var | |||
| ) | atomic_compare_exchange_strong_explicit(&_store, &_var, _var + 1, memory_order_release, memory_order_relaxed) |
Definition at line 50 of file atomic_queue.c.
| #define load | ( | _var | ) | atomic_load_explicit(&_var, memory_order_relaxed) |
Definition at line 52 of file atomic_queue.c.
| #define store | ( | _store, | |
| _var | |||
| ) | atomic_store_explicit(&_store, _var, memory_order_release) |
Definition at line 54 of file atomic_queue.c.
| #define STRIPES_PER_LINE (CACHE_LINE_SIZE / sizeof(fr_atomic_queue_slot_t)) |
How many stripes a line contains.
Definition at line 88 of file atomic_queue.c.
| typedef struct fr_atomic_ring_segment_s fr_atomic_ring_segment_t |
Definition at line 608 of file atomic_queue.c.
|
static |
Definition at line 337 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:
|
static |
talloc destructor for fr_atomic_ring_t: walk the chain and free segments
Definition at line 654 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:Bytes needed for a queue of size slots, header included.
| [in] | size | Slot count, already rounded up to a power of 2. |
Definition at line 193 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:
|
static |
Initialise the sequence numbers and head/tail on a fresh queue buffer.
Shared between the talloc and raw allocators. The buffer must already be cache-line aligned and sized to hold size slots.
| [in] | aq | The queue buffer to initialise. |
| [in] | size | Slot count, already rounded up to a power of 2. |
Definition at line 222 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:
|
static |
Create fixed-size atomic queue outside any talloc hierarchy.
Backed by posix_memalign. The queue has no owner: chunk is NULL and the caller releases it with fr_atomic_queue_free or plain free().
For callers that allocate from threads where talloc is not safe (for example, library-owned callback threads).
| [in] | size | The number of entries in the queue. |
Definition at line 311 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:Number of cache lines needed to hold size slots.
A queue smaller than one line uses a single, partly filled line.
| [in] | size | Slot count, already rounded up to a power of 2. |
Definition at line 182 of file atomic_queue.c.
Here is the caller graph for this function:
|
inlinestatic |
Map a queue position to its slot.
Positions wrap at size. Within one stripe, consecutive positions increment one line at a time, and move to the next stripe once every line has been visited.
| [in] | aq | The queue. |
| [in] | pos | The head or tail position, unmasked. |
Definition at line 207 of file atomic_queue.c.
Here is the caller graph for this function:
|
static |
Allocate a fresh segment and its embedded queue.
Uses the raw (non-talloc) allocator so this function is safe to call from the producer thread even when that thread cannot safely use talloc.
Definition at line 630 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:
|
static |
Definition at line 647 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:
|
inlinestatic |
Link a new segment after the current head and make it the head.
The consumer only inspects h->next and advances past h once it sees a non-NULL value there, so h->next is published with release ordering, which pairs with the acquire in fr_atomic_ring_pop().
Modelled for Coverity in src/coverity-model/merged_model.c, as the analyser does not treat an atomic store as publishing the pointer.
| [in] | ring | the segment belongs to. |
| [in] | h | Current head segment. |
| [in] | n | New segment to publish. |
Definition at line 728 of file atomic_queue.c.
Here is the caller graph for this function:| void fr_atomic_queue_debug | ( | FILE * | fp, |
| fr_atomic_queue_t * | aq | ||
| ) |
Dump an atomic queue.
Absolutely NOT thread-safe.
| [in] | aq | The atomic queue to debug. |
| [in] | fp | where the debugging information will be printed. |
Definition at line 839 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:| void fr_atomic_queue_free | ( | fr_atomic_queue_t ** | aq | ) |
Free an atomic queue if it's not freed by ctx.
The queue memory must be cache line aligned, so it lives in one of three places: a talloc aligned array (chunk is the array), a raw allocation owned by a talloc handle (chunk is the handle, whose destructor frees the queue), or a raw allocation with no owner (chunk is NULL).
Definition at line 386 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:| 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.
The queue itself is a raw posix_memalign allocation, so it can be pushed to and popped from by threads where talloc is not safe, and can later be placed in memory talloc does not manage. A small owner handle allocated in ctx frees the queue when ctx is freed, or when the caller calls fr_atomic_queue_free.
| [in] | ctx | The talloc ctx which owns the queue. |
| [in] | size | The number of entries in the queue. |
Definition at line 358 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:| bool fr_atomic_queue_pop | ( | fr_atomic_queue_t * | aq, |
| void ** | p_data | ||
| ) |
Pop a pointer from the atomic queue.
| [in] | aq | the atomic queue to retrieve data from. |
| [out] | p_data | where to write the data. |
Definition at line 529 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:| bool fr_atomic_queue_push | ( | fr_atomic_queue_t * | aq, |
| void * | data | ||
| ) |
Push a pointer into the atomic queue.
| [in] | aq | The atomic queue to add data to. |
| [in] | data | to push. |
Definition at line 406 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:| size_t fr_atomic_queue_size | ( | fr_atomic_queue_t * | aq | ) |
| fr_atomic_queue_t * fr_atomic_queue_talloc | ( | TALLOC_CTX * | ctx, |
| size_t | size | ||
| ) |
Create fixed-size atomic queue.
| [in] | ctx | The talloc ctx to allocate the queue in. |
| [in] | size | The number of entries in the queue. |
Definition at line 268 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:| fr_atomic_ring_t * fr_atomic_ring_alloc | ( | TALLOC_CTX * | ctx, |
| size_t | seg_size | ||
| ) |
Allocate an empty SPSC ring.
| [in] | ctx | talloc ctx that owns the ring handle (segments live outside talloc; they are freed by the ring's destructor). |
| [in] | seg_size | Per-segment capacity. Rounded up to a power of 2. |
Definition at line 678 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:| void fr_atomic_ring_free | ( | fr_atomic_ring_t ** | ring_p | ) |
Free the ring and all remaining segments.
Equivalent to talloc_free() on the ring, but nulls the caller's handle in the style of fr_atomic_queue_free.
Definition at line 707 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:| bool fr_atomic_ring_pop | ( | fr_atomic_ring_t * | ring, |
| void ** | p_data | ||
| ) |
Pop a pointer from the ring, advancing past drained segments.
Single-consumer only. Must not be called concurrently with itself.
| [in] | ring | Ring to pop from. |
| [out] | p_data | Where to write the popped value on success. |
Definition at line 773 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:| bool fr_atomic_ring_push | ( | fr_atomic_ring_t * | ring, |
| void * | data | ||
| ) |
Push a pointer into the ring; allocate a new segment on overflow.
Single-producer only. Must not be called concurrently with itself.
| [in] | ring | Ring to push into. |
| [in] | data | Value to push (must be non-NULL). |
Definition at line 746 of file atomic_queue.c.
Here is the call graph for this function:
Here is the caller graph for this function:
1.9.8