lib: Add epoch-based SMR

This commit is contained in:
2026-11-01 21:06:48 +01:00
parent 3ff068e944
commit 7c04530681
24 changed files with 1059 additions and 84 deletions
+108
View File
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: ISC
#include <zinnia/alloc.h>
#include <zinnia/assert.h>
#include <zinnia/epoch.h>
#include <zinnia/initgraph.h>
#include <zinnia/lfqueue.h>
#include <zinnia/slab.h>
struct lfqueue_node {
_Atomic(struct lfqueue_node*) next;
void* value;
};
static struct slab_cache* node_cache;
static void lfqueue_stage_init(struct boot_info*) {
node_cache =
slab_cache_new("lfqueue_node", sizeof(struct lfqueue_node), alignof(struct lfqueue_node), SLAB_EPOCH_SAFE);
assert(node_cache, "Unable to create the lfqueue node cache");
}
INIT_TASK(lfqueue_stage, "core.lfqueue", lfqueue_stage_init);
INIT_DEPENDS(lfqueue_stage, mem_stage);
static struct lfqueue_node* node_alloc(void* value) {
struct lfqueue_node* node = slab_cache_alloc(node_cache, 0);
if (node)
node->value = value;
return node;
}
errno_t lfqueue_init(struct lfqueue* queue) {
struct lfqueue_node* dummy = node_alloc(nullptr);
if (!dummy)
return ENOMEM;
atomic_init(&queue->head, dummy);
atomic_init(&queue->tail, dummy);
return 0;
}
void lfqueue_destroy(struct lfqueue* queue) {
struct lfqueue_node* node = atomic_load(&queue->head, ATOMIC_ACQUIRE);
while (node) {
struct lfqueue_node* next = atomic_load(&node->next, ATOMIC_RELAXED);
slab_cache_free(node_cache, node);
node = next;
}
atomic_store(&queue->head, nullptr, ATOMIC_RELAXED);
atomic_store(&queue->tail, nullptr, ATOMIC_RELAXED);
}
bool lfqueue_empty(struct lfqueue* queue) {
epoch_enter();
struct lfqueue_node* head = epoch_deref(&queue->head);
const bool empty = atomic_load(&head->next, ATOMIC_RELAXED) == nullptr;
epoch_exit();
return empty;
}
errno_t lfqueue_push(struct lfqueue* queue, void* value) {
struct lfqueue_node* node = node_alloc(value);
if (!node)
return ENOMEM;
epoch_enter();
while (true) {
struct lfqueue_node* tail = epoch_deref(&queue->tail);
struct lfqueue_node* next = epoch_deref(&tail->next);
if (tail != atomic_load(&queue->tail, ATOMIC_RELAXED))
continue;
if (next) {
atomic_compare_exchange(&queue->tail, &tail, next, ATOMIC_RELEASE, ATOMIC_RELAXED);
continue;
}
if (atomic_compare_exchange(&tail->next, &next, node, ATOMIC_RELEASE, ATOMIC_RELAXED)) {
atomic_compare_exchange(&queue->tail, &tail, node, ATOMIC_RELEASE, ATOMIC_RELAXED);
break;
}
}
epoch_exit();
return 0;
}
bool lfqueue_pop(struct lfqueue* queue, void** out) {
epoch_enter();
while (true) {
struct lfqueue_node* head = epoch_deref(&queue->head);
struct lfqueue_node* tail = atomic_load(&queue->tail, ATOMIC_RELAXED);
struct lfqueue_node* next = epoch_deref(&head->next);
if (head != atomic_load(&queue->head, ATOMIC_RELAXED))
continue;
if (!next) {
epoch_exit();
return false;
}
if (head == tail) {
atomic_compare_exchange(&queue->tail, &tail, next, ATOMIC_RELEASE, ATOMIC_RELAXED);
continue;
}
void* value = next->value;
if (atomic_compare_exchange(&queue->head, &head, next, ATOMIC_RELEASE, ATOMIC_RELAXED)) {
slab_cache_free(node_cache, head);
epoch_exit();
*out = value;
return true;
}
}
}