cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
io_select.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
4#include <pthread.h>
5#include <stdatomic.h>
6#include <stdbool.h>
7
20typedef struct job_node {
21 cwist_job_func func;
22 void *arg;
23 _Atomic(struct job_node *) next;
25
26struct cwist_io_queue {
27 job_node_t *head; /* guarded by head_lock */
28 job_node_t *tail; /* guarded by tail_lock */
29 pthread_mutex_t head_lock;
30 pthread_mutex_t tail_lock;
31 atomic_size_t pending_jobs;
32 atomic_bool running;
33 pthread_mutex_t sleep_lock;
34 pthread_cond_t sleep_cond;
35};
36
43static job_node_t *cwist_job_node_create(cwist_job_func func, void *arg) {
44 job_node_t *node = cwist_alloc(sizeof(*node));
45 if (!node) return NULL;
46 node->func = func;
47 node->arg = arg;
49 return node;
50}
51
56static void cwist_job_node_destroy(job_node_t *node) {
57 if (!node) return;
58 cwist_free(node);
59}
60
66static void cwist_queue_push(cwist_io_queue *q, job_node_t *node) {
68 pthread_mutex_lock(&q->tail_lock);
69 /* next is atomic because a consumer may read the sentinel's next under
70 * head_lock while it is also the tail being linked here. */
71 atomic_store_explicit(&q->tail->next, node, memory_order_release);
72 q->tail = node;
73 pthread_mutex_unlock(&q->tail_lock);
74
75 size_t prev = atomic_fetch_add_explicit(&q->pending_jobs, 1, memory_order_release);
76 if (prev == 0) {
77 pthread_mutex_lock(&q->sleep_lock);
78 pthread_cond_signal(&q->sleep_cond);
79 pthread_mutex_unlock(&q->sleep_lock);
80 }
81}
82
93static bool cwist_queue_pop(cwist_io_queue *q, cwist_job_func *func, void **arg) {
94 pthread_mutex_lock(&q->head_lock);
95 job_node_t *head = q->head;
97 if (!next) {
98 pthread_mutex_unlock(&q->head_lock);
99 return false;
100 }
101 *func = next->func;
102 *arg = next->arg;
103 q->head = next;
104 pthread_mutex_unlock(&q->head_lock);
105 /* head was the sentinel and is no longer the tail (it has a next), so
106 * no producer or consumer can reach it any more. */
107 cwist_job_node_destroy(head);
108 return true;
109}
110
116static bool cwist_queue_wait(cwist_io_queue *q) {
117 pthread_mutex_lock(&q->sleep_lock);
118 while (atomic_load_explicit(&q->pending_jobs, memory_order_acquire) == 0 &&
120 pthread_cond_wait(&q->sleep_cond, &q->sleep_lock);
121 }
123 atomic_load_explicit(&q->pending_jobs, memory_order_acquire) > 0;
124 pthread_mutex_unlock(&q->sleep_lock);
125 return should_continue;
126}
127
134 (void)capacity;
135 cwist_io_queue *q = cwist_alloc(sizeof(*q));
136 if (!q) return NULL;
137
138 job_node_t *stub = cwist_job_node_create(NULL, NULL);
139 if (!stub) {
140 cwist_free(q);
141 return NULL;
142 }
143
144 q->head = stub;
145 q->tail = stub;
146 atomic_init(&q->pending_jobs, 0);
148
149 if (pthread_mutex_init(&q->head_lock, NULL) != 0) goto fail_head;
150 if (pthread_mutex_init(&q->tail_lock, NULL) != 0) goto fail_tail;
151 if (pthread_mutex_init(&q->sleep_lock, NULL) != 0) goto fail_sleep;
152 if (pthread_cond_init(&q->sleep_cond, NULL) != 0) goto fail_cond;
153 return q;
154
156 pthread_mutex_destroy(&q->sleep_lock);
158 pthread_mutex_destroy(&q->tail_lock);
160 pthread_mutex_destroy(&q->head_lock);
162 cwist_job_node_destroy(stub);
163 cwist_free(q);
164 return NULL;
165}
166
174bool cwist_io_queue_submit(cwist_io_queue *q, cwist_job_func func, void *arg) {
175 if (!q || !func) return false;
176 if (!atomic_load_explicit(&q->running, memory_order_acquire)) {
177 return false;
178 }
179
180 job_node_t *node = cwist_job_node_create(func, arg);
181 if (!node) return false;
182 cwist_queue_push(q, node);
183 return true;
184}
185
190void cwist_io_queue_run(cwist_io_queue *q) {
191 if (!q) return;
192 while (atomic_load_explicit(&q->running, memory_order_acquire) ||
193 atomic_load_explicit(&q->pending_jobs, memory_order_acquire) > 0) {
194 cwist_job_func func = NULL;
195 void *arg = NULL;
196 if (!cwist_queue_pop(q, &func, &arg)) {
197 if (!cwist_queue_wait(q)) {
198 break;
199 }
200 continue;
201 }
202
204 if (func) func(arg);
205 }
206}
207
212void cwist_io_queue_stop(cwist_io_queue *q) {
213 if (!q) return;
214 atomic_store_explicit(&q->running, false, memory_order_release);
215 pthread_mutex_lock(&q->sleep_lock);
216 pthread_cond_broadcast(&q->sleep_cond);
217 pthread_mutex_unlock(&q->sleep_lock);
218}
219
224void cwist_io_queue_destroy(cwist_io_queue *q) {
225 if (!q) return;
226 atomic_store_explicit(&q->running, false, memory_order_release);
227 pthread_mutex_lock(&q->sleep_lock);
228 pthread_cond_broadcast(&q->sleep_cond);
229 pthread_mutex_unlock(&q->sleep_lock);
230
231 /* Runners are gone by contract; the locks only pair with the last pops. */
232 pthread_mutex_lock(&q->head_lock);
233 job_node_t *node = q->head;
234 pthread_mutex_unlock(&q->head_lock);
235 while (node) {
237 cwist_job_node_destroy(node);
238 node = next;
239 }
240
241 pthread_cond_destroy(&q->sleep_cond);
242 pthread_mutex_destroy(&q->sleep_lock);
243 pthread_mutex_destroy(&q->tail_lock);
244 pthread_mutex_destroy(&q->head_lock);
245 cwist_free(q);
246}
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
struct job_node job_node_t
struct job_node job_node_t
void cwist_io_queue_run(cwist_io_queue *q)
Drain queued jobs until shutdown is requested and no work remains.
Definition io_select.c:190
void cwist_io_queue_stop(cwist_io_queue *q)
Request that runners stop without freeing the queue.
Definition io_select.c:212
void cwist_io_queue_destroy(cwist_io_queue *q)
Stop the queue, wake sleepers, and free every remaining node.
Definition io_select.c:224
cwist_io_queue * cwist_io_queue_create(size_t capacity)
Allocate a queue instance and install its sentinel node.
Definition io_select.c:133
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_select.c:174
size_t head
Definition otel.c:57
size_t cap
Definition otel.c:56