25#define _POSIX_C_SOURCE 200809L
44#include <sys/syscall.h>
45#include <linux/io_uring.h>
46#if defined(__has_include)
47#if __has_include(<linux/time_types.h>)
48#include <linux/time_types.h>
53#include <sys/socket.h>
54#include <sys/eventfd.h>
60#ifndef IORING_ENTER_EXT_ARG
61#define IORING_ENTER_EXT_ARG (1U << 3)
70#ifndef IORING_ASYNC_CANCEL_ALL
71#define IORING_ASYNC_CANCEL_ALL (1U << 0)
74#ifndef IORING_ASYNC_CANCEL_FD
75#define IORING_ASYNC_CANCEL_FD (1U << 1)
78#ifndef IORING_ASYNC_CANCEL_ANY
79#define IORING_ASYNC_CANCEL_ANY (1U << 2)
82#ifndef IORING_ACCEPT_MULTISHOT
83#define IORING_ACCEPT_MULTISHOT (1U << 0)
86#ifndef IORING_CQE_F_MORE
87#define IORING_CQE_F_MORE (1U << 1)
92#define SOCK_CLOEXEC O_CLOEXEC
95#define SOCK_NONBLOCK O_NONBLOCK
112#if defined(__SANITIZE_THREAD__)
113#define REACTOR_TSAN 1
114#elif defined(__has_feature)
115#if __has_feature(thread_sanitizer)
116#define REACTOR_TSAN 1
122#define reactor_tsan_release(ring) __tsan_release((void *)(ring))
123#define reactor_tsan_acquire(ring) __tsan_acquire((void *)(ring))
125#define reactor_tsan_release(ring) ((void)(ring))
126#define reactor_tsan_acquire(ring) ((void)(ring))
175 return p->sq_off.array +
p->sq_entries *
sizeof(
uint32_t);
182 return p->cq_off.cqes +
p->cq_entries *
sizeof(
struct io_uring_cqe);
206#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
207#include <sys/event.h>
236} reactor_event_ctx_t;
241#define REACTOR_CHUNK_EVENTS 512
243typedef struct reactor_slot_chunk {
244 struct reactor_slot_chunk *next;
246 reactor_event_ctx_t slots[];
264struct cwist_reactor {
317#define LATENCY_PROBE_BUCKETS 18
337static inline bool reactor_on_run_thread(
const cwist_reactor_t *reactor) {
338 return t_run_reactor == reactor;
345 10, 25, 50, 100, 250, 500, 1000, 2500, 5000,
346 10000, 25000, 50000, 100000, 250000, 500000, 1000000, 2500000};
354 static _Atomic
int cached = -1;
357 const char *s =
getenv(
"CWIST_LATENCY_PROBE");
358 v = (s &&
strcmp(s,
"1") == 0) ? 1 : 0;
378 int mid = (lo + hi) / 2;
417 if (
count == 0)
continue;
420 (
unsigned long long)
count);
422 "[latency-probe] pid=%d %-12s p50=%llu p90=%llu p99=%llu p999=%llu max=%llu us\n",
427 (
unsigned long long)reactor->probe[
phase].max_us);
428 fprintf(
stderr,
"[latency-probe] pid=%d %-12s mean=%llu us over_5ms=%llu\n", (
int)
getpid(),
429 name, (
unsigned long long)(reactor->probe[
phase].sum_us /
count),
430 (
unsigned long long)reactor->probe[
phase].over_5ms);
434 fprintf(
stderr,
"[latency-probe] pid=%d %-12s [%-8u,%-8u) us : %llu\n",
438 fprintf(
stderr,
"[latency-probe] pid=%d %-12s [1000000, +inf) us : %llu\n",
439 (
int)
getpid(), name, (
unsigned long long)b[
i]);
450bool cwist_rx_uring_env_enabled(
void) {
451 static _Atomic
int cached = -1;
454 const char *s =
getenv(
"CWIST_RX_URING");
455 v = (s &&
strcmp(s,
"0") == 0) ? 0 : 1;
464 static _Atomic
int cached = -1;
467 const char *s =
getenv(
"CWIST_RX_EPOLL");
468 v = (s &&
strcmp(s,
"0") == 0) ? 0 : 1;
475 if (!reactor)
return false;
477 return cwist_rx_uring_env_enabled();
510#define EPFD_CHUNK_BITS 10
511#define EPFD_CHUNK (1u << EPFD_CHUNK_BITS)
512#define EPFD_MAX_FDS (1u << 22)
547void cwist_reactor_fd_forget(
int fd) {
563 if (
slot->queued)
return true;
564 if (r->rx_ready_n == r->rx_ready_cap) {
565 uint32_t cap = r->rx_ready_cap ? r->rx_ready_cap * 2 : 64;
567 if (!
grown)
return false;
568 if (r->rx_ready_n)
memcpy(
grown, r->rx_ready, r->rx_ready_n *
sizeof(*
grown));
571 r->rx_ready_cap =
cap;
573 r->rx_ready[r->rx_ready_n++] = fd;
586 slot->maybe_more =
false;
596 if (r->rx_cb) r->rx_cb(
slot->conn, res);
606 slot->maybe_more =
true;
617 int fd = r->rx_ready[
i];
620 slot->queued =
false;
627 memmove(r->rx_ready, r->rx_ready +
n, (r->rx_ready_n -
n) *
sizeof(*r->rx_ready));
636 if (!reactor_on_run_thread(reactor))
return false;
638 if (!
slot)
return false;
650 ev.data.u64 = ((
uint64_t)(
unsigned)fd << 1) | 1u;
653 slot->maybe_more =
false;
666 if (reactor) reactor->rx_cb = cb;
669bool cwist_reactor_recv_arm(
cwist_reactor_t *reactor,
int fd,
void *buf,
unsigned len,
void *conn,
671 if (!reactor || fd < 0 || !buf || len == 0 || !conn)
return false;
672 if (!cwist_reactor_rx_supported(reactor))
return false;
673 if (reactor->impl.use_epoll) {
685 if (((
uintptr_t)conn & 1u) != 0)
return false;
704 if (reactor_on_run_thread(reactor) && reactor->dispatching &&
705 reactor->deferred_n <
706 (
uint32_t)(
sizeof(reactor->deferred_sqes) /
sizeof(reactor->deferred_sqes[0]))) {
708 reactor->deferred_sqes[
slot] =
sqe;
709 reactor->deferred_ctxs[
slot] =
NULL;
719 &reactor->probe[
phase].sum_us, &reactor->probe[
phase].max_us,
728 static _Atomic
int cached = -1;
731 const char *s =
getenv(
"CWIST_ACCEPT_URING");
732 v = (s &&
strcmp(s,
"0") == 0) ? 0 : 1;
751 if (!reactor ||
listen_fd < 0 || !cb)
return false;
752 if (reactor->impl.use_epoll || !reactor->impl.active)
return false;
756 if (!
slot)
return false;
771 slot->next = reactor->accept_slots;
772 reactor->accept_slots =
slot;
786 reactor_event_ctx_t *
ev_ctx = r->free_head;
787 if (
ev_ctx) r->free_head = (reactor_event_ctx_t *)
ev_ctx->ctx;
805 chunk->next = r->chunks;
808 r->free_head = &
chunk->slots[0];
810 r->free_head = (reactor_event_ctx_t *)
ev_ctx->ctx;
826 if (!r || !
ev_ctx)
return;
828 ev_ctx->ctx = r->free_head;
834 if (!r || !node || !node->
cb)
return false;
842 if (
head ==
NULL && r->wake_wr >= 0) {
878#define REACTOR_IDLE_WAIT_NS 100000000ull
880static uint64_t reactor_now_ns(
void) {
887 r->timer_heap[
i] =
t;
888 t->heap_slot =
i + 1;
895 if (r->timer_heap[
parent]->deadline_ns <=
t->deadline_ns)
break;
896 timer_heap_place(r,
i, r->timer_heap[
parent]);
899 timer_heap_place(r,
i,
t);
906 if (child >= r->timer_n)
break;
907 if (child + 1 < r->timer_n &&
908 r->timer_heap[child + 1]->deadline_ns < r->timer_heap[child]->deadline_ns)
910 if (
t->deadline_ns <= r->timer_heap[child]->deadline_ns)
break;
911 timer_heap_place(r,
i, r->timer_heap[child]);
914 timer_heap_place(r,
i,
t);
921 if (
i == r->timer_n)
return;
922 timer_heap_place(r,
i,
last);
923 if (
i > 0 && r->timer_heap[(
i - 1) / 2]->deadline_ns >
last->deadline_ns)
926 timer_heap_down(r,
i);
938 if (!r || !timer || !timer->
cb)
return false;
939 if (timer->
heap_slot) timer_heap_remove(r, timer);
940 if (r->timer_n == r->timer_cap) {
941 uint32_t cap = r->timer_cap ? r->timer_cap * 2 : 64;
943 if (!heap)
return false;
944 if (r->timer_n)
memcpy(heap, r->timer_heap, r->timer_n *
sizeof(*heap));
946 r->timer_heap = heap;
951 timer_heap_place(r, r->timer_n - 1, timer);
952 timer_heap_up(r, r->timer_n - 1);
957 if (!r || !timer || !timer->
heap_slot)
return;
958 timer_heap_remove(r, timer);
969 if (r->timer_n == 0)
return;
972 while (
budget-- > 0 && r->timer_n > 0 && r->timer_heap[0]->deadline_ns <=
now) {
974 timer_heap_remove(r,
t);
984 uint64_t deadline = r->timer_heap[0]->deadline_ns;
985 if (deadline <=
now)
return 0;
990static void reactor_wake_cb(
int fd,
void *ctx) {
993 while (
read(fd, buf,
sizeof(buf)) > 0) {
995 reactor_drain_posts(r);
1008 if (!r)
return NULL;
1017 r->impl.use_epoll =
false;
1021 const char *backend =
getenv(
"CWIST_REACTOR_BACKEND");
1022 if (backend &&
strcmp(backend,
"epoll") == 0) r->impl.use_epoll =
true;
1028 r->impl.ring_fd = fd;
1031 r->impl.sqes_sz =
p.sq_entries *
sizeof(
struct io_uring_sqe);
1040 r->impl.sq_ring_mask = (
uint32_t *)((
char *)
sq_ptr +
p.sq_off.ring_mask);
1043 r->impl.sq_entries =
p.sq_entries;
1047 r->impl.cq_ring_mask = (
uint32_t *)((
char *)
cq_ptr +
p.cq_off.ring_mask);
1049 r->impl.cq_entries =
p.cq_entries;
1050 r->impl.active =
true;
1054 for (
uint32_t i = 0;
i <
p.sq_entries;
i++) r->impl.sq_array[
i] =
i;
1060 r->impl.use_epoll =
true;
1063 r->impl.use_epoll =
true;
1066 if (r->impl.use_epoll) {
1068 if (r->impl.epoll_fd < 0) {
1075#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1076 r->impl.kq_fd =
kqueue();
1077 if (r->impl.kq_fd < 0) {
1086 r->wake_wr = r->wake_fd;
1093 for (
int i = 0;
i < 2;
i++) {
1097 r->wake_fd =
pfds[0];
1098 r->wake_wr =
pfds[1];
1106 if (r->wake_fd >= 0 && !
cwist_reactor_add(r, r->wake_fd, reactor_wake_cb, &r,
sizeof(r))) {
1109 if (r->wake_wr != r->wake_fd)
close(r->wake_wr);
1117 if (!reactor)
return;
1122 reactor_drain_posts(reactor);
1123 if (reactor->wake_fd >= 0)
close(reactor->wake_fd);
1124 if (reactor->wake_wr >= 0 && reactor->wake_wr != reactor->wake_fd)
close(reactor->wake_wr);
1126 if (!reactor->impl.use_epoll) {
1128 if (reactor->impl.sqes)
munmap(reactor->impl.sqes, reactor->impl.sqes_sz);
1129 if (reactor->impl.cqes) {
1130 munmap((
char *)reactor->impl.cqes -
1131 (reactor->impl.cq_ring_sz -
1132 reactor->impl.cq_entries *
sizeof(
struct io_uring_cqe)),
1133 reactor->impl.cq_ring_sz);
1135 if (reactor->impl.sq_array) {
1136 munmap((
char *)reactor->impl.sq_array -
1137 (reactor->impl.sq_ring_sz - reactor->impl.sq_entries *
sizeof(
uint32_t)),
1138 reactor->impl.sq_ring_sz);
1140 close(reactor->impl.ring_fd);
1142 close(reactor->impl.epoll_fd);
1145#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1146 close(reactor->impl.kq_fd);
1175 uint32_t tail = *reactor->impl.sq_tail;
1178 uint32_t index = tail & *reactor->impl.sq_ring_mask;
1184 if (cwist_async_debug_enabled()) {
1187 if (
n <= 5 ||
n % 10000 == 0)
1189 "[reactor] uring enter failed fd=%d errno=%d(%s) sq=%u/%u total=%ld\n",
1191 reactor->impl.sq_entries,
n);
1197 }
else if (cwist_async_debug_enabled()) {
1200 if (
n <= 5 ||
n % 10000 == 0)
1202 tail -
head, reactor->impl.sq_entries,
n);
1218 uint32_t tail = *reactor->impl.sq_tail;
1222 uint32_t index = (tail +
i) & *reactor->impl.sq_ring_mask;
1239 reactor->deferred_n = 0;
1244 reactor_event_ctx_t *
ev_ctx = reactor->deferred_ctxs[
i];
1248 struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000};
1252 if (cwist_async_debug_enabled()) {
1253 fprintf(
stderr,
"[reactor] deferred submit failed fd=%d; closing\n", (
int)
sqe->fd);
1257 free_reactor_ctx(reactor,
ev_ctx);
1258 }
else if (reactor->rx_cb) {
1275 uint32_t tail = *reactor->impl.sq_tail;
1279 uint32_t index = (tail +
i) & *reactor->impl.sq_ring_mask;
1280 memcpy(&reactor->impl.sqes[index], &reactor->deferred_sqes[
i],
1281 sizeof(reactor->deferred_sqes[
i]));
1285 reactor->deferred_n = 0;
1286 reactor->sq_unsubmitted +=
n;
1295 if (!reactor || fd < 0)
return false;
1296 reactor_event_ctx_t *
ev_ctx = alloc_reactor_ctx(reactor, fd, cb, payload,
payload_size);
1297 if (!
ev_ctx)
return false;
1305 if (!reactor->impl.use_epoll) {
1317 if (reactor_on_run_thread(reactor) && reactor->dispatching &&
1318 reactor->deferred_n <
1319 (
uint32_t)(
sizeof(reactor->deferred_sqes) /
sizeof(reactor->deferred_sqes[0]))) {
1321 reactor->deferred_sqes[
slot] =
sqe;
1328 free_reactor_ctx(reactor,
ev_ctx);
1340 free_reactor_ctx(reactor,
ev_ctx);
1343#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1350 free_reactor_ctx(reactor,
ev_ctx);
1353 free_reactor_ctx(reactor,
ev_ctx);
1359 return reactor_add_common(reactor, fd, cb, payload,
payload_size,
false);
1364 return reactor_add_common(reactor, fd, cb, payload,
payload_size,
true);
1369 if (!reactor || fd < 0)
return false;
1370 reactor_event_ctx_t *
ev_ctx = alloc_reactor_ctx(reactor, fd, cb, payload,
payload_size);
1371 if (!
ev_ctx)
return false;
1374 if (!reactor->impl.use_epoll) {
1384 free_reactor_ctx(reactor,
ev_ctx);
1394 free_reactor_ctx(reactor,
ev_ctx);
1397#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1403 free_reactor_ctx(reactor,
ev_ctx);
1406 free_reactor_ctx(reactor,
ev_ctx);
1411 if (!reactor || fd < 0)
return false;
1414 if (!reactor->impl.use_epoll) {
1427#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1445static uint32_t reactor_drain_chunk(
void) {
1446 static _Atomic
int cached = -1;
1449 const char *s =
getenv(
"CWIST_REACTOR_DRAIN_CHUNK");
1470 static _Atomic
long cached = -1;
1473 const char *s =
getenv(
"CWIST_REACTOR_CQ_GRACE_NS");
1500 static _Atomic
long cached = -1;
1503 const char *s =
getenv(
"CWIST_REACTOR_ROUND_BUDGET_US");
1525 static _Atomic
int cached = -1;
1528 const char *s =
getenv(
"CWIST_MALLOC_TRIM_IDLE_SEC");
1554 r->idle_since_ns = 0;
1560 if (r->idle_since_ns == 0) {
1561 r->idle_since_ns =
now;
1566 r->idle_since_ns =
now;
1570#if defined(__x86_64__) || defined(__i386__)
1571#define reactor_cpu_relax() __builtin_ia32_pause()
1572#elif defined(__aarch64__)
1573#define reactor_cpu_relax() __asm__ __volatile__("yield")
1575#define reactor_cpu_relax() ((void)0)
1580 if (!reactor)
return;
1582 t_run_reactor = reactor;
1585 if (!reactor->impl.use_epoll) {
1587 while (reactor_running(reactor)) {
1588 reactor_drain_posts(reactor);
1589 reactor_run_timers(reactor);
1620 if (
ph == *reactor->impl.cq_tail) {
1626 while (
ph == *reactor->impl.cq_tail) {
1628 if (((++
spins) & 31u) == 0) {
1646 reactor->sq_unsubmitted = 0;
1668 uint32_t tail = *reactor->impl.cq_tail;
1677 reactor->dispatching =
true;
1700 while (
head != tail) {
1703 if (user_data & 1u) {
1710 if (reactor->rx_cb) {
1711 reactor->rx_cb((
void *)(user_data & ~(
uint64_t)1u),
cqe->res);
1713 }
else if (user_data & 2u) {
1723 if (res >= 0)
slot->cb(
slot->ctx, res);
1730 reactor_event_ctx_t *
ev_ctx = (reactor_event_ctx_t *)user_data;
1732 if (
cqe->res >= 0) {
1762 free_reactor_ctx(reactor,
ev_ctx);
1769 reactor_drain_posts(reactor);
1789 reactor->dispatching =
false;
1795 while (reactor_running(reactor)) {
1796 reactor_drain_posts(reactor);
1797 reactor_run_timers(reactor);
1808 int wait_ms = (
int)((reactor_wait_ns(reactor) + 500000ull) / 1000000ull);
1811 if (reactor->rx_ready_n > 0)
wait_ms = 0;
1838 for (
int i = 0;
i <
n;
i++) {
1841 if (
events[
i].data.u64 & 1u) {
1845 reactor_drain_posts(reactor);
1849 reactor_event_ctx_t *
ev_ctx = (reactor_event_ctx_t *)
events[
i].data.ptr;
1852 free_reactor_ctx(reactor,
ev_ctx);
1855 reactor_drain_posts(reactor);
1862#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1868 while (reactor_running(reactor)) {
1869 reactor_drain_posts(reactor);
1870 reactor_run_timers(reactor);
1888 for (
int i = 0;
i <
n;
i++) {
1892 free_reactor_ctx(reactor,
ev_ctx);
1895 reactor_drain_posts(reactor);
1901 t_run_reactor =
NULL;
1905 if (!reactor)
return;
1915 if (reactor->wake_wr >= 0) {
__attribute__((constructor))
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
bool cwist_reactor_add(cwist_reactor_t *reactor, int fd, cwist_reactor_cb_t cb, const void *payload, size_t payload_size)
void cwist_reactor_timer_init(cwist_reactor_timer_t *timer, void(*cb)(void *ctx), void *ctx)
bool cwist_reactor_del(cwist_reactor_t *reactor, int fd)
#define REACTOR_IDLE_WAIT_NS
#define REACTOR_CHUNK_EVENTS
void cwist_reactor_destroy(cwist_reactor_t *reactor)
void cwist_reactor_timer_cancel(cwist_reactor_t *r, cwist_reactor_timer_t *timer)
struct reactor_slot_chunk reactor_slot_chunk_t
void cwist_reactor_stop(cwist_reactor_t *reactor)
cwist_reactor_t * cwist_reactor_create(void)
void cwist_reactor_run(cwist_reactor_t *reactor)
bool cwist_reactor_mod(cwist_reactor_t *reactor, int fd, cwist_reactor_cb_t cb, const void *payload, size_t payload_size)
bool cwist_reactor_timer_armed(const cwist_reactor_timer_t *timer)
bool cwist_reactor_timer_arm(cwist_reactor_t *r, cwist_reactor_timer_t *timer, uint64_t delay_us)
bool cwist_reactor_add_out(cwist_reactor_t *reactor, int fd, cwist_reactor_cb_t cb, const void *payload, size_t payload_size)
#define CWIST_REACTOR_PAYLOAD_SIZE
struct cwist_reactor cwist_reactor_t
void(* cwist_reactor_cb_t)(int fd, void *ctx)
void(* cwist_accept_cb_t)(void *ctx, int fd)
void(* cwist_rx_cb_t)(void *conn, int res)
Unified Graceful Shutdown support for CWIST.
atomic_int g_cwist_running
Global running flag. Checked by all server event loops.
struct cwist_reactor_post * next