cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
io_queue.c
Go to the documentation of this file.
3#include <cwist/core/mem/gc.h>
4#include <ttak/mem/epoch.h>
5#include <pthread.h>
6#include <stdatomic.h>
7#include <stdbool.h>
8
30typedef struct job_node {
31 cwist_job_func func;
32 void *arg;
33 struct job_node *_Atomic next;
34 struct job_node *retire_next;
36
37struct cwist_io_queue {
38 job_node_t *_Atomic head;
39 job_node_t *_Atomic tail;
40 atomic_size_t pending_jobs;
41 atomic_bool running;
42 pthread_mutex_t sleep_lock;
43 pthread_cond_t sleep_cond;
44 /* Deferred reclamation staging (consumers only, see file banner). */
45 pthread_mutex_t retire_lock;
46 job_node_t *retire_new;
47 job_node_t *retire_old;
48 unsigned int last_epoch;
49};
50
57static job_node_t *cwist_job_node_create(cwist_job_func func, void *arg) {
58 job_node_t *node = cwist_alloc(sizeof(*node));
59 if (!node) return NULL;
60 /* Sentinels and submitted nodes belong to the queue's retirement /
61 * destroy lifecycle, even after their allocating thread exits.
62 * The callback argument remains opaque and caller-owned. */
64 node->func = func;
65 node->arg = arg;
67 return node;
68}
69
74static void cwist_job_node_destroy(job_node_t *node) {
75 if (!node) return;
76 cwist_free(node);
77}
78
85static void cwist_queue_defer_free(cwist_io_queue *q, job_node_t *node) {
86 pthread_mutex_lock(&q->retire_lock);
87 node->retire_next = q->retire_new;
88 q->retire_new = node;
89 pthread_mutex_unlock(&q->retire_lock);
90}
91
97static void cwist_job_node_free_cb(void *ptr) {
98 cwist_free(ptr);
99}
100
107static void cwist_queue_reclaim(cwist_io_queue *q) {
109 unsigned int epoch = atomic_load_explicit(&g_epoch_mgr.global_epoch, memory_order_acquire);
110
111 pthread_mutex_lock(&q->retire_lock);
112 if (epoch != q->last_epoch) {
113 q->last_epoch = epoch;
114 job_node_t *old = q->retire_old;
115 q->retire_old = q->retire_new;
116 q->retire_new = NULL;
117 pthread_mutex_unlock(&q->retire_lock);
118
119 while (old) {
120 job_node_t *next = old->retire_next;
121 ttak_epoch_retire(old, cwist_job_node_free_cb);
122 old = next;
123 }
124 } else {
125 pthread_mutex_unlock(&q->retire_lock);
126 }
127}
128
134static void cwist_queue_push(cwist_io_queue *q, job_node_t *node) {
137 while (1) {
140 if (tail == atomic_load_explicit(&q->tail, memory_order_acquire)) {
141 if (!next) {
143 &tail->next, &next, node, memory_order_release, memory_order_relaxed)) {
145 &q->tail, &tail, node, memory_order_release, memory_order_relaxed);
146 break;
147 }
148 } else {
151 }
152 }
153 }
155
156 size_t prev = atomic_fetch_add_explicit(&q->pending_jobs, 1, memory_order_release);
157 if (prev == 0) {
158 pthread_mutex_lock(&q->sleep_lock);
159 pthread_cond_signal(&q->sleep_cond);
160 pthread_mutex_unlock(&q->sleep_lock);
161 }
162}
163
175static bool cwist_queue_pop(cwist_io_queue *q, cwist_job_func *func, void **arg) {
176 bool popped = false;
178 while (1) {
182 if (!next) {
183 break;
184 }
185 if (head == tail) {
188 continue;
189 }
192 /* head is now unreachable from the queue, but a preempted
193 * producer may still hold it; defer the free across an epoch
194 * boundary instead of releasing it here. */
195 cwist_queue_defer_free(q, head);
196 *func = next->func;
197 *arg = next->arg;
198 popped = true;
199 break;
200 }
201 }
203 return popped;
204}
205
211static bool cwist_queue_wait(cwist_io_queue *q) {
212 pthread_mutex_lock(&q->sleep_lock);
213 while (atomic_load_explicit(&q->pending_jobs, memory_order_acquire) == 0 &&
215 pthread_cond_wait(&q->sleep_cond, &q->sleep_lock);
216 }
218 atomic_load_explicit(&q->pending_jobs, memory_order_acquire) > 0;
219 pthread_mutex_unlock(&q->sleep_lock);
220 return should_continue;
221}
222
229 (void)capacity;
230 cwist_io_queue *q = cwist_alloc(sizeof(*q));
231 if (!q) return NULL;
232 /* A returned queue can outlive its creator; destroy owns its shell. */
234
235 job_node_t *stub = cwist_job_node_create(NULL, NULL);
236 if (!stub) {
237 cwist_free(q);
238 return NULL;
239 }
240
243 atomic_init(&q->pending_jobs, 0);
245 q->retire_new = NULL;
246 q->retire_old = NULL;
247 q->last_epoch = atomic_load_explicit(&g_epoch_mgr.global_epoch, memory_order_acquire);
248
249 if (pthread_mutex_init(&q->retire_lock, NULL) != 0) {
250 cwist_job_node_destroy(stub);
251 cwist_free(q);
252 return NULL;
253 }
254 if (pthread_mutex_init(&q->sleep_lock, NULL) != 0) {
255 pthread_mutex_destroy(&q->retire_lock);
256 cwist_job_node_destroy(stub);
257 cwist_free(q);
258 return NULL;
259 }
260 if (pthread_cond_init(&q->sleep_cond, NULL) != 0) {
261 pthread_mutex_destroy(&q->sleep_lock);
262 pthread_mutex_destroy(&q->retire_lock);
263 cwist_job_node_destroy(stub);
264 cwist_free(q);
265 return NULL;
266 }
267 return q;
268}
269
277bool cwist_io_queue_submit(cwist_io_queue *q, cwist_job_func func, void *arg) {
278 if (!q || !func) return false;
279 if (!atomic_load_explicit(&q->running, memory_order_acquire)) {
280 return false;
281 }
282
283 job_node_t *node = cwist_job_node_create(func, arg);
284 if (!node) return false;
285 cwist_queue_push(q, node);
286 return true;
287}
288
293void cwist_io_queue_run(cwist_io_queue *q) {
294 if (!q) return;
295 unsigned int since_reclaim = 0;
296 while (atomic_load_explicit(&q->running, memory_order_acquire) ||
297 atomic_load_explicit(&q->pending_jobs, memory_order_acquire) > 0) {
298 cwist_job_func func = NULL;
299 void *arg = NULL;
300 if (!cwist_queue_pop(q, &func, &arg)) {
301 cwist_queue_reclaim(q);
302 since_reclaim = 0;
303 if (!cwist_queue_wait(q)) {
304 break;
305 }
306 continue;
307 }
308
310 if (func) func(arg);
311
312 // Full-GC mode: catch whatever this job forgot to cwist_free()
313 // right away instead of letting it pile up for the lifetime of this
314 // (long-lived, pooled) worker thread. The existing reclaim cadence
315 // below actually frees it.
316 if (cwist_full_gc_enabled()) {
318 }
319
320 if (++since_reclaim >= 64) {
321 since_reclaim = 0;
322 cwist_queue_reclaim(q);
323 }
324 }
325}
326
331void cwist_io_queue_stop(cwist_io_queue *q) {
332 if (!q) return;
333 atomic_store_explicit(&q->running, false, memory_order_release);
334 pthread_mutex_lock(&q->sleep_lock);
335 pthread_cond_broadcast(&q->sleep_cond);
336 pthread_mutex_unlock(&q->sleep_lock);
337}
338
343void cwist_io_queue_destroy(cwist_io_queue *q) {
344 if (!q) return;
345 atomic_store_explicit(&q->running, false, memory_order_release);
346 pthread_mutex_lock(&q->sleep_lock);
347 pthread_cond_broadcast(&q->sleep_cond);
348 pthread_mutex_unlock(&q->sleep_lock);
349
351 while (node) {
353 cwist_job_node_destroy(node);
354 node = next;
355 }
356
357 /* All runners are gone by contract, so staged nodes can be freed
358 * directly without an epoch grace period. */
359 for (int stage = 0; stage < 2; stage++) {
360 job_node_t *list = stage == 0 ? q->retire_new : q->retire_old;
361 while (list) {
362 job_node_t *next = list->retire_next;
363 cwist_job_node_destroy(list);
364 list = next;
365 }
366 }
367
368 pthread_cond_destroy(&q->sleep_cond);
369 pthread_mutex_destroy(&q->sleep_lock);
370 pthread_mutex_destroy(&q->retire_lock);
371 cwist_free(q);
372}
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
struct job_node job_node_t
void cwist_io_queue_run(cwist_io_queue *q)
Process queued jobs until shutdown is requested and no work remains.
Definition io_queue.c:293
void cwist_io_queue_stop(cwist_io_queue *q)
Request that runners stop without freeing the queue.
Definition io_queue.c:331
void cwist_io_queue_destroy(cwist_io_queue *q)
Stop the queue, wake sleepers, and free all remaining nodes.
Definition io_queue.c:343
cwist_io_queue * cwist_io_queue_create(size_t capacity)
Allocate the queue object and install its sentinel node.
Definition io_queue.c:228
bool cwist_io_queue_submit(cwist_io_queue *q, cwist_job_func func, void *arg)
Submit a callback for asynchronous execution by the queue runner.
Definition io_queue.c:277
size_t head
Definition otel.c:57
size_t cap
Definition otel.c:56