91 lines
2.5 KiB
C
91 lines
2.5 KiB
C
/*
|
|
* queue.c - Lock-free single-producer single-consumer (SPSC) bounded queue
|
|
*
|
|
* Uses C11 atomics with acquire/release ordering for correct head/tail
|
|
* synchronization without locks. The eventfd notify on push wakes the
|
|
* consumer's epoll loop (cold thread) for immediate dispatch.
|
|
*/
|
|
|
|
#include "queue.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/eventfd.h>
|
|
#include <errno.h>
|
|
|
|
int spsc_init(spsc_queue_t *q, int wakeup_fd) {
|
|
memset(q, 0, sizeof(*q));
|
|
q->buffer = calloc(Spsc_QUEUE_DEPTH, sizeof(signal_entry_t));
|
|
if (!q->buffer) return -1;
|
|
q->head = 0;
|
|
q->tail = 0;
|
|
q->depth = Spsc_QUEUE_DEPTH;
|
|
q->dropped = 0;
|
|
q->eventfd = wakeup_fd;
|
|
return 0;
|
|
}
|
|
|
|
void spsc_destroy(spsc_queue_t *q) {
|
|
/* eventfd is owned by caller (event_loops), don't close */
|
|
q->eventfd = -1;
|
|
free(q->buffer);
|
|
q->buffer = NULL;
|
|
}
|
|
|
|
static inline void eventfd_notify(int fd) {
|
|
uint64_t val = 1;
|
|
ssize_t ret;
|
|
do {
|
|
ret = write(fd, &val, sizeof(val));
|
|
} while (ret < 0 && errno == EINTR);
|
|
(void)ret;
|
|
}
|
|
|
|
bool spsc_push(spsc_queue_t *q, const signal_entry_t *entry) {
|
|
uint32_t head = atomic_load_explicit(&q->head, memory_order_relaxed);
|
|
uint32_t tail = atomic_load_explicit(&q->tail, memory_order_acquire);
|
|
|
|
uint32_t next_head = head + 1;
|
|
if (next_head >= q->depth) next_head = 0;
|
|
|
|
if (next_head == tail) {
|
|
q->dropped++;
|
|
return false;
|
|
}
|
|
|
|
q->buffer[head] = *entry;
|
|
|
|
atomic_store_explicit(&q->head, next_head, memory_order_release);
|
|
eventfd_notify(q->eventfd);
|
|
return true;
|
|
}
|
|
|
|
bool spsc_pop(spsc_queue_t *q, signal_entry_t *entry) {
|
|
uint32_t tail = atomic_load_explicit(&q->tail, memory_order_relaxed);
|
|
uint32_t head = atomic_load_explicit(&q->head, memory_order_acquire);
|
|
|
|
if (tail == head) return false;
|
|
|
|
*entry = q->buffer[tail];
|
|
|
|
uint32_t next_tail = tail + 1;
|
|
if (next_tail >= q->depth) next_tail = 0;
|
|
|
|
atomic_store_explicit(&q->tail, next_tail, memory_order_release);
|
|
return true;
|
|
}
|
|
|
|
bool spsc_empty(const spsc_queue_t *q) {
|
|
uint32_t head = atomic_load_explicit(&q->head, memory_order_acquire);
|
|
uint32_t tail = atomic_load_explicit(&q->tail, memory_order_acquire);
|
|
return head == tail;
|
|
}
|
|
|
|
uint32_t spsc_count(const spsc_queue_t *q) {
|
|
uint32_t head = atomic_load_explicit(&q->head, memory_order_acquire);
|
|
uint32_t tail = atomic_load_explicit(&q->tail, memory_order_acquire);
|
|
if (head >= tail) return head - tail;
|
|
return q->depth - tail + head;
|
|
}
|