@velox0/cerver 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/runtime/server.c CHANGED
@@ -1,9 +1,12 @@
1
1
  /*
2
- * server.c — Socket setup, thread pool, and event loop for the cerver runtime.
2
+ * server.c — Hybrid event-loop + thread-pool server for the cerver runtime.
3
3
  *
4
- * Uses kqueue on macOS, epoll on Linux, with a select() fallback.
5
- * Requests are dispatched to a fixed-size thread pool via a ring-buffer
6
- * task queue protected by a mutex + condition variable.
4
+ * Architecture:
5
+ * - 1 acceptor thread per core with its own kqueue/epoll (accept only)
6
+ * - Shared connection thread pool (configurable size, default 128)
7
+ * - Acceptors never block — they push fds into a lock-free ring buffer
8
+ * - Pool workers handle full request lifecycle including keep-alive
9
+ * - On Linux: SO_REUSEPORT per acceptor; on macOS: shared listener
7
10
  */
8
11
 
9
12
  #include "cerver.h"
@@ -15,541 +18,795 @@
15
18
  #include <errno.h>
16
19
  #include <signal.h>
17
20
  #include <fcntl.h>
21
+ #include <time.h>
22
+ #include <sys/mman.h>
18
23
  #include <sys/socket.h>
19
24
  #include <sys/types.h>
20
25
  #include <netinet/in.h>
26
+ #include <netinet/tcp.h>
21
27
  #include <arpa/inet.h>
22
28
  #include <pthread.h>
23
29
 
24
- /* Platform-specific event API */
25
30
  #if defined(__APPLE__) || defined(__FreeBSD__)
26
- #define CERVER_USE_KQUEUE 1
27
- #include <sys/event.h>
31
+ #define CERVER_USE_KQUEUE 1
32
+ #include <sys/event.h>
28
33
  #elif defined(__linux__)
29
- #define CERVER_USE_EPOLL 1
30
- #include <sys/epoll.h>
34
+ #define CERVER_USE_EPOLL 1
35
+ #include <sys/epoll.h>
36
+ #include <sys/sendfile.h>
31
37
  #else
32
- #define CERVER_USE_SELECT 1
33
- #include <sys/select.h>
38
+ #define CERVER_USE_SELECT 1
39
+ #include <sys/select.h>
34
40
  #endif
35
41
 
36
- #define MAX_EVENTS 64
42
+ #ifdef __linux__
43
+ #include <sched.h>
44
+ #endif
45
+
46
+ /* Connection pool sizing */
47
+ #define CERVER_CONN_POOL_SIZE 128
48
+ #define CERVER_CONN_QUEUE_SIZE 4096
49
+
50
+ /* ------------------------------------------------------------------ */
51
+ /* memmem fallback */
52
+ /* ------------------------------------------------------------------ */
37
53
 
38
- /* Global server pointer for signal handler */
39
- static cerver_server_t *g_srv = NULL;
54
+ #if !defined(__APPLE__) && !defined(__linux__) && !defined(_GNU_SOURCE)
55
+ static void* cerver_memmem(const void* hay, size_t haylen, const void* needle, size_t nlen) {
56
+ if (nlen == 0) return (void*)hay;
57
+ if (nlen > haylen) return NULL;
58
+ const char* p = (const char*)hay;
59
+ const char* end = p + haylen - nlen;
60
+ for (; p <= end; p++) {
61
+ if (memcmp(p, needle, nlen) == 0) return (void*)p;
62
+ }
63
+ return NULL;
64
+ }
65
+ #define memmem cerver_memmem
66
+ #endif
67
+
68
+ /* Global for signal handler */
69
+ static cerver_server_t* g_srv = NULL;
40
70
 
41
71
  static void signal_handler(int sig) {
42
- (void)sig;
43
- if (g_srv) {
44
- g_srv->running = 0;
45
- }
72
+ (void)sig;
73
+ if (g_srv) g_srv->running = 0;
46
74
  }
47
75
 
48
76
  static int set_nonblocking(int fd) {
49
- int flags = fcntl(fd, F_GETFL, 0);
50
- if (flags < 0) return -1;
51
- return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
77
+ int flags = fcntl(fd, F_GETFL, 0);
78
+ if (flags < 0) return -1;
79
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
80
+ }
81
+
82
+ static int get_cpu_count(void) {
83
+ long n = sysconf(_SC_NPROCESSORS_ONLN);
84
+ if (n < 1) n = 1;
85
+ if (n > 64) n = 64;
86
+ return (int)n;
52
87
  }
53
88
 
54
89
  /* ------------------------------------------------------------------ */
55
- /* memmem fallback for systems that lack it */
90
+ /* Connection queue (shared between acceptors and pool workers) */
56
91
  /* ------------------------------------------------------------------ */
57
92
 
