cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
log_async.c
Go to the documentation of this file.
1
18#define _POSIX_C_SOURCE 200809L
19#include "log_async.h"
22#include <errno.h>
23#include <pthread.h>
24#include <stdbool.h>
25#include <stdlib.h>
26#include <string.h>
27#include <unistd.h>
28#include <cwist/sys/platform.h>
29
30#if defined(__wasm__) || defined(CWIST_BAREMETAL)
31/* WASM builds have no fork() and, without the threads proposal, no
32 * background thread (bare metal has neither): async logging never starts
33 * and every line is written synchronously. */
36 return false;
37}
38void cwist_log_async_flush(void) {}
39void cwist_log_async_stop(void) {}
40bool cwist_log_async_active(void) {
41 return false;
42}
43bool cwist_log_async_submit(int fd, const char *line, size_t len) {
44 (void)fd;
45 (void)line;
46 (void)len;
47 return false;
48}
49#else
50
51#define LOG_ASYNC_DEFAULT_BYTES ((size_t)256 * 1024)
52#define LOG_ASYNC_STREAMS 2 /* [0] = stdout, [1] = stderr */
53
54typedef struct {
55 char *buf;
56 size_t cap;
57 size_t head; /* total bytes queued; guarded by g_lock */
58 size_t tail; /* total bytes written; guarded by g_lock */
59} log_ring_t;
60
62static pthread_cond_t g_work = PTHREAD_COND_INITIALIZER; /* writer waits for bytes */
63static pthread_cond_t g_space = PTHREAD_COND_INITIALIZER; /* callers wait for space or a drain */
64static log_ring_t g_rings[LOG_ASYNC_STREAMS];
65static bool g_active; /* accepting lines */
66static bool g_stopping; /* writer drains, then exits */
67static bool g_writer_alive; /* g_writer is a live thread in this process */
68static pthread_t g_writer;
69static bool g_hooks_installed;
70
72static log_ring_t *ring_for_fd(int fd) {
73 if (fd == STDOUT_FILENO) return &g_rings[0];
74 if (fd == STDERR_FILENO) return &g_rings[1];
75 return NULL;
76}
77
79static bool rings_empty(void) {
80 for (int i = 0; i < LOG_ASYNC_STREAMS; i++) {
81 if (g_rings[i].head != g_rings[i].tail) return false;
82 }
83 return true;
84}
85
88static void write_all(int fd, const char *p, size_t len) {
89 while (len > 0) {
90 ssize_t n = write(fd, p, len);
91 if (n < 0) {
92 if (errno == EINTR) continue;
93 return;
94 }
95 p += n;
96 len -= (size_t)n;
97 }
98}
99
103static void *log_writer_main(void *arg) {
104 (void)arg;
105 pthread_mutex_lock(&g_lock);
106 for (;;) {
107 while (rings_empty() && !g_stopping) pthread_cond_wait(&g_work, &g_lock);
108 if (rings_empty()) break; /* stopping and drained */
109
110 for (int i = 0; i < LOG_ASYNC_STREAMS; i++) {
111 log_ring_t *r = &g_rings[i];
112 while (r->head != r->tail) {
113 size_t off = r->tail % r->cap;
114 size_t len = r->head - r->tail;
115 if (len > r->cap - off) len = r->cap - off; /* up to the wrap */
116 const char *p = r->buf + off;
117 pthread_mutex_unlock(&g_lock);
118 write_all(i == 0 ? STDOUT_FILENO : STDERR_FILENO, p, len);
119 pthread_mutex_lock(&g_lock);
120 r->tail += len;
121 pthread_cond_broadcast(&g_space);
122 }
123 }
124 }
125 pthread_mutex_unlock(&g_lock);
126 return NULL;
127}
128
131static bool spawn_writer(void) {
132 if (g_writer_alive) return true;
133 if (pthread_create(&g_writer, NULL, log_writer_main, NULL) != 0) return false;
134 g_writer_alive = true;
135 return true;
136}
137
139static void atfork_prepare(void) {
140 pthread_mutex_lock(&g_lock);
141}
142
144static void atfork_parent(void) {
145 pthread_mutex_unlock(&g_lock);
146}
147
151static void atfork_child(void) {
152 for (int i = 0; i < LOG_ASYNC_STREAMS; i++) g_rings[i].tail = g_rings[i].head;
153 g_writer_alive = false;
154 /* Waiters of the parent do not exist here; start the conditions clean. */
156 g_work = fresh;
157 g_space = fresh;
158 pthread_mutex_unlock(&g_lock);
159}
160
162static void atexit_stop(void) {
164}
165
178
179 pthread_mutex_lock(&g_lock);
180 if (g_active) {
181 pthread_mutex_unlock(&g_lock);
182 return true;
183 }
184 if (!g_hooks_installed) {
185 if (pthread_atfork(atfork_prepare, atfork_parent, atfork_child) != 0 ||
186 atexit(atexit_stop) != 0) {
187 pthread_mutex_unlock(&g_lock);
188 return false;
189 }
190 g_hooks_installed = true;
191 }
192 for (int i = 0; i < LOG_ASYNC_STREAMS; i++) {
193 log_ring_t *r = &g_rings[i];
194 r->buf = cwist_alloc(buffer_bytes);
195 if (!r->buf) {
196 for (int j = 0; j < i; j++) {
197 cwist_free(g_rings[j].buf);
198 g_rings[j].buf = NULL;
199 }
200 pthread_mutex_unlock(&g_lock);
201 return false;
202 }
203 r->cap = buffer_bytes;
204 r->head = r->tail = 0;
205 }
206 g_stopping = false;
207 if (!spawn_writer()) {
208 for (int i = 0; i < LOG_ASYNC_STREAMS; i++) {
209 cwist_free(g_rings[i].buf);
210 g_rings[i].buf = NULL;
211 }
212 pthread_mutex_unlock(&g_lock);
213 return false;
214 }
215 g_active = true;
216 pthread_mutex_unlock(&g_lock);
217 return true;
218}
219
222 pthread_mutex_lock(&g_lock);
223 while (g_active && g_writer_alive && !rings_empty()) pthread_cond_wait(&g_space, &g_lock);
224 pthread_mutex_unlock(&g_lock);
225}
226
230 pthread_mutex_lock(&g_lock);
231 if (!g_active) {
232 pthread_mutex_unlock(&g_lock);
233 return;
234 }
235 g_active = false; /* new lines go synchronous from here on */
236 g_stopping = true;
237 bool join = g_writer_alive;
238 pthread_cond_broadcast(&g_work);
239 pthread_cond_broadcast(&g_space); /* callers waiting for space fall back */
240 pthread_mutex_unlock(&g_lock);
241
242 if (join) pthread_join(g_writer, NULL);
243
244 pthread_mutex_lock(&g_lock);
245 g_writer_alive = false;
246 for (int i = 0; i < LOG_ASYNC_STREAMS; i++) {
247 cwist_free(g_rings[i].buf);
248 g_rings[i].buf = NULL;
249 g_rings[i].cap = g_rings[i].head = g_rings[i].tail = 0;
250 }
251 pthread_mutex_unlock(&g_lock);
252}
253
257 pthread_mutex_lock(&g_lock);
258 bool active = g_active;
259 pthread_mutex_unlock(&g_lock);
260 return active;
261}
262
263bool cwist_log_async_submit(int fd, const char *line, size_t len) {
264 if (len == 0) return true;
265 pthread_mutex_lock(&g_lock);
266 log_ring_t *r = ring_for_fd(fd);
267 if (!g_active || !r || len > r->cap) {
268 pthread_mutex_unlock(&g_lock);
269 return false;
270 }
271 if (!g_writer_alive && !spawn_writer()) { /* first line in a forked child */
272 pthread_mutex_unlock(&g_lock);
273 return false;
274 }
275 while (g_active && r->cap - (r->head - r->tail) < len) pthread_cond_wait(&g_space, &g_lock);
276 if (!g_active) { /* stopped while waiting */
277 pthread_mutex_unlock(&g_lock);
278 return false;
279 }
280
281 size_t off = r->head % r->cap;
282 size_t first = r->cap - off;
283 if (first > len) first = len;
284 memcpy(r->buf + off, line, first);
285 memcpy(r->buf, line + first, len - first);
286 bool was_empty = rings_empty();
287 r->head += len;
288 if (was_empty) pthread_cond_signal(&g_work);
289 pthread_mutex_unlock(&g_lock);
290 return true;
291}
292
293#endif /* !__wasm__ */
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
#define LOG_ASYNC_STREAMS
Definition log_async.c:52
bool cwist_log_async_submit(int fd, const char *line, size_t len)
Queue one complete line for fd (STDOUT_FILENO or STDERR_FILENO).
Definition log_async.c:263
void cwist_log_async_flush(void)
Block until every line queued so far has been written. No-op when inactive.
Definition log_async.c:221
bool cwist_log_async_start(size_t buffer_bytes)
Start writing log lines from a background thread.
Definition log_async.c:176
void cwist_log_async_stop(void)
Write out every queued line, stop the writer and go back to synchronous logging. Safe to call when as...
Definition log_async.c:229
#define LOG_ASYNC_DEFAULT_BYTES
Definition log_async.c:51
bool cwist_log_async_active(void)
Report whether async logging is active.
Definition log_async.c:256
Internal hook from the log call sites into the async log writer.
Application-level structured logging.
size_t head
Definition otel.c:57
size_t cap
Definition otel.c:56
Target capability macros shared by all CWIST sources.