cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
kqueue.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
4#include <cwist/core/mem/gc.h>
5#include <sys/types.h>
6#include <sys/event.h>
7#include <sys/time.h>
8#include <stdlib.h>
9#include <stdio.h>
10#include <unistd.h>
11#include <errno.h>
12#include <stdatomic.h>
13#include <stdint.h>
14#include <time.h>
15
21/* ident 0 is never a job: jobs use their wrapper's address as ident. */
22#define KQ_WAKE_IDENT 0
23
24struct cwist_io_queue {
25 int kq_fd;
26 atomic_bool running;
27 /* Jobs accepted by submit and not yet taken by a runner. */
28 atomic_size_t pending_jobs;
29};
30
37 (void)capacity;
39 if (!q) return NULL;
41
42 q->kq_fd = kqueue();
43 if (q->kq_fd < 0) {
44 perror("kqueue");
45 cwist_free(q);
46 return NULL;
47 }
48
49 /* The stop wake. No EV_CLEAR: once stop triggers it, it stays active and
50 * every runner's kevent() returns, not just the first one to look. */
51 struct kevent kev;
53 if (kevent(q->kq_fd, &kev, 1, NULL, 0, NULL) < 0) {
54 perror("kevent wake");
55 close(q->kq_fd);
56 cwist_free(q);
57 return NULL;
58 }
59 atomic_init(&q->running, true);
60 atomic_init(&q->pending_jobs, 0);
61 return q;
62}
63
67struct job_wrapper {
68 cwist_job_func func;
69 void *arg;
70};
71
80bool cwist_io_queue_submit(cwist_io_queue *q, cwist_job_func func, void *arg) {
81 if (!q || !func) return false;
82
83 /* Count the job before checking running, so a runner that sees
84 * running == false and pending_jobs == 0 knows no submit is in flight. */
86 if (!atomic_load_explicit(&q->running, memory_order_acquire)) {
88 return false;
89 }
90
91 struct job_wrapper *job = cwist_alloc(sizeof(struct job_wrapper));
92 if (!job) {
94 return false;
95 }
97 job->func = func;
98 job->arg = arg;
99
100 struct kevent kev;
101 // We use EVFILT_USER to trigger a custom event.
102 // Ident = pointer to job (unique ID)
104 job);
105
106 if (kevent(q->kq_fd, &kev, 1, NULL, 0, NULL) < 0) {
107 perror("kevent submit");
110 return false;
111 }
112 return true;
113}
114
119void cwist_io_queue_run(cwist_io_queue *q) {
120 if (!q) return;
121 struct kevent events[32];
122 while (atomic_load_explicit(&q->running, memory_order_acquire) ||
123 atomic_load_explicit(&q->pending_jobs, memory_order_acquire) > 0) {
124 int n = kevent(q->kq_fd, NULL, 0, events, 32, NULL);
125 if (n < 0) {
126 if (errno == EINTR) continue;
127 perror("kevent wait");
128 break;
129 }
130
131 int ran = 0;
132 for (int i = 0; i < n; i++) {
133 if (events[i].filter != EVFILT_USER || events[i].ident == KQ_WAKE_IDENT) continue;
134 struct job_wrapper *job = (struct job_wrapper *)events[i].udata;
135 if (!job) continue;
137 job->func(job->arg);
139 ran++;
140
141 // Full-GC mode: catch whatever this job forgot to cwist_free()
142 // right away instead of letting it pile up for the lifetime of
143 // this (long-lived, pooled) worker thread, as io_queue.c does.
144 if (cwist_full_gc_enabled()) {
146 }
147 }
148
149 /* Stopping, but another runner still holds the last jobs: the wake
150 * event keeps kevent() returning at once, so back off instead of
151 * spinning until that runner takes them. */
152 if (ran == 0 && n > 0 && !atomic_load_explicit(&q->running, memory_order_acquire)) {
153 struct timespec ts = {0, 1000000}; /* 1ms */
154 nanosleep(&ts, NULL);
155 }
156 }
157}
158
168void cwist_io_queue_stop(cwist_io_queue *q) {
169 if (!q) return;
170 atomic_store_explicit(&q->running, false, memory_order_release);
171 struct kevent kev;
173 if (kevent(q->kq_fd, &kev, 1, NULL, 0, NULL) < 0) {
174 perror("kevent stop");
175 }
176}
177
185void cwist_io_queue_destroy(cwist_io_queue *q) {
186 if (!q) return;
187 atomic_store_explicit(&q->running, false, memory_order_release);
188
189 /* Drop the level-triggered wake first, or the drain below never ends. */
190 struct kevent kev;
192 (void)kevent(q->kq_fd, &kev, 1, NULL, 0, NULL);
193
194 struct kevent events[32];
195 const struct timespec zero = {0, 0};
196 int n;
197 while ((n = kevent(q->kq_fd, NULL, 0, events, 32, &zero)) > 0) {
198 for (int i = 0; i < n; i++) {
199 if (events[i].filter == EVFILT_USER && events[i].udata) {
200 cwist_free((void *)events[i].udata);
201 }
202 }
203 }
204 close(q->kq_fd);
205 cwist_free(q);
206}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
Platform Agnostic Async Job Queue Interface.
void(* cwist_job_func)(void *arg)
Job function signature.
Definition cwist_io.h:22
struct cwist_io_queue cwist_io_queue
Opaque handle for the IO Queue. Backend implementation depends on OS (io_uring, kqueue,...
Definition cwist_io.h:16
Epoch-based garbage collection wrapper.
bool cwist_gc_scope_disown(void *ptr)
Remove ptr from the current thread's pending-sweep list without freeing it, because ownership is bein...
Definition gc.c:766
bool cwist_full_gc_enabled(void)
Check whether full-GC mode is currently enabled.
Definition gc.c:515
void cwist_gc_scope_flush(void)
Retire every block still on the current thread's pending-sweep list right now, instead of waiting for...
Definition gc.c:776
#define KQ_WAKE_IDENT
Definition kqueue.c:22
void cwist_io_queue_run(cwist_io_queue *q)
Process queued jobs until shutdown is requested and no work remains.
Definition kqueue.c:119
void cwist_io_queue_stop(cwist_io_queue *q)
Request that runners stop without freeing the queue.
Definition kqueue.c:168
void cwist_io_queue_destroy(cwist_io_queue *q)
Free jobs nobody ran, close the kqueue descriptor, free the queue.
Definition kqueue.c:185
cwist_io_queue * cwist_io_queue_create(size_t capacity)
Allocate a kqueue-backed IO queue wrapper.
Definition kqueue.c:36
bool cwist_io_queue_submit(cwist_io_queue *q, cwist_job_func func, void *arg)
Enqueue one callback by triggering an EVFILT_USER event.
Definition kqueue.c:80
size_t cap
Definition otel.c:56