58
- #if !defined(__APPLE__) && !defined(__linux__) && !defined(_GNU_SOURCE)
59
- static void *cerver_memmem(const void *hay, size_t haylen,
60
- const void *needle, size_t nlen) {
61
- if (nlen == 0) return (void *)hay;
62
- if (nlen > haylen) return NULL;
63
- const char *p = (const char *)hay;
64
- const char *end = p + haylen - nlen;
65
- for (; p <= end; p++) {
66
- if (memcmp(p, needle, nlen) == 0) return (void *)p;
67
- }
68
- return NULL;
93
+ typedef struct {
94
+ int fds[CERVER_CONN_QUEUE_SIZE];
95
+ int head;
96
+ int tail;
97
+ int count;
98
+ pthread_mutex_t lock;
99
+ pthread_cond_t not_empty;
100
+ } conn_queue_t;
101
+
102
+ static void cq_init(conn_queue_t* q) {
103
+ memset(q->fds, -1, sizeof(q->fds));
104
+ q->head = q->tail = q->count = 0;
105
+ pthread_mutex_init(&q->lock, NULL);
106
+ pthread_cond_init(&q->not_empty, NULL);
107
+ }
108
+
109
+ static void cq_destroy(conn_queue_t* q) {
110
+ pthread_mutex_destroy(&q->lock);
111
+ pthread_cond_destroy(&q->not_empty);
112
+ }
113
+
114
+ /* Returns 0 on success, -1 if queue is full (drop connection). */
115
+ static int cq_push(conn_queue_t* q, int fd) {
116
+ pthread_mutex_lock(&q->lock);
117
+ if (q->count >= CERVER_CONN_QUEUE_SIZE) {
118
+ pthread_mutex_unlock(&q->lock);
119
+ return -1;
120
+ }
121
+ q->fds[q->tail] = fd;
122
+ q->tail = (q->tail + 1) % CERVER_CONN_QUEUE_SIZE;
123
+ q->count++;
124
+ pthread_cond_signal(&q->not_empty);
125
+ pthread_mutex_unlock(&q->lock);
126
+ return 0;
69
127
  }
70
- #define memmem cerver_memmem
71
- #endif
128
+
129
+ /* Returns fd, or -1 on shutdown. */
130
+ static int cq_pop(conn_queue_t* q, volatile int* running) {
131
+ pthread_mutex_lock(&q->lock);
132
+ while (q->count == 0 && *running) {
133
+ /* Timed wait so we can check running flag periodically */
134
+ struct timespec ts;
135
+ clock_gettime(CLOCK_REALTIME, &ts);
136
+ ts.tv_sec += 1;
137
+ pthread_cond_timedwait(&q->not_empty, &q->lock, &ts);
138
+ }
139
+ if (q->count == 0) {
140
+ pthread_mutex_unlock(&q->lock);
141
+ return -1;
142
+ }
143
+ int fd = q->fds[q->head];
144
+ q->head = (q->head + 1) % CERVER_CONN_QUEUE_SIZE;
145
+ q->count--;
146
+ pthread_mutex_unlock(&q->lock);
147
+ return fd;
148
+ }
149
+
150
+ /* Global connection queue */
151
+ static conn_queue_t g_conn_queue;
152
+
153
+ /* Global worker readiness tracking */
154
+ typedef struct {
155
+ int workers_ready;
156
+ int workers_expected;
157
+ pthread_mutex_t lock;
158
+ pthread_cond_t ready_cv;
159
+ } worker_readiness_t;
160
+
161
+ static worker_readiness_t g_worker_readiness = {.workers_ready = 0,
162
+ .workers_expected = 0,
163
+ .lock = PTHREAD_MUTEX_INITIALIZER,
164
+ .ready_cv = PTHREAD_COND_INITIALIZER};
165
+
166
+ /* Global acceptor startup tracking */
167
+ typedef struct {
168
+ int acceptors_ready;
169
+ int start_accepting;
170
+ pthread_mutex_t lock;
171
+ pthread_cond_t ready_cv;
172
+ pthread_cond_t start_cv;
173
+ } acceptor_readiness_t;
174
+
175
+ static acceptor_readiness_t g_acceptor_readiness = {.acceptors_ready = 0,
176
+ .start_accepting = 0,
177
+ .lock = PTHREAD_MUTEX_INITIALIZER,
178
+ .ready_cv = PTHREAD_COND_INITIALIZER,
179
+ .start_cv = PTHREAD_COND_INITIALIZER};
72
180
 
73
181
  /* ------------------------------------------------------------------ */
74
- /* Buffered read — accumulates until \r\n\r\n or limit reached */
182
+ /* Buffered read */
75
183
  /* ------------------------------------------------------------------ */
76
184
 
77
- static char *read_full_request(int fd, size_t *out_len) {
78
- size_t cap = CERVER_READ_BUF;
79
- size_t len = 0;
80
- char *buf = malloc(cap + 1); /* +1 for null terminator */
81
- if (!buf) return NULL;
82
-
83
- while (len < (size_t)CERVER_READ_BUF_MAX) {
84
- ssize_t n = read(fd, buf + len, cap - len);
85
- if (n <= 0) break;
86
- len += (size_t)n;
87
-
88
- /* Check for end of headers */
89
- if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4)) break;
90
-
91
- /* Grow buffer if full */
92
- if (len == cap) {
93
- size_t newcap = cap * 2;
94
- if (newcap > (size_t)CERVER_READ_BUF_MAX)
95
- newcap = (size_t)CERVER_READ_BUF_MAX;
96
- char *tmp = realloc(buf, newcap + 1);
97
- if (!tmp) { free(buf); return NULL; }
98
- buf = tmp;
99
- cap = newcap;
100
- }
101
- }
102
-
103
- if (len == 0) {
185
+ static char* read_full_request(int fd, size_t* out_len) {
186
+ size_t cap = CERVER_READ_BUF;
187
+ size_t len = 0;
188
+ char* buf = malloc(cap + 1);
189
+ if (!buf) return NULL;
190
+
191
+ while (len < (size_t)CERVER_READ_BUF_MAX) {
192
+ ssize_t n = read(fd, buf + len, cap - len);
193
+ if (n <= 0) break;
194
+ len += (size_t)n;
195
+ if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4)) break;
196
+ if (len == cap) {
197
+ size_t newcap = cap * 2;
198
+ if (newcap > (size_t)CERVER_READ_BUF_MAX) newcap = (size_t)CERVER_READ_BUF_MAX;
199
+ char* tmp = realloc(buf, newcap + 1);
200
+ if (!tmp) {
104
201
  free(buf);
105
202
  return NULL;
203
+ }
204
+ buf = tmp;
205
+ cap = newcap;
106
206
  }
207
+ }
107
208
 
108
- buf[len] = '\0';
109
- *out_len = len;
110
- return buf;
209
+ if (len == 0) {
210
+ free(buf);
211
+ return NULL;
212
+ }
213
+ buf[len] = '\0';
214
+ *out_len = len;
215
+ return buf;
111
216
  }
112
217
 
113
218
  /* ------------------------------------------------------------------ */
114
- /* Handle a single connection: read, parse, dispatch, respond */
219
+ /* Handle connection with keep-alive */
115
220
  /* ------------------------------------------------------------------ */
116
221
 
