cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
async.c
Go to the documentation of this file.
1
17#define _POSIX_C_SOURCE 200809L
21#include <cwist/sys/app/app.h>
25#include <cwist/core/mem/gc.h>
26#include "async_gc.h"
27#include "async_internal.h"
28#include <pthread.h>
29#include <stdatomic.h>
30#include <fcntl.h>
31#include <sched.h>
32#include <string.h>
33#include <cwist/sys/platform.h>
34#ifndef CWIST_BAREMETAL
35#include <sys/socket.h>
36#endif
37#include <unistd.h>
38
40
41struct cwist_async {
42 _Atomic size_t refs; /* Completion plus retained producers/timers. */
43 _Atomic int state;
44 _Atomic bool ack; /* Dispatch path observed the handoff. */
45 pthread_t dispatch_thread;
46 /* Serializes a producer's mutation of the exchange with the dispatch
47 * ack: before the ack, dispatch still reads req/res (GC disown,
48 * keep-alive), so a producer that completes first must not touch them
49 * concurrently, and the "who sends" decision below must be atomic.
50 * Held only for short, non-blocking sections; nobody waits for the
51 * other side while holding it. */
53 bool finish_on_ack; /* A completion arrived before the ack; ack sends it. Guarded by mu. */
55 cwist_http_response *res; /* Handler's response (request arena). */
56 cwist_http_response *final_res;
57 bool final_res_owned; /* final_res came from respond_with. */
58 int client_fd;
59 bool keep_alive;
60 struct cwist_app *app;
61 cwist_reactor_t *reactor; /* NULL on the classic pool path. */
62 uint64_t reactor_gen; /* Pool generation @c reactor belongs to. */
63 bool orphaned; /* @c reactor was destroyed before completion. */
65 void *https_conn; /* cwist_https_connection * on TLS path. */
66 cwist_h2_async_queue *h2_queue; /* Per-connection queue on the H2 path. */
67 uint32_t h2_stream_id;
69};
70
71static void cwist_async_reactor_complete(void *ctx);
72static void cwist_async_deliver(cwist_async *a);
73static bool cwist_async_publish_locked(cwist_async *a);
74
75static const char *cwist_async_reason(cwist_http_status_t status) {
76 switch (status) {
77 case CWIST_HTTP_OK: return "OK";
78 case CWIST_HTTP_CREATED: return "Created";
79 case CWIST_HTTP_NO_CONTENT: return "No Content";
80 case CWIST_HTTP_PARTIAL_CONTENT: return "Partial Content";
81 case CWIST_HTTP_NOT_MODIFIED: return "Not Modified";
82 case CWIST_HTTP_BAD_REQUEST: return "Bad Request";
83 case CWIST_HTTP_UNAUTHORIZED: return "Unauthorized";
84 case CWIST_HTTP_FORBIDDEN: return "Forbidden";
85 case CWIST_HTTP_NOT_FOUND: return "Not Found";
86 case CWIST_HTTP_RANGE_NOT_SATISFIABLE: return "Range Not Satisfiable";
87 case CWIST_HTTP_INTERNAL_ERROR: return "Internal Server Error";
88 case CWIST_HTTP_NOT_IMPLEMENTED: return "Not Implemented";
89 case CWIST_HTTP_SERVICE_UNAVAILABLE: return "Service Unavailable";
90 case CWIST_HTTP_GATEWAY_TIMEOUT: return "Gateway Timeout";
91 default: return "Status";
92 }
93}
94
95static __thread bool t_https_deferred = false;
96
98 bool deferred = t_https_deferred;
99 t_https_deferred = false;
100 return deferred;
101}
102
104 if (!req || !res || res->deferred) return NULL;
105 cwist_async *a = cwist_alloc(sizeof(*a));
106 if (!a) return NULL;
107 memset(a, 0, sizeof(*a));
108 atomic_init(&a->refs, 1);
110 atomic_init(&a->ack, false);
111 if (pthread_mutex_init(&a->mu, NULL) != 0) {
112 cwist_free(a);
113 return NULL;
114 }
115 a->dispatch_thread = pthread_self();
116 a->req = req;
117 a->res = res;
118 a->final_res = res;
119 a->client_fd = req->client_fd;
120 a->keep_alive = req->keep_alive && res->keep_alive;
121 a->app = req->app;
123 if (conn) {
124 a->reactor = conn->reactor;
125 a->reactor_gen = cwist_http_reactor_generation();
126 a->conn = conn;
127 }
128 if (req->https_conn) {
129 a->https_conn = req->https_conn;
130 ((cwist_https_connection *)req->https_conn)->deferred = true;
131 /* Only an HTTP/1.1 completion takes the conn itself; an h2 stream's
132 * completion goes through h2_queue and the h2 loop keeps the conn. */
133 if (!req->h2_queue) t_https_deferred = true;
134 }
135 if (req->h2_queue) {
136 /* Keep the queue alive until the completion has been enqueued, even
137 * if the connection tears down first (teardown closes the queue and
138 * releases only its own reference). */
140 a->h2_stream_id = req->stream_id;
141 }
142 a->post.cb = cwist_async_reactor_complete;
143 a->post.ctx = a;
144 /* Explicit async refs, not the creator's TLS GC sweep, own this handle. */
146 cwist_http_async_disown_request(req);
147 cwist_http_async_disown_response(res);
148 res->async = a;
149 res->deferred = true;
151 return a;
152}
153
156 return a;
157}
158
159void cwist_async_release(cwist_async *a) {
160 if (a && atomic_fetch_sub_explicit(&a->refs, 1, memory_order_acq_rel) == 1) {
163 cwist_free(a);
164 }
165}
166
167void cwist_async_dispatch_ack(cwist_async *a) {
168 if (!a) return;
170 /* Middleware may allocate after next() returns. No producer may mutate
171 * or destroy this graph until the acknowledgement; a producer that
172 * already completed waits on mu and left its result to send here. */
173 pthread_mutex_lock(&a->mu);
174 cwist_http_async_disown_request(a->req);
175 cwist_http_async_disown_response(a->res);
176 a->keep_alive = a->keep_alive && a->req->keep_alive && a->res->keep_alive;
177 bool finish = a->finish_on_ack;
180 /* Without a recorded completion a foreign producer may free a from
181 * here on; with one, this thread owns sending it. */
182 if (finish) cwist_async_deliver(a);
183}
184
185void cwist_async_wait_dispatched(cwist_async *a) {
186 if (!a || pthread_equal(a->dispatch_thread, pthread_self())) return;
188}
189
190static bool cwist_async_claim(cwist_async *a) {
194}
195
196static void cwist_async_reactor_complete(void *ctx);
197
198/* Send the final response, re-arm or close the connection, then release the
199 * request/response pair and the handle itself. */
200static void cwist_async_complete(cwist_async *a) {
201 cwist_http_response *res = a->final_res;
203 res->keep_alive = keep;
204
205 if (a->h2_queue) {
206 /* Plan B handoff: never write frames from a worker thread. Enqueue
207 * the finished exchange and poke the connection's wake fd; the
208 * connection thread drains the queue, HPACK-encodes, and sends.
209 * Wait for the dispatch ack first: enqueueing transfers req/res
210 * ownership, which is only legal once dispatch observed the defer. */
212 cwist_h2_async_queue_enqueue(a->h2_queue, a->h2_stream_id, a->req, a->final_res, a->res,
213 a->final_res_owned);
216 return;
217 }
218
219 if (a->https_conn) {
220 cwist_https_connection *conn = (cwist_https_connection *)a->https_conn;
221 cwist_error_t err = (a->req && a->req->method == CWIST_HTTP_HEAD)
223 : cwist_https_send_response(conn, res);
224 bool ok = cwist_error_is_ok(&err);
225
226 if (keep && ok && a->app && a->app->ssl_ctx && a->app->https_request_handler) {
227 conn->deferred = false;
228 https_pool_submit_conn(conn, a->app->ssl_ctx, a->app->https_request_handler, a->app);
229 } else {
231 }
232 } else if (a->orphaned) {
233 /* The server stopped and destroyed the worker reactor this
234 * connection was parked on: nothing can send on it or re-arm it any
235 * more, so close it. */
236 cwist_http_async_close_orphan(a->client_fd, a->conn);
237 } else if (a->reactor) {
238 /* Resumable write: on a partial send the remainder is parked on a
239 * POLLOUT slot and the reactor thread is freed immediately; the
240 * parked callback performs the rearm/close when the drain ends. */
241 cwist_http_async_send_response(a->client_fd, res, a->reactor, a->conn, keep,
242 a->req && a->req->method == CWIST_HTTP_HEAD);
243 } else {
244 cwist_error_t err = (a->req && a->req->method == CWIST_HTTP_HEAD)
245 ? cwist_http_send_response_head(a->client_fd, res)
246 : cwist_http_send_response(a->client_fd, res);
247 bool ok = err.error.err_i16 == 0;
248
249 if (keep && ok && a->app) {
251 } else {
252 close(a->client_fd);
253 }
254 }
255
256 /* See the file header: never free before dispatch observed the defer. */
258
259 if (a->final_res_owned) cwist_http_response_destroy(res);
263}
264
265static void cwist_async_reactor_complete(void *ctx) {
266 cwist_async_complete((cwist_async *)ctx);
267}
268
269/* Producer side, called with mu held after the response is final: detach
270 * what the producer allocated from its own GC scope, then decide who sends.
271 * Returns true if the caller must deliver (after unlocking); false if the
272 * ack has not happened yet and will deliver. */
273static bool cwist_async_publish_locked(cwist_async *a) {
274 /* Runs on the producer, before a reactor/H2 consumer can see or free
275 * newly allocated body/header/string state (also timeout and abort). */
276 cwist_http_async_disown_response(a->final_res);
278 /* Dispatch has not unwound yet (or this is the creator completing
279 * inline): record the completion; the ack sends it. Needs no
280 * allocation, so an abort still works when scheduling failed. */
281 a->finish_on_ack = true;
282 return false;
283 }
284 return true;
285}
286
287/* Send the completed exchange: post it to the owning reactor, or finish
288 * inline on the classic/TLS/orphaned paths. Runs exactly once, on the
289 * producer or on the dispatch ack, never under mu. */
290static void cwist_async_deliver(cwist_async *a) {
291 if (a->reactor) {
292 if (cwist_http_reactor_post_live(a->reactor, a->reactor_gen, &a->post)) return;
293 /* The pool that owned the reactor has been destroyed (the server
294 * stopped while this exchange was pending): the reactor is freed, so
295 * close out inline without it. */
296 a->orphaned = true;
297 }
298 cwist_async_complete(a);
299}
300
301/* --- Timeout ------------------------------------------------------------- */
302
303static pthread_once_t g_timeout_once = PTHREAD_ONCE_INIT;
304static cwist_scheduler_t *g_timeout_sched;
305
306static void cwist_async_timeout_sched_init(void) {
307 g_timeout_sched = cwist_scheduler_create(1, 64);
308}
309
310static void cwist_async_timeout_job(void *arg) {
311 cwist_async *a = (cwist_async *)arg;
312 if (!cwist_async_claim(a)) {
313 cwist_async_release(a); /* A real response beat the timeout. */
314 return;
315 }
316 pthread_mutex_lock(&a->mu);
317 cwist_http_response *res = a->res;
319 cwist_sstring_assign(res->status_text, (char *)"Gateway Timeout");
320 cwist_sstring_assign(res->body, (char *)"Gateway Timeout");
321 cwist_http_header_add(&res->headers, "Content-Type", "text/plain");
322 bool deliver = cwist_async_publish_locked(a);
324 if (deliver) cwist_async_deliver(a);
326}
327
329 if (!a || ms == 0) return;
330 pthread_once(&g_timeout_once, cwist_async_timeout_sched_init);
331 if (!g_timeout_sched) return;
333 if (!cwist_scheduler_schedule(g_timeout_sched, cwist_async_timeout_job, a, ms)) {
335 }
336}
337
338/* --- Completion API ------------------------------------------------------ */
339
340bool cwist_async_respond(cwist_async *a, cwist_http_status_t status, const char *content_type,
341 const void *body, size_t len) {
342 if (!a || !cwist_async_claim(a)) return false;
343 pthread_mutex_lock(&a->mu);
344 cwist_http_response *res = a->res;
345 res->status_code = status;
346 cwist_sstring_assign(res->status_text, (char *)cwist_async_reason(status));
347 if (body && len > 0) {
348 cwist_sstring_assign_len(res->body, (const char *)body, len);
349 }
350 if (content_type) {
351 cwist_http_header_add(&res->headers, "Content-Type", content_type);
352 }
353 bool deliver = cwist_async_publish_locked(a);
355 if (deliver) cwist_async_deliver(a);
356 return true;
357}
358
360 if (!a || !res || !cwist_async_claim(a)) return false;
361 pthread_mutex_lock(&a->mu);
362 a->final_res = res;
363 a->final_res_owned = res != a->res;
364 bool deliver = cwist_async_publish_locked(a);
366 if (deliver) cwist_async_deliver(a);
367 return true;
368}
369
370bool cwist_async_abort(cwist_async *a, cwist_http_status_t status) {
371 if (!a || !cwist_async_claim(a)) return false;
372 pthread_mutex_lock(&a->mu);
373 cwist_http_response *res = a->res;
374 res->status_code = status;
375 const char *reason = cwist_async_reason(status);
377 cwist_sstring_assign(res->body, (char *)reason);
378 cwist_http_header_add(&res->headers, "Content-Type", "text/plain");
379 a->keep_alive = false;
380 bool deliver = cwist_async_publish_locked(a);
382 if (deliver) cwist_async_deliver(a);
383 return true;
384}
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
void cwist_async_dispatch_ack(cwist_async *a)
Internal: on the dispatch thread, after all middleware has unwound. Call exactly once; do not touch t...
Definition async.c:167
bool cwist_async_respond_with(cwist_async *a, cwist_http_response *res)
Complete the exchange with a caller-built response. On success the framework takes ownership of res (...
Definition async.c:359
cwist_async * cwist_async_retain(cwist_async *a)
Retain a live handle for a producer that may outlive completion. Must be called while the caller alre...
Definition async.c:154
bool cwist_async_https_take_deferred(void)
Definition async.c:97
bool cwist_async_respond(cwist_async *a, cwist_http_status_t status, const char *content_type, const void *body, size_t len)
Complete the exchange with a simple body response. Thread-safe and one-shot: the first of respond/res...
Definition async.c:340
void cwist_async_set_timeout(cwist_async *a, uint64_t ms)
Answer with a 504 Gateway Timeout if the exchange is still pending after ms milliseconds....
Definition async.c:328
bool cwist_async_abort(cwist_async *a, cwist_http_status_t status)
Complete the exchange with an error status and close the connection.
Definition async.c:370
void cwist_async_wait_dispatched(cwist_async *a)
Wait until dispatch has acknowledged the handoff of a.
Definition async.c:185
cwist_async * cwist_async_defer(cwist_http_request *req, cwist_http_response *res)
Defer the response for the current request.
Definition async.c:103
cwist_async_state
Definition async.c:39
@ CWIST_ASYNC_ST_CLAIMED
Definition async.c:39
@ CWIST_ASYNC_ST_PENDING
Definition async.c:39
void cwist_async_release(cwist_async *a)
Release a reference acquired with cwist_async_retain(). Does not complete or abort the exchange....
Definition async.c:159
Deferred-response (async handler) API for CWIST.
struct cwist_async cwist_async
Definition async.h:54
Private wiring between the deferred-response path (async.c) and the C1M connection engine (http....
void cwist_async_record_pending(struct cwist_async *a, const cwist_http_response *res)
Definition async_tls.c:38
uint64_t cwist_http_reactor_generation(void)
Return the current reactor generation counter.
Definition http.c:268
void cwist_http_async_close_orphan(int client_fd, cwist_http_async_conn_t *conn)
Close an async connection whose owning worker pool is gone.
Definition http.c:1453
void cwist_async_forget_handle(const struct cwist_async *a)
bool cwist_http_reactor_post_live(cwist_reactor_t *reactor, uint64_t gen, cwist_reactor_post_t *node)
Post a reactor node only while the captured generation is current.
Definition http.c:283
cwist_error_t cwist_sstring_assign_len(cwist_sstring *str, const char *data, size_t len)
Assign raw bytes with length.
Definition sstring.c:88
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
Definition sstring.c:504
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
HTTP/2 Protocol Definitions for CWIST.
cwist_h2_async_queue * cwist_h2_async_queue_acquire(cwist_h2_async_queue *q)
Take an additional reference on the queue (for an in-flight cwist_async handle).
Definition http2.c:316
int cwist_h2_async_queue_enqueue(cwist_h2_async_queue *q, uint32_t stream_id, cwist_http_request *req, cwist_http_response *send, cwist_http_response *res, bool send_owned)
Definition http2.c:339
void cwist_h2_async_queue_release(cwist_h2_async_queue *q)
Drop a reference on the queue. On the last reference: closes the wake descriptors,...
Definition http2.c:327
struct cwist_h2_async_queue cwist_h2_async_queue
Definition http2.h:123
cwist_error_t cwist_http_send_response(int client_fd, cwist_http_response *res)
Send a response over a socket.
Definition http.c:2878
cwist_error_t cwist_http_send_response_head(int client_fd, cwist_http_response *res)
Send only the status line and headers (HEAD replies).
Definition http.c:2961
cwist_http_status_t
Definition http.h:27
@ CWIST_HTTP_UNAUTHORIZED
Definition http.h:32
@ CWIST_HTTP_FORBIDDEN
Definition http.h:33
@ CWIST_HTTP_INTERNAL_ERROR
Definition http.h:35
@ CWIST_HTTP_CREATED
Definition http.h:29
@ CWIST_HTTP_NOT_IMPLEMENTED
Definition http.h:36
@ CWIST_HTTP_BAD_REQUEST
Definition http.h:31
@ CWIST_HTTP_NOT_FOUND
Definition http.h:34
@ CWIST_HTTP_OK
Definition http.h:28
@ CWIST_HTTP_NO_CONTENT
Definition http.h:30
void cwist_http_request_destroy(cwist_http_request *req)
Destroy an HTTP request object.
Definition http.c:2180
@ CWIST_HTTP_HEAD
Definition http.h:22
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
Definition http.c:1861
void cwist_http_response_destroy(cwist_http_response *res)
Destroy an HTTP response object.
Definition http.c:2315
cwist_error_t cwist_https_send_response(cwist_https_connection *conn, cwist_http_response *res)
Serialize an HTTP response and send it over an active TLS connection.
Definition https.c:2102
void cwist_https_close_connection(cwist_https_connection *conn)
Definition https.c:1875
bool cwist_http_pool_rearm_current(int client_fd, void(*handler)(int, void *), void *ctx)
Requeue the current connection's next handler turn on the pool.
Definition http.c:824
void cwist_http_async_send_response(int client_fd, cwist_http_response *res, cwist_reactor_t *reactor, cwist_http_async_conn_t *conn, bool keep_alive, bool head_only)
Send a response on the async path, parking the unsent remainder on EAGAIN instead of blocking the rea...
Definition http.c:3413
@ CWIST_HTTP_NOT_MODIFIED
Definition http.h:71
@ CWIST_HTTP_PARTIAL_CONTENT
Definition http.h:62
@ CWIST_HTTP_GATEWAY_TIMEOUT
Definition http.h:112
@ CWIST_HTTP_RANGE_NOT_SATISFIABLE
Definition http.h:93
@ CWIST_HTTP_SERVICE_UNAVAILABLE
Definition http.h:111
HTTPS/TLS wrapper interface.
void https_pool_submit_conn(cwist_https_connection *conn, cwist_https_context *ctx, void(*handler)(cwist_https_connection *, void *), void *user_ctx)
Submit an established connection back to the worker pool (e.g. keep-alive re-arm after async defer)....
Definition https.c:341
cwist_error_t cwist_https_send_response_head(cwist_https_connection *conn, cwist_http_response *res)
Definition https.c:2197
size_t cap
Definition otel.c:56
Target capability macros shared by all CWIST sources.
struct cwist_reactor cwist_reactor_t
Definition reactor.h:14
Background job scheduler / delayed task queue for CWIST.
struct cwist_scheduler cwist_scheduler_t
Opaque background job scheduler handle.
Definition scheduler.h:21
bool cwist_scheduler_schedule(cwist_scheduler_t *s, cwist_job_func func, void *arg, uint64_t delay_ms)
Schedule a job to run after a relative delay.
Definition scheduler.c:380
cwist_scheduler_t * cwist_scheduler_create(size_t worker_count, size_t queue_capacity)
Create a scheduler with a pool of background workers.
Definition scheduler.c:265
Unified Graceful Shutdown support for CWIST.
atomic_int g_cwist_running
Global running flag. Checked by all server event loops.
Definition shutdown.c:17
Main Application Context.
Definition app.h:155
Integrated error structure used throughout the system.
Definition cwist_err.h:72
cwist_reactor_t * reactor
Definition http.h:469
void * async_conn
C1M connection shell when on the reactor path (internal).
Definition http.h:188
void * h2_queue
HTTP/2 async queue when on H2 path (internal).
Definition http.h:191
struct cwist_app * app
Owning app context (if any).
Definition http.h:176
uint32_t stream_id
HTTP/2 or HTTP/3 stream ID (0 for HTTP/1.1).
Definition http.h:77
void * https_conn
HTTPS connection wrapper when on TLS path (internal).
Definition http.h:190
bool keep_alive
Definition http.h:74
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
Definition http.h:82
cwist_sstring * status_text
e.g., "OK"
Definition http.h:85
cwist_sstring * body
Definition http.h:87
cwist_http_status_t status_code
Definition http.h:84
cwist_http_header_node * headers
Definition http.h:86
void * async
Owning cwist_async while deferred (read-only).
Definition http.h:245
bool deferred
Deferred-response handoff (internal; see net/http/async.h)
Definition http.h:244
Definition reactor.h:46
Core Application Structure and Lifecycle Management.
void cwist_app_http_handler(int client_fd, void *ctx)
Definition app.c:3506