117
- static void handle_connection(cerver_server_t *srv, int client_fd) {
118
- /* Ensure the client socket is in blocking mode for reads.
119
- Some platforms inherit O_NONBLOCK from the listening socket. */
120
- int flags = fcntl(client_fd, F_GETFL, 0);
121
- if (flags >= 0 && (flags & O_NONBLOCK)) {
122
- fcntl(client_fd, F_SETFL, flags & ~O_NONBLOCK);
123
- }
222
+ static void handle_connection(cerver_server_t* srv, int client_fd) {
223
+ int flags = fcntl(client_fd, F_GETFL, 0);
224
+ if (flags >= 0 && (flags & O_NONBLOCK)) fcntl(client_fd, F_SETFL, flags & ~O_NONBLOCK);
225
+
226
+ int nodelay = 1;
227
+ setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
124
228
 
125
- /* Set a read timeout so workers don't block on stale connections */
126
- struct timeval tv = { 5, 0 }; /* 5 seconds */
229
+ int request_count = 0;
230
+ int keepalive = 1;
231
+
232
+ while (keepalive && srv->running && request_count < CERVER_KEEPALIVE_MAX) {
233
+ struct timeval tv;
234
+ tv.tv_sec = (request_count == 0) ? 5 : CERVER_KEEPALIVE_TIMEOUT;
235
+ tv.tv_usec = 0;
127
236
  setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
128
237
 
129
- /* Read the full request with buffering */
130
238
  size_t req_len = 0;
131
- char *buf = read_full_request(client_fd, &req_len);
132
-
239
+ char* buf = read_full_request(client_fd, &req_len);
133
240
  if (!buf || req_len == 0) {
134
- if (buf) free(buf);
135
- close(client_fd);
136
- return;
241
+ if (buf) free(buf);
242
+ break;
137
243
  }
138
244
 
139
- /* Parse HTTP request */
140
245
  cerver_request_t req;
141
246
  memset(&req, 0, sizeof(req));
142
247
 
143
248
  if (cerver_parse_request(buf, req_len, &req) < 0) {
144
- /* Bad request — send 400 */
145
- const char *resp = "HTTP/1.1 400 Bad Request\r\nContent-Length: 11\r\nConnection: close\r\n\r\nBad Request";
146
- write(client_fd, resp, strlen(resp));
147
- free(buf);
148
- close(client_fd);
149
- return;
249
+ const char* resp =
250
+ "HTTP/1.1 400 Bad Request\r\n"
251
+ "Content-Length: 11\r\nConnection: close\r\n\r\nBad Request";
252
+ write(client_fd, resp, strlen(resp));
253
+ free(buf);
254
+ break;
150
255
  }
151
256
 
152
- /*
153
- * The parser malloc'd its own mutable copy (req._raw_buf) and all
154
- * internal pointers (path, headers, etc.) reference that copy.
155
- * We can now free our original read buffer.
156
- */
157
- free(buf);
257
+ request_count++;
258
+ keepalive = !cerver_req_wants_close(&req);
158
259
 
159
- /* Prepare response */
160
260
  cerver_response_t res;
161
261
  memset(&res, 0, sizeof(res));
162
262
 
163
- /* Try static assets first — files take priority over route handlers */
164
263
  if (cerver_serve_static(srv, &req, &res) < 0) {
165
- /* No static file found — try route handlers */
166
- cerver_handler_fn handler = cerver_dispatch(srv, &req);
167
-
168
- if (handler) {
169
- handler(&req, &res);
170
- } else {
171
- /* 404 */
172
- cerver_res_text(&res, 404, "Not Found");
173
- }
264
+ cerver_handler_fn handler = cerver_dispatch(srv, &req);
265
+ if (handler) {
266
+ handler(&req, &res);
267
+ } else {
268
+ cerver_res_text(&res, 404, "Not Found");
269
+ }
174
270
  }
175
271
 
176
- /* Write response */
177
- cerver_write_response(client_fd, &res);
272
+ if (res._force_close) keepalive = 0;
178
273
 
179
- /* Cleanup */
180
- if (res._body_owned && res.body) {
181
- free((void *)res.body);
182
- }
183
- if (req._raw_buf) {
184
- free(req._raw_buf);
185
- }
274
+ int write_err = cerver_write_response(client_fd, &res, keepalive);
186
275
 
187
- close(client_fd);
276
+ if (res._body_owned == 1 && res.body)
277
+ free((void*)res.body);
278
+ else if (res._body_owned == 2 && res.body)
279
+ munmap((void*)res.body, res.body_len);
280
+
281
+ free(buf);
282
+ if (write_err < 0) break;
283
+ }
284
+
285
+ close(client_fd);
188
286
  }
189
287
 
190
288
  /* ------------------------------------------------------------------ */
191
- /* Thread pool task queue (ring buffer) */
289
+ /* Connection pool worker thread */
192
290
  /* ------------------------------------------------------------------ */
193
291
 
194
- static int enqueue_task(cerver_server_t *srv, int client_fd) {
195
- pthread_mutex_lock(&srv->tq_mutex);
196
-
197
- if (srv->tq_count >= CERVER_TASK_QUEUE_SIZE) {
198
- /* Queue full — drop connection */
199
- pthread_mutex_unlock(&srv->tq_mutex);
200
- const char *resp = "HTTP/1.1 503 Service Unavailable\r\n"
201
- "Content-Length: 19\r\nConnection: close\r\n\r\n"
202
- "Service Unavailable";
203
- write(client_fd, resp, strlen(resp));
204
- close(client_fd);
205
- return -1;
206
- }
207
-
208
- srv->task_queue[srv->tq_tail] = client_fd;
209
- srv->tq_tail = (srv->tq_tail + 1) % CERVER_TASK_QUEUE_SIZE;
210
- srv->tq_count++;
211
-
212
- pthread_cond_signal(&srv->tq_cond);
213
- pthread_mutex_unlock(&srv->tq_mutex);
214
- return 0;
292
+ static void* conn_pool_worker(void* arg) {
293
+ cerver_server_t* srv = (cerver_server_t*)arg;
294
+
295
+ /* Signal that this worker is ready */
296
+ pthread_mutex_lock(&g_worker_readiness.lock);
297
+ g_worker_readiness.workers_ready++;
298
+ pthread_cond_broadcast(&g_worker_readiness.ready_cv);
299
+ pthread_mutex_unlock(&g_worker_readiness.lock);
300
+
301
+ while (srv->running) {
302
+ int fd = cq_pop(&g_conn_queue, &srv->running);
303
+ if (fd < 0) continue;
304
+ handle_connection(srv, fd);
305
+ }
306
+ return NULL;
215
307
  }
216
308
 
217
- static int dequeue_task(cerver_server_t *srv) {
218
- pthread_mutex_lock(&srv->tq_mutex);
219
-
220
- while (srv->tq_count == 0 && srv->running) {
221
- pthread_cond_wait(&srv->tq_cond, &srv->tq_mutex);
222
- }
223
-
224
- if (!srv->running && srv->tq_count == 0) {
225
- pthread_mutex_unlock(&srv->tq_mutex);
226
- return -1; /* shutdown signal */
227
- }
309
+ static int wait_for_pool_workers_ready(cerver_server_t* srv, int expected) {
310
+ pthread_mutex_lock(&g_worker_readiness.lock);
311
+ while (g_worker_readiness.workers_ready < expected && srv->running) {
312
+ pthread_cond_wait(&g_worker_readiness.ready_cv, &g_worker_readiness.lock);
313
+ }
314
+ int ready = (g_worker_readiness.workers_ready >= expected);
315
+ pthread_mutex_unlock(&g_worker_readiness.lock);
316
+ return ready;
317
+ }
228
318
 
229
- int fd = srv->task_queue[srv->tq_head];
230
- srv->tq_head = (srv->tq_head + 1) % CERVER_TASK_QUEUE_SIZE;
231
- srv->tq_count--;
319
+ static int wait_for_acceptors_ready(cerver_server_t* srv, int expected) {
320
+ pthread_mutex_lock(&g_acceptor_readiness.lock);
321
+ while (g_acceptor_readiness.acceptors_ready < expected && srv->running) {
322
+ pthread_cond_wait(&g_acceptor_readiness.ready_cv, &g_acceptor_readiness.lock);
323
+ }
324
+ int ready = (g_acceptor_readiness.acceptors_ready >= expected);
325
+ pthread_mutex_unlock(&g_acceptor_readiness.lock);
326
+ return ready;
327
+ }
232
328
 
233
- pthread_mutex_unlock(&srv->tq_mutex);
234
- return fd;
329
+ static void release_acceptors(void) {
330
+ pthread_mutex_lock(&g_acceptor_readiness.lock);
331
+ g_acceptor_readiness.start_accepting = 1;
332
+ pthread_cond_broadcast(&g_acceptor_readiness.start_cv);
333
+ pthread_mutex_unlock(&g_acceptor_readiness.lock);
235
334
  }
236
335
 
237
336
  /* ------------------------------------------------------------------ */
238
- /* Worker thread entry point */
337
+ /* Create a listening socket */
239
338
  /* ------------------------------------------------------------------ */
240
339
 
241
- static void *worker_thread(void *arg) {
242
- cerver_server_t *srv = (cerver_server_t *)arg;
340
+ static int create_listener(int port, int reuseport) {
341
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
342
+ if (fd < 0) {
343
+ perror("cerver: socket");
344
+ return -1;
345
+ }
243
346
 
244
- while (srv->running) {
245
- int client_fd = dequeue_task(srv);
246
- if (client_fd < 0) break; /* shutdown */
347
+ int opt = 1;
348
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
247
349
 
248
- handle_connection(srv, client_fd);
249
- }
350
+ #ifdef __linux__
351
+ if (reuseport) setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
352
+ #else
353
+ (void)reuseport;
354
+ #endif
250
355
 
251
- return NULL;
356
+ struct sockaddr_in addr;
357
+ memset(&addr, 0, sizeof(addr));
358
+ addr.sin_family = AF_INET;
359
+ addr.sin_addr.s_addr = INADDR_ANY;
360
+ addr.sin_port = htons((uint16_t)port);
361
+
362
+ if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
363
+ perror("cerver: bind");
364
+ close(fd);
365
+ return -1;
366
+ }
367
+ if (listen(fd, CERVER_LISTEN_BACKLOG) < 0) {
368
+ perror("cerver: listen");
369
+ close(fd);
370
+ return -1;
371
+ }
372
+
373
+ set_nonblocking(fd);
374
+ return fd;
252
375
  }
253
376
 
254
377
  /* ------------------------------------------------------------------ */
255
- /* Server init */
378
+ /* Accept helper */
256
379
  /* ------------------------------------------------------------------ */
257
380
 
258
- int cerver_init(cerver_server_t *srv, int port, int threads) {
259
- memset(srv, 0, sizeof(*srv));
260
- srv->port = port;
261
- srv->sock_fd = -1;
262
- srv->running = 0;
263
- srv->public_dir = NULL;
264
-
265
- /* Thread pool config */
266
- srv->thread_count = (threads > 0) ? threads : CERVER_THREAD_POOL_DEFAULT;
267
- srv->threads = NULL;
268
- srv->tq_head = 0;
269
- srv->tq_tail = 0;
270
- srv->tq_count = 0;
381
+ static int accept_connection(int listen_fd) {
382
+ struct sockaddr_in ca;
383
+ socklen_t cl = sizeof(ca);
384
+ #ifdef __linux__
385
+ return accept4(listen_fd, (struct sockaddr*)&ca, &cl, SOCK_CLOEXEC);
386
+ #else
387
+ return accept(listen_fd, (struct sockaddr*)&ca, &cl);
388
+ #endif
389
+ }
271
390
 
272
- pthread_mutex_init(&srv->tq_mutex, NULL);
273
- pthread_cond_init(&srv->tq_cond, NULL);
391
+ /* ------------------------------------------------------------------ */
392
+ /* Acceptor event loops (one per core, accept-only, never block) */
393
+ /* ------------------------------------------------------------------ */
274
394
 
275
- return 0;
276
- }
395
+ #if CERVER_USE_KQUEUE
396
+ static void* acceptor_loop(void* arg) {
397
+ cerver_worker_t* w = (cerver_worker_t*)arg;
398
+ cerver_server_t* srv = w->srv;
277
399
 
278
- int cerver_add_routes(cerver_server_t *srv, cerver_route_t *routes, int count) {
279
- srv->routes = routes;
280
- srv->route_count = count;
281
- return 0;
282
- }
400
+ int kq = kqueue();
401
+ if (kq < 0) {
402
+ perror("cerver: kqueue");
403
+ return NULL;
404
+ }
405
+ w->event_fd = kq;
406
+
407
+ struct kevent change;
408
+ EV_SET(&change, w->listen_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
409
+ kevent(kq, &change, 1, NULL, 0, NULL);
410
+
411
+ struct kevent events[CERVER_MAX_EVENTS];
412
+
413
+ pthread_mutex_lock(&g_acceptor_readiness.lock);
414
+ g_acceptor_readiness.acceptors_ready++;
415
+ pthread_cond_broadcast(&g_acceptor_readiness.ready_cv);
416
+ while (!g_acceptor_readiness.start_accepting && srv->running) {
417
+ pthread_cond_wait(&g_acceptor_readiness.start_cv, &g_acceptor_readiness.lock);
418
+ }
419
+ pthread_mutex_unlock(&g_acceptor_readiness.lock);
420
+
421
+ while (srv->running) {
422
+ struct timespec ts = {1, 0};
423
+ int nev = kevent(kq, NULL, 0, events, CERVER_MAX_EVENTS, &ts);
424
+ if (nev < 0) {
425
+ if (errno == EINTR) continue;
426
+ break;
427
+ }
283
428
 
284
- int cerver_set_assets(cerver_server_t *srv, cerver_asset_t *assets, int count) {
285
- srv->assets = assets;
286
- srv->asset_count = count;
287
- return 0;
288
- }
429
+ for (int i = 0; i < nev; i++) {
430
+ if ((int)events[i].ident == w->listen_fd) {
431
+ while (1) {
432
+ int cfd = accept_connection(w->listen_fd);
433
+ if (cfd < 0) {
434
+ if (errno == EAGAIN || errno == EWOULDBLOCK) break;
435
+ break;
436
+ }
437
+ if (cq_push(&g_conn_queue, cfd) < 0) {
438
+ /* Queue full — send 503 and close */
439
+ const char* r =
440
+ "HTTP/1.1 503 Service Unavailable\r\n"
441
+ "Content-Length: 19\r\nConnection: close\r\n\r\n"
442
+ "Service Unavailable";
443
+ write(cfd, r, strlen(r));
444
+ close(cfd);
445
+ }
446
+ }
447
+ }
448
+ }
449
+ }
289
450
 
290
- void cerver_set_public_dir(cerver_server_t *srv, const char *dir) {
291
- srv->public_dir = dir;
451
+ close(kq);
452
+ return NULL;
292
453
  }
293
454
 
294
- /* ------------------------------------------------------------------ */
295
- /* Start thread pool */
296
- /* ------------------------------------------------------------------ */
455
+ #elif CERVER_USE_EPOLL
456
+ static void* acceptor_loop(void* arg) {
457
+ cerver_worker_t* w = (cerver_worker_t*)arg;
458
+ cerver_server_t* srv = w->srv;
297
459
 
298
- static int start_thread_pool(cerver_server_t *srv) {
299
- srv->threads = malloc(sizeof(pthread_t) * (size_t)srv->thread_count);
300
- if (!srv->threads) {
301
- perror("cerver: malloc threads");
302
- return -1;
460
+ int ep = epoll_create1(EPOLL_CLOEXEC);
461
+ if (ep < 0) {
462
+ perror("cerver: epoll");
463
+ return NULL;
464
+ }
465
+ w->event_fd = ep;
466
+
467
+ struct epoll_event ev = {.events = EPOLLIN, .data.fd = w->listen_fd};
468
+ epoll_ctl(ep, EPOLL_CTL_ADD, w->listen_fd, &ev);
469
+
470
+ struct epoll_event events[CERVER_MAX_EVENTS];
471
+
472
+ pthread_mutex_lock(&g_acceptor_readiness.lock);
473
+ g_acceptor_readiness.acceptors_ready++;
474
+ pthread_cond_broadcast(&g_acceptor_readiness.ready_cv);
475
+ while (!g_acceptor_readiness.start_accepting && srv->running) {
476
+ pthread_cond_wait(&g_acceptor_readiness.start_cv, &g_acceptor_readiness.lock);
477
+ }
478
+ pthread_mutex_unlock(&g_acceptor_readiness.lock);
479
+
480
+ while (srv->running) {
481
+ int nev = epoll_wait(ep, events, CERVER_MAX_EVENTS, 1000);
482
+ if (nev < 0) {
483
+ if (errno == EINTR) continue;
484
+ break;
303
485
  }
304
486
 
305
- /* Use 2 MB stack per worker (handles deep call chains with large buffers) */
306
- pthread_attr_t attr;
307
- pthread_attr_init(&attr);
308
- pthread_attr_setstacksize(&attr, 2 * 1024 * 1024);
309
-
310
- for (int i = 0; i < srv->thread_count; i++) {
311
- if (pthread_create(&srv->threads[i], &attr, worker_thread, srv) != 0) {
312
- perror("cerver: pthread_create");
313
- /* Clean up already-created threads */
314
- srv->running = 0;
315
- pthread_cond_broadcast(&srv->tq_cond);
316
- for (int j = 0; j < i; j++) {
317
- pthread_join(srv->threads[j], NULL);
318
- }
319
- free(srv->threads);
320
- srv->threads = NULL;
321
- return -1;
487
+ for (int i = 0; i < nev; i++) {
488
+ if (events[i].data.fd == w->listen_fd) {
489
+ while (1) {
490
+ int cfd = accept_connection(w->listen_fd);
491
+ if (cfd < 0) {
492
+ if (errno == EAGAIN || errno == EWOULDBLOCK) break;
493
+ break;
494
+ }
495
+ if (cq_push(&g_conn_queue, cfd) < 0) {
496
+ const char* r =
497
+ "HTTP/1.1 503 Service Unavailable\r\n"
498
+ "Content-Length: 19\r\nConnection: close\r\n\r\n"
499
+ "Service Unavailable";
500
+ write(cfd, r, strlen(r));
501
+ close(cfd);
502
+ }
322
503
  }
504
+ }
323
505
  }
506
+ }
324
507
 
325
- pthread_attr_destroy(&attr);
508
+ close(ep);
509
+ return NULL;
510
+ }
326
511
 
327
- printf("cerver: started %d worker thread(s)\n", srv->thread_count);
328
- return 0;
512
+ #else /* SELECT */
513
+ static void* acceptor_loop(void* arg) {
514
+ cerver_worker_t* w = (cerver_worker_t*)arg;
515
+ cerver_server_t* srv = w->srv;
516
+
517
+ pthread_mutex_lock(&g_acceptor_readiness.lock);
518
+ g_acceptor_readiness.acceptors_ready++;
519
+ pthread_cond_broadcast(&g_acceptor_readiness.ready_cv);
520
+ while (!g_acceptor_readiness.start_accepting && srv->running) {
521
+ pthread_cond_wait(&g_acceptor_readiness.start_cv, &g_acceptor_readiness.lock);
522
+ }
523
+ pthread_mutex_unlock(&g_acceptor_readiness.lock);
524
+
525
+ while (srv->running) {
526
+ fd_set rfds;
527
+ FD_ZERO(&rfds);
528
+ FD_SET(w->listen_fd, &rfds);
529
+ struct timeval tv = {1, 0};
530
+ int ret = select(w->listen_fd + 1, &rfds, NULL, NULL, &tv);
531
+ if (ret < 0) {
532
+ if (errno == EINTR) continue;
533
+ break;
534
+ }
535
+ if (ret > 0 && FD_ISSET(w->listen_fd, &rfds)) {
536
+ int cfd = accept_connection(w->listen_fd);
537
+ if (cfd >= 0) {
538
+ if (cq_push(&g_conn_queue, cfd) < 0) {
539
+ close(cfd);
540
+ }
541
+ }
542
+ }
543
+ }
544
+ return NULL;
329
545
  }
546
+ #endif
330
547
 
331
548
  /* ------------------------------------------------------------------ */
332
- /* Server listen — event loop */
549
+ /* Stat cache */
333
550
  /* ------------------------------------------------------------------ */
334
551
 
335
- int cerver_listen(cerver_server_t *srv) {
336
- /* Create socket */
337
- srv->sock_fd = socket(AF_INET, SOCK_STREAM, 0);
338
- if (srv->sock_fd < 0) {
339
- perror("cerver: socket");
340
- return -1;
341
- }
552
+ void cerver_stat_cache_init(cerver_stat_cache_t* cache) {
553
+ memset(cache, 0, sizeof(*cache));
554
+ pthread_mutex_init(&cache->lock, NULL);
555
+ }
342
556
 
343
- /* SO_REUSEADDR so we can restart quickly */
344
- int opt = 1;
345
- setsockopt(srv->sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
346
-
347
- /* Bind */
348
- struct sockaddr_in addr;
349
- memset(&addr, 0, sizeof(addr));
350
- addr.sin_family = AF_INET;
351
- addr.sin_addr.s_addr = INADDR_ANY;
352
- addr.sin_port = htons((uint16_t)srv->port);
353
-
354
- if (bind(srv->sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
355
- perror("cerver: bind");
356
- close(srv->sock_fd);
357
- return -1;
557
+ int cerver_stat_cache_lookup(cerver_stat_cache_t* cache, const char* path, size_t* file_size) {
558
+ time_t now = time(NULL);
559
+ pthread_mutex_lock(&cache->lock);
560
+ for (int i = 0; i < CERVER_STAT_CACHE_SIZE; i++) {
561
+ cerver_stat_entry_t* e = &cache->entries[i];
562
+ if (e->valid && strcmp(e->path, path) == 0) {
563
+ if (now - e->cached_at < CERVER_STAT_CACHE_TTL) {
564
+ *file_size = e->file_size;
565
+ pthread_mutex_unlock(&cache->lock);
566
+ return 0;
567
+ }
568
+ e->valid = 0;
569
+ break;
358
570
  }
571
+ }
572
+ pthread_mutex_unlock(&cache->lock);
573
+ return -1;
574
+ }
359
575
 
360
- /* Listen */
361
- if (listen(srv->sock_fd, 128) < 0) {
362
- perror("cerver: listen");
363
- close(srv->sock_fd);
364
- return -1;
576
+ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_t file_size,
577
+ time_t mtime) {
578
+ time_t now = time(NULL);
579
+ pthread_mutex_lock(&cache->lock);
580
+ int best = 0;
581
+ time_t oldest = cache->entries[0].cached_at;
582
+ for (int i = 0; i < CERVER_STAT_CACHE_SIZE; i++) {
583
+ cerver_stat_entry_t* e = &cache->entries[i];
584
+ if (!e->valid) {
585
+ best = i;
586
+ break;
365
587
  }
366
-
367
- set_nonblocking(srv->sock_fd);
368
-
369
- /* Install signal handlers */
370
- g_srv = srv;
371
- signal(SIGINT, signal_handler);
372
- signal(SIGTERM, signal_handler);
373
- signal(SIGPIPE, SIG_IGN);
374
-
375
- srv->running = 1;
376
-
377
- printf("cerver: listening on http://localhost:%d\n", srv->port);
378
-
379
- /* Start the thread pool */
380
- if (start_thread_pool(srv) < 0) {
381
- close(srv->sock_fd);
382
- return -1;
588
+ if (e->cached_at < oldest) {
589
+ oldest = e->cached_at;
590
+ best = i;
383
591
  }
592
+ }
593
+ cerver_stat_entry_t* slot = &cache->entries[best];
594
+ strncpy(slot->path, path, sizeof(slot->path) - 1);
595
+ slot->path[sizeof(slot->path) - 1] = '\0';
596
+ slot->file_size = file_size;
597
+ slot->mtime = mtime;
598
+ slot->cached_at = now;
599
+ slot->valid = 1;
600
+ pthread_mutex_unlock(&cache->lock);
601
+ }
384
602
 
385
- /* ================================================================== */
386
- /* kqueue event loop (macOS / FreeBSD) */
387
- /* ================================================================== */
388
- #if CERVER_USE_KQUEUE
603
+ /* ------------------------------------------------------------------ */
604
+ /* Server init */
605
+ /* ------------------------------------------------------------------ */
389
606
 
390
- int kq = kqueue();
391
- if (kq < 0) {
392
- perror("cerver: kqueue");
393
- close(srv->sock_fd);
394
- return -1;
395
- }
607
+ int cerver_init(cerver_server_t* srv, int port, int threads) {
608
+ memset(srv, 0, sizeof(*srv));
609
+ srv->port = port;
610
+ srv->sock_fd = -1;
611
+ srv->running = 0;
612
+ srv->public_dir = NULL;
613
+ srv->dispatch_override = NULL;
614
+ srv->worker_count = (threads > 0) ? threads : get_cpu_count();
615
+ srv->workers = NULL;
616
+ cerver_stat_cache_init(&srv->stat_cache);
617
+ return 0;
618
+ }
396
619
 
397
- struct kevent change;
398
- EV_SET(&change, srv->sock_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
399
- kevent(kq, &change, 1, NULL, 0, NULL);
620
+ int cerver_add_routes(cerver_server_t* srv, cerver_route_t* routes, int count) {
621
+ srv->routes = routes;
622
+ srv->route_count = count;
623
+ return 0;
624
+ }
400
625
 
401
- struct kevent events[MAX_EVENTS];
626
+ int cerver_set_assets(cerver_server_t* srv, cerver_asset_t* assets, int count) {
627
+ srv->assets = assets;
628
+ srv->asset_count = count;
629
+ return 0;
630
+ }
402
631
 
403
- while (srv->running) {
404
- struct timespec timeout = { 1, 0 }; /* 1 second */
405
- int nev = kevent(kq, NULL, 0, events, MAX_EVENTS, &timeout);
632
+ void cerver_set_public_dir(cerver_server_t* srv, const char* dir) { srv->public_dir = dir; }
406
633
 
407
- if (nev < 0) {
408
- if (errno == EINTR) continue;
409
- perror("cerver: kevent");
410
- break;
411
- }
634
+ /* ------------------------------------------------------------------ */
635
+ /* Server listen */
636
+ /* ------------------------------------------------------------------ */
412
637
 
413
- for (int i = 0; i < nev; i++) {
414
- int fd = (int)events[i].ident;
415
-
416
- if (fd == srv->sock_fd) {
417
- /* Accept new connections */
418
- while (1) {
419
- struct sockaddr_in client_addr;
420
- socklen_t client_len = sizeof(client_addr);
421
- int client_fd = accept(srv->sock_fd,
422
- (struct sockaddr *)&client_addr,
423
- &client_len);
424
- if (client_fd < 0) {
425
- if (errno == EAGAIN || errno == EWOULDBLOCK) break;
426
- perror("cerver: accept");
427
- break;
428
- }
429
- enqueue_task(srv, client_fd);
430
- }
431
- }
432
- }
638
+ int cerver_listen(cerver_server_t* srv) {
639
+ g_srv = srv;
640
+ signal(SIGINT, signal_handler);
641
+ signal(SIGTERM, signal_handler);
642
+ signal(SIGPIPE, SIG_IGN);
643
+
644
+ srv->running = 1;
645
+
646
+ /* Determine pool and acceptor counts.
647
+ * Acceptors: min(worker_count, cpu_count) — one per core for accept.
648
+ * Pool workers: worker_count * 16 — enough to cover concurrent keep-alive. */
649
+ int cpu_count = get_cpu_count();
650
+ int acceptor_count = cpu_count;
651
+ if (acceptor_count > srv->worker_count) acceptor_count = srv->worker_count;
652
+ if (acceptor_count < 1) acceptor_count = 1;
653
+
654
+ int pool_size = srv->worker_count * 16;
655
+ if (pool_size < CERVER_CONN_POOL_SIZE) pool_size = CERVER_CONN_POOL_SIZE;
656
+ if (pool_size > 1024) pool_size = 1024;
657
+
658
+ /* Init shared connection queue */
659
+ cq_init(&g_conn_queue);
660
+
661
+ /* Initialize worker readiness tracking */
662
+ pthread_mutex_lock(&g_worker_readiness.lock);
663
+ g_worker_readiness.workers_ready = 0;
664
+ g_worker_readiness.workers_expected = pool_size;
665
+ pthread_mutex_unlock(&g_worker_readiness.lock);
666
+
667
+ pthread_mutex_lock(&g_acceptor_readiness.lock);
668
+ g_acceptor_readiness.acceptors_ready = 0;
669
+ g_acceptor_readiness.start_accepting = 0;
670
+ pthread_mutex_unlock(&g_acceptor_readiness.lock);
671
+
672
+ /* Start connection pool workers */
673
+ pthread_t* pool_threads = calloc((size_t)pool_size, sizeof(pthread_t));
674
+ if (!pool_threads) {
675
+ perror("cerver: calloc pool");
676
+ if (srv->sock_fd >= 0) close(srv->sock_fd);
677
+ return -1;
678
+ }
679
+
680
+ pthread_attr_t attr;
681
+ pthread_attr_init(&attr);
682
+ pthread_attr_setstacksize(&attr, 2 * 1024 * 1024);
683
+
684
+ for (int i = 0; i < pool_size; i++) {
685
+ if (pthread_create(&pool_threads[i], &attr, conn_pool_worker, srv) != 0) {
686
+ perror("cerver: pool thread create");
687
+ srv->running = 0;
688
+ for (int j = 0; j < i; j++) pthread_join(pool_threads[j], NULL);
689
+ free(pool_threads);
690
+ if (srv->sock_fd >= 0) close(srv->sock_fd);
691
+ pthread_attr_destroy(&attr);
692
+ return -1;
433
693
  }
694
+ }
434
695
 
435
- close(kq);
436
-
437
- /* ================================================================== */
438
- /* epoll event loop (Linux) */
439
- /* ================================================================== */
440
- #elif CERVER_USE_EPOLL
696
+ if (!wait_for_pool_workers_ready(srv, pool_size)) {
697
+ srv->running = 0;
698
+ for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
699
+ free(pool_threads);
700
+ pthread_attr_destroy(&attr);
701
+ return -1;
702
+ }
441
703
 
442
- int ep = epoll_create1(0);
443
- if (ep < 0) {
444
- perror("cerver: epoll_create1");
445
- close(srv->sock_fd);
446
- return -1;
704
+ srv->sock_fd = create_listener(srv->port, 0);
705
+ if (srv->sock_fd < 0) {
706
+ srv->running = 0;
707
+ for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
708
+ free(pool_threads);
709
+ pthread_attr_destroy(&attr);
710
+ return -1;
711
+ }
712
+
713
+ /* Start acceptor threads */
714
+ srv->workers = calloc((size_t)acceptor_count, sizeof(cerver_worker_t));
715
+ srv->worker_count = acceptor_count;
716
+ if (!srv->workers) {
717
+ perror("cerver: calloc acceptors");
718
+ srv->running = 0;
719
+ pthread_cond_broadcast(&g_conn_queue.not_empty);
720
+ for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
721
+ free(pool_threads);
722
+ close(srv->sock_fd);
723
+ pthread_attr_destroy(&attr);
724
+ return -1;
725
+ }
726
+
727
+ for (int i = 0; i < acceptor_count; i++) {
728
+ cerver_worker_t* w = &srv->workers[i];
729
+ w->id = i;
730
+ w->srv = srv;
731
+ w->event_fd = -1;
732
+ #ifdef __linux__
733
+ w->listen_fd = create_listener(srv->port, 1);
734
+ if (w->listen_fd < 0) w->listen_fd = srv->sock_fd;
735
+ #else
736
+ w->listen_fd = srv->sock_fd;
737
+ #endif
738
+ if (pthread_create(&w->thread, &attr, acceptor_loop, w) != 0) {
739
+ perror("cerver: acceptor create");
740
+ srv->running = 0;
741
+ for (int j = 0; j < i; j++) pthread_join(srv->workers[j].thread, NULL);
742
+ break;
447
743
  }
744
+ }
448
745
 
449
- struct epoll_event ev;
450
- ev.events = EPOLLIN;
451
- ev.data.fd = srv->sock_fd;
452
- epoll_ctl(ep, EPOLL_CTL_ADD, srv->sock_fd, &ev);
453
-
454
- struct epoll_event events[MAX_EVENTS];
455
-
456
- while (srv->running) {
457
- int nev = epoll_wait(ep, events, MAX_EVENTS, 1000);
458
-
459
- if (nev < 0) {
460
- if (errno == EINTR) continue;
461
- perror("cerver: epoll_wait");
462
- break;
463
- }
746
+ if (!wait_for_acceptors_ready(srv, acceptor_count)) {
747
+ srv->running = 0;
748
+ release_acceptors();
749
+ for (int i = 0; i < acceptor_count; i++) pthread_join(srv->workers[i].thread, NULL);
750
+ for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
751
+ free(pool_threads);
752
+ pthread_attr_destroy(&attr);
753
+ return -1;
754
+ }
464
755
 
465
- for (int i = 0; i < nev; i++) {
466
- if (events[i].data.fd == srv->sock_fd) {
467
- while (1) {
468
- struct sockaddr_in client_addr;
469
- socklen_t client_len = sizeof(client_addr);
470
- int client_fd = accept(srv->sock_fd,
471
- (struct sockaddr *)&client_addr,
472
- &client_len);
473
- if (client_fd < 0) {
474
- if (errno == EAGAIN || errno == EWOULDBLOCK) break;
475
- perror("cerver: accept");
476
- break;
477
- }
478
- enqueue_task(srv, client_fd);
479
- }
480
- }
481
- }
482
- }
756
+ printf("cerver: listening on http://localhost:%d\n", srv->port);
757
+ printf("cerver: %d acceptor(s), %d connection workers, keep-alive max %d req/conn\n",
758
+ acceptor_count, pool_size, CERVER_KEEPALIVE_MAX);
483
759
 
484
- close(ep);
760
+ release_acceptors();
485
761
 
486
- /* ================================================================== */
487
- /* select() fallback */
488
- /* ================================================================== */
489
- #elif CERVER_USE_SELECT
762
+ pthread_attr_destroy(&attr);
490
763
 
491
- while (srv->running) {
492
- fd_set readfds;
493
- FD_ZERO(&readfds);
494
- FD_SET(srv->sock_fd, &readfds);
764
+ /* Main thread waits for shutdown */
765
+ while (srv->running) sleep(1);
495
766
 
496
- struct timeval timeout = { 1, 0 };
497
- int ret = select(srv->sock_fd + 1, &readfds, NULL, NULL, &timeout);
767
+ /* Shutdown: stop acceptors first, then drain pool */
768
+ srv->running = 0;
498
769
 
499
- if (ret < 0) {
500
- if (errno == EINTR) continue;
501
- perror("cerver: select");
502
- break;
503
- }
770
+ for (int i = 0; i < acceptor_count; i++) pthread_join(srv->workers[i].thread, NULL);
504
771
 
505
- if (ret > 0 && FD_ISSET(srv->sock_fd, &readfds)) {
506
- struct sockaddr_in client_addr;
507
- socklen_t client_len = sizeof(client_addr);
508
- int client_fd = accept(srv->sock_fd,
509
- (struct sockaddr *)&client_addr,
510
- &client_len);
511
- if (client_fd >= 0) {
512
- enqueue_task(srv, client_fd);
513
- }
514
- }
515
- }
772
+ /* Wake all pool workers */
773
+ pthread_mutex_lock(&g_conn_queue.lock);
774
+ pthread_cond_broadcast(&g_conn_queue.not_empty);
775
+ pthread_mutex_unlock(&g_conn_queue.lock);
516
776
 
517
- #endif
777
+ for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
778
+ free(pool_threads);
518
779
 
519
- cerver_shutdown(srv);
520
- return 0;
780
+ cerver_shutdown(srv);
781
+ return 0;
521
782
  }
522
783
 
523
784
  /* ------------------------------------------------------------------ */
524
785
  /* Shutdown */
525
786
  /* ------------------------------------------------------------------ */
526
787
 
527
- void cerver_shutdown(cerver_server_t *srv) {
528
- srv->running = 0;
529
-
530
- /* Wake all worker threads so they can exit */
531
- pthread_mutex_lock(&srv->tq_mutex);
532
- pthread_cond_broadcast(&srv->tq_cond);
533
- pthread_mutex_unlock(&srv->tq_mutex);
788
+ void cerver_shutdown(cerver_server_t* srv) {
789
+ srv->running = 0;
534
790
 
535
- /* Join all worker threads */
536
- if (srv->threads) {
537
- for (int i = 0; i < srv->thread_count; i++) {
538
- pthread_join(srv->threads[i], NULL);
539
- }
540
- free(srv->threads);
541
- srv->threads = NULL;
791
+ if (srv->workers) {
792
+ for (int i = 0; i < srv->worker_count; i++) {
793
+ #ifdef __linux__
794
+ if (srv->workers[i].listen_fd != srv->sock_fd && srv->workers[i].listen_fd >= 0)
795
+ close(srv->workers[i].listen_fd);
796
+ #endif
797
+ if (srv->workers[i].event_fd >= 0) close(srv->workers[i].event_fd);
542
798
  }
799
+ free(srv->workers);
800
+ srv->workers = NULL;
801
+ }
543
802
 
544
- /* Close listener socket */
545
- if (srv->sock_fd >= 0) {
546
- close(srv->sock_fd);
547
- srv->sock_fd = -1;
548
- }
803
+ if (srv->sock_fd >= 0) {
804
+ close(srv->sock_fd);
805
+ srv->sock_fd = -1;
806
+ }
549
807
 
550
- /* Destroy synchronization primitives */
551
- pthread_mutex_destroy(&srv->tq_mutex);
552
- pthread_cond_destroy(&srv->tq_cond);
808
+ cq_destroy(&g_conn_queue);
809
+ pthread_mutex_destroy(&srv->stat_cache.lock);
553
810
 
554
- printf("\ncerver: server stopped\n");
811
+ printf("\ncerver: server stopped\n");
555
812
  }