@velox0/cerver 0.3.1 → 0.4.0

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,44 +18,37 @@
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
31
  #define CERVER_USE_KQUEUE 1
27
32
  #include <sys/event.h>
28
33
  #elif defined(__linux__)
29
34
  #define CERVER_USE_EPOLL 1
30
35
  #include <sys/epoll.h>
36
+ #include <sys/sendfile.h>
31
37
  #else
32
38
  #define CERVER_USE_SELECT 1
33
39
  #include <sys/select.h>
34
40
  #endif
35
41
 
36
- #define MAX_EVENTS 64
42
+ #ifdef __linux__
43
+ #include <sched.h>
44
+ #endif
37
45
 
38
- /* Global server pointer for signal handler */
39
- static cerver_server_t *g_srv = NULL;
40
-
41
- static void signal_handler(int sig) {
42
- (void)sig;
43
- if (g_srv) {
44
- g_srv->running = 0;
45
- }
46
- }
47
-
48
- 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);
52
- }
46
+ /* Connection pool sizing */
47
+ #define CERVER_CONN_POOL_SIZE 128
48
+ #define CERVER_CONN_QUEUE_SIZE 4096
53
49
 
54
50
  /* ------------------------------------------------------------------ */
55
- /* memmem fallback for systems that lack it */
51
+ /* memmem fallback */
56
52
  /* ------------------------------------------------------------------ */
57
53
 
58
54
  #if !defined(__APPLE__) && !defined(__linux__) && !defined(_GNU_SOURCE)
@@ -70,25 +66,106 @@ static void *cerver_memmem(const void *hay, size_t haylen,
70
66
  #define memmem cerver_memmem
71
67
  #endif
72
68
 
69
+ /* Global for signal handler */
70
+ static cerver_server_t *g_srv = NULL;
71
+
72
+ static void signal_handler(int sig) {
73
+ (void)sig;
74
+ if (g_srv) g_srv->running = 0;
75
+ }
76
+
77
+ static int set_nonblocking(int fd) {
78
+ int flags = fcntl(fd, F_GETFL, 0);
79
+ if (flags < 0) return -1;
80
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
81
+ }
82
+
83
+ static int get_cpu_count(void) {
84
+ long n = sysconf(_SC_NPROCESSORS_ONLN);
85
+ if (n < 1) n = 1;
86
+ if (n > 64) n = 64;
87
+ return (int)n;
88
+ }
89
+
73
90
  /* ------------------------------------------------------------------ */
74
- /* Buffered read accumulates until \r\n\r\n or limit reached */
91
+ /* Connection queue (shared between acceptors and pool workers) */
92
+ /* ------------------------------------------------------------------ */
93
+
94
+ typedef struct {
95
+ int fds[CERVER_CONN_QUEUE_SIZE];
96
+ int head;
97
+ int tail;
98
+ int count;
99
+ pthread_mutex_t lock;
100
+ pthread_cond_t not_empty;
101
+ } conn_queue_t;
102
+
103
+ static void cq_init(conn_queue_t *q) {
104
+ memset(q->fds, -1, sizeof(q->fds));
105
+ q->head = q->tail = q->count = 0;
106
+ pthread_mutex_init(&q->lock, NULL);
107
+ pthread_cond_init(&q->not_empty, NULL);
108
+ }
109
+
110
+ static void cq_destroy(conn_queue_t *q) {
111
+ pthread_mutex_destroy(&q->lock);
112
+ pthread_cond_destroy(&q->not_empty);
113
+ }
114
+
115
+ /* Returns 0 on success, -1 if queue is full (drop connection). */
116
+ static int cq_push(conn_queue_t *q, int fd) {
117
+ pthread_mutex_lock(&q->lock);
118
+ if (q->count >= CERVER_CONN_QUEUE_SIZE) {
119
+ pthread_mutex_unlock(&q->lock);
120
+ return -1;
121
+ }
122
+ q->fds[q->tail] = fd;
123
+ q->tail = (q->tail + 1) % CERVER_CONN_QUEUE_SIZE;
124
+ q->count++;
125
+ pthread_cond_signal(&q->not_empty);
126
+ pthread_mutex_unlock(&q->lock);
127
+ return 0;
128
+ }
129
+
130
+ /* Returns fd, or -1 on shutdown. */
131
+ static int cq_pop(conn_queue_t *q, volatile int *running) {
132
+ pthread_mutex_lock(&q->lock);
133
+ while (q->count == 0 && *running) {
134
+ /* Timed wait so we can check running flag periodically */
135
+ struct timespec ts;
136
+ clock_gettime(CLOCK_REALTIME, &ts);
137
+ ts.tv_sec += 1;
138
+ pthread_cond_timedwait(&q->not_empty, &q->lock, &ts);
139
+ }
140
+ if (q->count == 0) {
141
+ pthread_mutex_unlock(&q->lock);
142
+ return -1;
143
+ }
144
+ int fd = q->fds[q->head];
145
+ q->head = (q->head + 1) % CERVER_CONN_QUEUE_SIZE;
146
+ q->count--;
147
+ pthread_mutex_unlock(&q->lock);
148
+ return fd;
149
+ }
150
+
151
+ /* Global connection queue */
152
+ static conn_queue_t g_conn_queue;
153
+
154
+ /* ------------------------------------------------------------------ */
155
+ /* Buffered read */
75
156
  /* ------------------------------------------------------------------ */
76
157
 
77
158
  static char *read_full_request(int fd, size_t *out_len) {
78
159
  size_t cap = CERVER_READ_BUF;
79
160
  size_t len = 0;
80
- char *buf = malloc(cap + 1); /* +1 for null terminator */
161
+ char *buf = malloc(cap + 1);
81
162
  if (!buf) return NULL;
82
163
 
83
164
  while (len < (size_t)CERVER_READ_BUF_MAX) {
84
165
  ssize_t n = read(fd, buf + len, cap - len);
85
166
  if (n <= 0) break;
86
167
  len += (size_t)n;
87
-
88
- /* Check for end of headers */
89
168
  if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4)) break;
90
-
91
- /* Grow buffer if full */
92
169
  if (len == cap) {
93
170
  size_t newcap = cap * 2;
94
171
  if (newcap > (size_t)CERVER_READ_BUF_MAX)
@@ -100,157 +177,310 @@ static char *read_full_request(int fd, size_t *out_len) {
100
177
  }
101
178
  }
102
179
 
103
- if (len == 0) {
104
- free(buf);
105
- return NULL;
106
- }
107
-
180
+ if (len == 0) { free(buf); return NULL; }
108
181
  buf[len] = '\0';
109
182
  *out_len = len;
110
183
  return buf;
111
184
  }
112
185
 
113
186
  /* ------------------------------------------------------------------ */
114
- /* Handle a single connection: read, parse, dispatch, respond */
187
+ /* Handle connection with keep-alive */
115
188
  /* ------------------------------------------------------------------ */
116
189
 
117
190
  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
191
  int flags = fcntl(client_fd, F_GETFL, 0);
121
- if (flags >= 0 && (flags & O_NONBLOCK)) {
192
+ if (flags >= 0 && (flags & O_NONBLOCK))
122
193
  fcntl(client_fd, F_SETFL, flags & ~O_NONBLOCK);
123
- }
124
194
 
125
- /* Set a read timeout so workers don't block on stale connections */
126
- struct timeval tv = { 5, 0 }; /* 5 seconds */
127
- setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
195
+ int nodelay = 1;
196
+ setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
128
197
 
129
- /* Read the full request with buffering */
130
- size_t req_len = 0;
131
- char *buf = read_full_request(client_fd, &req_len);
198
+ int request_count = 0;
199
+ int keepalive = 1;
132
200
 
133
- if (!buf || req_len == 0) {
134
- if (buf) free(buf);
135
- close(client_fd);
136
- return;
137
- }
201
+ while (keepalive && srv->running && request_count < CERVER_KEEPALIVE_MAX) {
202
+ struct timeval tv;
203
+ tv.tv_sec = (request_count == 0) ? 5 : CERVER_KEEPALIVE_TIMEOUT;
204
+ tv.tv_usec = 0;
205
+ setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
138
206
 
139
- /* Parse HTTP request */
140
- cerver_request_t req;
141
- memset(&req, 0, sizeof(req));
207
+ size_t req_len = 0;
208
+ char *buf = read_full_request(client_fd, &req_len);
209
+ if (!buf || req_len == 0) {
210
+ if (buf) free(buf);
211
+ break;
212
+ }
142
213
 
143
- 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;
150
- }
214
+ cerver_request_t req;
215
+ memset(&req, 0, sizeof(req));
151
216
 
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);
158
-
159
- /* Prepare response */
160
- cerver_response_t res;
161
- memset(&res, 0, sizeof(res));
162
-
163
- /* Try static assets first — files take priority over route handlers */
164
- 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");
217
+ if (cerver_parse_request(buf, req_len, &req) < 0) {
218
+ const char *resp = "HTTP/1.1 400 Bad Request\r\n"
219
+ "Content-Length: 11\r\nConnection: close\r\n\r\nBad Request";
220
+ write(client_fd, resp, strlen(resp));
221
+ free(buf);
222
+ break;
173
223
  }
174
- }
175
224
 
176
- /* Write response */
177
- cerver_write_response(client_fd, &res);
225
+ request_count++;
226
+ keepalive = !cerver_req_wants_close(&req);
178
227
 
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);
228
+ cerver_response_t res;
229
+ memset(&res, 0, sizeof(res));
230
+
231
+ if (cerver_serve_static(srv, &req, &res) < 0) {
232
+ cerver_handler_fn handler = cerver_dispatch(srv, &req);
233
+ if (handler) {
234
+ handler(&req, &res);
235
+ } else {
236
+ cerver_res_text(&res, 404, "Not Found");
237
+ }
238
+ }
239
+
240
+ if (res._force_close) keepalive = 0;
241
+
242
+ int write_err = cerver_write_response(client_fd, &res, keepalive);
243
+
244
+ if (res._body_owned == 1 && res.body) free((void *)res.body);
245
+ else if (res._body_owned == 2 && res.body) munmap((void *)res.body, res.body_len);
246
+
247
+ free(buf);
248
+ if (write_err < 0) break;
185
249
  }
186
250
 
187
251
  close(client_fd);
188
252
  }
189
253
 
190
254
  /* ------------------------------------------------------------------ */
191
- /* Thread pool task queue (ring buffer) */
255
+ /* Connection pool worker thread */
192
256
  /* ------------------------------------------------------------------ */
193
257
 
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;
258
+ static void *conn_pool_worker(void *arg) {
259
+ cerver_server_t *srv = (cerver_server_t *)arg;
260
+
261
+ while (srv->running) {
262
+ int fd = cq_pop(&g_conn_queue, &srv->running);
263
+ if (fd < 0) continue;
264
+ handle_connection(srv, fd);
206
265
  }
266
+ return NULL;
267
+ }
207
268
 
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++;
269
+ /* ------------------------------------------------------------------ */
270
+ /* Create a listening socket */
271
+ /* ------------------------------------------------------------------ */
211
272
 
212
- pthread_cond_signal(&srv->tq_cond);
213
- pthread_mutex_unlock(&srv->tq_mutex);
214
- return 0;
215
- }
273
+ static int create_listener(int port, int reuseport) {
274
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
275
+ if (fd < 0) { perror("cerver: socket"); return -1; }
216
276
 
217
- static int dequeue_task(cerver_server_t *srv) {
218
- pthread_mutex_lock(&srv->tq_mutex);
277
+ int opt = 1;
278
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
219
279
 
220
- while (srv->tq_count == 0 && srv->running) {
221
- pthread_cond_wait(&srv->tq_cond, &srv->tq_mutex);
222
- }
280
+ #ifdef __linux__
281
+ if (reuseport)
282
+ setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
283
+ #else
284
+ (void)reuseport;
285
+ #endif
223
286
 
224
- if (!srv->running && srv->tq_count == 0) {
225
- pthread_mutex_unlock(&srv->tq_mutex);
226
- return -1; /* shutdown signal */
227
- }
287
+ struct sockaddr_in addr;
288
+ memset(&addr, 0, sizeof(addr));
289
+ addr.sin_family = AF_INET;
290
+ addr.sin_addr.s_addr = INADDR_ANY;
291
+ addr.sin_port = htons((uint16_t)port);
228
292
 
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--;
293
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
294
+ perror("cerver: bind"); close(fd); return -1;
295
+ }
296
+ if (listen(fd, CERVER_LISTEN_BACKLOG) < 0) {
297
+ perror("cerver: listen"); close(fd); return -1;
298
+ }
232
299
 
233
- pthread_mutex_unlock(&srv->tq_mutex);
300
+ set_nonblocking(fd);
234
301
  return fd;
235
302
  }
236
303
 
237
304
  /* ------------------------------------------------------------------ */
238
- /* Worker thread entry point */
305
+ /* Accept helper */
239
306
  /* ------------------------------------------------------------------ */
240
307
 
241
- static void *worker_thread(void *arg) {
242
- cerver_server_t *srv = (cerver_server_t *)arg;
308
+ static int accept_connection(int listen_fd) {
309
+ struct sockaddr_in ca;
310
+ socklen_t cl = sizeof(ca);
311
+ #ifdef __linux__
312
+ return accept4(listen_fd, (struct sockaddr *)&ca, &cl, SOCK_CLOEXEC);
313
+ #else
314
+ return accept(listen_fd, (struct sockaddr *)&ca, &cl);
315
+ #endif
316
+ }
317
+
318
+ /* ------------------------------------------------------------------ */
319
+ /* Acceptor event loops (one per core, accept-only, never block) */
320
+ /* ------------------------------------------------------------------ */
321
+
322
+ #if CERVER_USE_KQUEUE
323
+ static void *acceptor_loop(void *arg) {
324
+ cerver_worker_t *w = (cerver_worker_t *)arg;
325
+ cerver_server_t *srv = w->srv;
326
+
327
+ int kq = kqueue();
328
+ if (kq < 0) { perror("cerver: kqueue"); return NULL; }
329
+ w->event_fd = kq;
330
+
331
+ struct kevent change;
332
+ EV_SET(&change, w->listen_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
333
+ kevent(kq, &change, 1, NULL, 0, NULL);
334
+
335
+ struct kevent events[CERVER_MAX_EVENTS];
243
336
 
244
337
  while (srv->running) {
245
- int client_fd = dequeue_task(srv);
246
- if (client_fd < 0) break; /* shutdown */
338
+ struct timespec ts = { 1, 0 };
339
+ int nev = kevent(kq, NULL, 0, events, CERVER_MAX_EVENTS, &ts);
340
+ if (nev < 0) { if (errno == EINTR) continue; break; }
247
341
 
248
- handle_connection(srv, client_fd);
342
+ for (int i = 0; i < nev; i++) {
343
+ if ((int)events[i].ident == w->listen_fd) {
344
+ while (1) {
345
+ int cfd = accept_connection(w->listen_fd);
346
+ if (cfd < 0) {
347
+ if (errno == EAGAIN || errno == EWOULDBLOCK) break;
348
+ break;
349
+ }
350
+ if (cq_push(&g_conn_queue, cfd) < 0) {
351
+ /* Queue full — send 503 and close */
352
+ const char *r = "HTTP/1.1 503 Service Unavailable\r\n"
353
+ "Content-Length: 19\r\nConnection: close\r\n\r\n"
354
+ "Service Unavailable";
355
+ write(cfd, r, strlen(r));
356
+ close(cfd);
357
+ }
358
+ }
359
+ }
360
+ }
249
361
  }
250
362
 
363
+ close(kq);
251
364
  return NULL;
252
365
  }
253
366
 
367
+ #elif CERVER_USE_EPOLL
368
+ static void *acceptor_loop(void *arg) {
369
+ cerver_worker_t *w = (cerver_worker_t *)arg;
370
+ cerver_server_t *srv = w->srv;
371
+
372
+ int ep = epoll_create1(EPOLL_CLOEXEC);
373
+ if (ep < 0) { perror("cerver: epoll"); return NULL; }
374
+ w->event_fd = ep;
375
+
376
+ struct epoll_event ev = { .events = EPOLLIN, .data.fd = w->listen_fd };
377
+ epoll_ctl(ep, EPOLL_CTL_ADD, w->listen_fd, &ev);
378
+
379
+ struct epoll_event events[CERVER_MAX_EVENTS];
380
+
381
+ while (srv->running) {
382
+ int nev = epoll_wait(ep, events, CERVER_MAX_EVENTS, 1000);
383
+ if (nev < 0) { if (errno == EINTR) continue; break; }
384
+
385
+ for (int i = 0; i < nev; i++) {
386
+ if (events[i].data.fd == w->listen_fd) {
387
+ while (1) {
388
+ int cfd = accept_connection(w->listen_fd);
389
+ if (cfd < 0) {
390
+ if (errno == EAGAIN || errno == EWOULDBLOCK) break;
391
+ break;
392
+ }
393
+ if (cq_push(&g_conn_queue, cfd) < 0) {
394
+ const char *r = "HTTP/1.1 503 Service Unavailable\r\n"
395
+ "Content-Length: 19\r\nConnection: close\r\n\r\n"
396
+ "Service Unavailable";
397
+ write(cfd, r, strlen(r));
398
+ close(cfd);
399
+ }
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ close(ep);
406
+ return NULL;
407
+ }
408
+
409
+ #else /* SELECT */
410
+ static void *acceptor_loop(void *arg) {
411
+ cerver_worker_t *w = (cerver_worker_t *)arg;
412
+ cerver_server_t *srv = w->srv;
413
+
414
+ while (srv->running) {
415
+ fd_set rfds;
416
+ FD_ZERO(&rfds);
417
+ FD_SET(w->listen_fd, &rfds);
418
+ struct timeval tv = { 1, 0 };
419
+ int ret = select(w->listen_fd + 1, &rfds, NULL, NULL, &tv);
420
+ if (ret < 0) { if (errno == EINTR) continue; break; }
421
+ if (ret > 0 && FD_ISSET(w->listen_fd, &rfds)) {
422
+ int cfd = accept_connection(w->listen_fd);
423
+ if (cfd >= 0) {
424
+ if (cq_push(&g_conn_queue, cfd) < 0) {
425
+ close(cfd);
426
+ }
427
+ }
428
+ }
429
+ }
430
+ return NULL;
431
+ }
432
+ #endif
433
+
434
+ /* ------------------------------------------------------------------ */
435
+ /* Stat cache */
436
+ /* ------------------------------------------------------------------ */
437
+
438
+ void cerver_stat_cache_init(cerver_stat_cache_t *cache) {
439
+ memset(cache, 0, sizeof(*cache));
440
+ pthread_mutex_init(&cache->lock, NULL);
441
+ }
442
+
443
+ int cerver_stat_cache_lookup(cerver_stat_cache_t *cache, const char *path,
444
+ size_t *file_size) {
445
+ time_t now = time(NULL);
446
+ pthread_mutex_lock(&cache->lock);
447
+ for (int i = 0; i < CERVER_STAT_CACHE_SIZE; i++) {
448
+ cerver_stat_entry_t *e = &cache->entries[i];
449
+ if (e->valid && strcmp(e->path, path) == 0) {
450
+ if (now - e->cached_at < CERVER_STAT_CACHE_TTL) {
451
+ *file_size = e->file_size;
452
+ pthread_mutex_unlock(&cache->lock);
453
+ return 0;
454
+ }
455
+ e->valid = 0;
456
+ break;
457
+ }
458
+ }
459
+ pthread_mutex_unlock(&cache->lock);
460
+ return -1;
461
+ }
462
+
463
+ void cerver_stat_cache_store(cerver_stat_cache_t *cache, const char *path,
464
+ size_t file_size, time_t mtime) {
465
+ time_t now = time(NULL);
466
+ pthread_mutex_lock(&cache->lock);
467
+ int best = 0;
468
+ time_t oldest = cache->entries[0].cached_at;
469
+ for (int i = 0; i < CERVER_STAT_CACHE_SIZE; i++) {
470
+ cerver_stat_entry_t *e = &cache->entries[i];
471
+ if (!e->valid) { best = i; break; }
472
+ if (e->cached_at < oldest) { oldest = e->cached_at; best = i; }
473
+ }
474
+ cerver_stat_entry_t *slot = &cache->entries[best];
475
+ strncpy(slot->path, path, sizeof(slot->path) - 1);
476
+ slot->path[sizeof(slot->path) - 1] = '\0';
477
+ slot->file_size = file_size;
478
+ slot->mtime = mtime;
479
+ slot->cached_at = now;
480
+ slot->valid = 1;
481
+ pthread_mutex_unlock(&cache->lock);
482
+ }
483
+
254
484
  /* ------------------------------------------------------------------ */
255
485
  /* Server init */
256
486
  /* ------------------------------------------------------------------ */
@@ -261,17 +491,10 @@ int cerver_init(cerver_server_t *srv, int port, int threads) {
261
491
  srv->sock_fd = -1;
262
492
  srv->running = 0;
263
493
  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;
271
-
272
- pthread_mutex_init(&srv->tq_mutex, NULL);
273
- pthread_cond_init(&srv->tq_cond, NULL);
274
-
494
+ srv->dispatch_override = NULL;
495
+ srv->worker_count = (threads > 0) ? threads : get_cpu_count();
496
+ srv->workers = NULL;
497
+ cerver_stat_cache_init(&srv->stat_cache);
275
498
  return 0;
276
499
  }
277
500
 
@@ -292,81 +515,13 @@ void cerver_set_public_dir(cerver_server_t *srv, const char *dir) {
292
515
  }
293
516
 
294
517
  /* ------------------------------------------------------------------ */
295
- /* Start thread pool */
296
- /* ------------------------------------------------------------------ */
297
-
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;
303
- }
304
-
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;
322
- }
323
- }
324
-
325
- pthread_attr_destroy(&attr);
326
-
327
- printf("cerver: started %d worker thread(s)\n", srv->thread_count);
328
- return 0;
329
- }
330
-
331
- /* ------------------------------------------------------------------ */
332
- /* Server listen — event loop */
518
+ /* Server listen */
333
519
  /* ------------------------------------------------------------------ */
334
520
 
335
521
  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
- }
342
-
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;
358
- }
359
-
360
- /* Listen */
361
- if (listen(srv->sock_fd, 128) < 0) {
362
- perror("cerver: listen");
363
- close(srv->sock_fd);
364
- return -1;
365
- }
522
+ srv->sock_fd = create_listener(srv->port, 0);
523
+ if (srv->sock_fd < 0) return -1;
366
524
 
367
- set_nonblocking(srv->sock_fd);
368
-
369
- /* Install signal handlers */
370
525
  g_srv = srv;
371
526
  signal(SIGINT, signal_handler);
372
527
  signal(SIGTERM, signal_handler);
@@ -374,147 +529,97 @@ int cerver_listen(cerver_server_t *srv) {
374
529
 
375
530
  srv->running = 1;
376
531
 
377
- printf("cerver: listening on http://localhost:%d\n", srv->port);
532
+ /* Determine pool and acceptor counts.
533
+ * Acceptors: min(worker_count, cpu_count) — one per core for accept.
534
+ * Pool workers: worker_count * 16 — enough to cover concurrent keep-alive. */
535
+ int cpu_count = get_cpu_count();
536
+ int acceptor_count = cpu_count;
537
+ if (acceptor_count > srv->worker_count) acceptor_count = srv->worker_count;
538
+ if (acceptor_count < 1) acceptor_count = 1;
378
539
 
379
- /* Start the thread pool */
380
- if (start_thread_pool(srv) < 0) {
381
- close(srv->sock_fd);
382
- return -1;
383
- }
540
+ int pool_size = srv->worker_count * 16;
541
+ if (pool_size < CERVER_CONN_POOL_SIZE) pool_size = CERVER_CONN_POOL_SIZE;
542
+ if (pool_size > 1024) pool_size = 1024;
384
543
 
385
- /* ================================================================== */
386
- /* kqueue event loop (macOS / FreeBSD) */
387
- /* ================================================================== */
388
- #if CERVER_USE_KQUEUE
389
-
390
- int kq = kqueue();
391
- if (kq < 0) {
392
- perror("cerver: kqueue");
393
- close(srv->sock_fd);
394
- return -1;
395
- }
396
-
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);
544
+ printf("cerver: listening on http://localhost:%d\n", srv->port);
545
+ printf("cerver: %d acceptor(s), %d connection workers, keep-alive max %d req/conn\n",
546
+ acceptor_count, pool_size, CERVER_KEEPALIVE_MAX);
400
547
 
401
- struct kevent events[MAX_EVENTS];
548
+ /* Init shared connection queue */
549
+ cq_init(&g_conn_queue);
402
550
 
403
- while (srv->running) {
404
- struct timespec timeout = { 1, 0 }; /* 1 second */
405
- int nev = kevent(kq, NULL, 0, events, MAX_EVENTS, &timeout);
551
+ /* Start connection pool workers */
552
+ pthread_t *pool_threads = calloc((size_t)pool_size, sizeof(pthread_t));
553
+ if (!pool_threads) { perror("cerver: calloc pool"); close(srv->sock_fd); return -1; }
406
554
 
407
- if (nev < 0) {
408
- if (errno == EINTR) continue;
409
- perror("cerver: kevent");
410
- break;
411
- }
412
-
413
- for (int i = 0; i < nev; i++) {
414
- int fd = (int)events[i].ident;
555
+ pthread_attr_t attr;
556
+ pthread_attr_init(&attr);
557
+ pthread_attr_setstacksize(&attr, 2 * 1024 * 1024);
415
558
 
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
- }
559
+ for (int i = 0; i < pool_size; i++) {
560
+ if (pthread_create(&pool_threads[i], &attr, conn_pool_worker, srv) != 0) {
561
+ perror("cerver: pool thread create");
562
+ srv->running = 0;
563
+ for (int j = 0; j < i; j++) pthread_join(pool_threads[j], NULL);
564
+ free(pool_threads);
565
+ close(srv->sock_fd);
566
+ pthread_attr_destroy(&attr);
567
+ return -1;
432
568
  }
433
569
  }
434
570
 
435
- close(kq);
436
-
437
- /* ================================================================== */
438
- /* epoll event loop (Linux) */
439
- /* ================================================================== */
440
- #elif CERVER_USE_EPOLL
441
-
442
- int ep = epoll_create1(0);
443
- if (ep < 0) {
444
- perror("cerver: epoll_create1");
571
+ /* Start acceptor threads */
572
+ srv->workers = calloc((size_t)acceptor_count, sizeof(cerver_worker_t));
573
+ srv->worker_count = acceptor_count;
574
+ if (!srv->workers) {
575
+ perror("cerver: calloc acceptors");
576
+ srv->running = 0;
577
+ pthread_cond_broadcast(&g_conn_queue.not_empty);
578
+ for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
579
+ free(pool_threads);
445
580
  close(srv->sock_fd);
581
+ pthread_attr_destroy(&attr);
446
582
  return -1;
447
583
  }
448
584
 
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");
585
+ for (int i = 0; i < acceptor_count; i++) {
586
+ cerver_worker_t *w = &srv->workers[i];
587
+ w->id = i;
588
+ w->srv = srv;
589
+ w->event_fd = -1;
590
+ #ifdef __linux__
591
+ w->listen_fd = create_listener(srv->port, 1);
592
+ if (w->listen_fd < 0) w->listen_fd = srv->sock_fd;
593
+ #else
594
+ w->listen_fd = srv->sock_fd;
595
+ #endif
596
+ if (pthread_create(&w->thread, &attr, acceptor_loop, w) != 0) {
597
+ perror("cerver: acceptor create");
598
+ srv->running = 0;
599
+ for (int j = 0; j < i; j++) pthread_join(srv->workers[j].thread, NULL);
462
600
  break;
463
601
  }
464
-
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
602
  }
483
603
 
484
- close(ep);
485
-
486
- /* ================================================================== */
487
- /* select() fallback */
488
- /* ================================================================== */
489
- #elif CERVER_USE_SELECT
604
+ pthread_attr_destroy(&attr);
490
605
 
491
- while (srv->running) {
492
- fd_set readfds;
493
- FD_ZERO(&readfds);
494
- FD_SET(srv->sock_fd, &readfds);
606
+ /* Main thread waits for shutdown */
607
+ while (srv->running) sleep(1);
495
608
 
496
- struct timeval timeout = { 1, 0 };
497
- int ret = select(srv->sock_fd + 1, &readfds, NULL, NULL, &timeout);
609
+ /* Shutdown: stop acceptors first, then drain pool */
610
+ srv->running = 0;
498
611
 
499
- if (ret < 0) {
500
- if (errno == EINTR) continue;
501
- perror("cerver: select");
502
- break;
503
- }
612
+ for (int i = 0; i < acceptor_count; i++)
613
+ pthread_join(srv->workers[i].thread, NULL);
504
614
 
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
- }
615
+ /* Wake all pool workers */
616
+ pthread_mutex_lock(&g_conn_queue.lock);
617
+ pthread_cond_broadcast(&g_conn_queue.not_empty);
618
+ pthread_mutex_unlock(&g_conn_queue.lock);
516
619
 
517
- #endif
620
+ for (int i = 0; i < pool_size; i++)
621
+ pthread_join(pool_threads[i], NULL);
622
+ free(pool_threads);
518
623
 
519
624
  cerver_shutdown(srv);
520
625
  return 0;
@@ -527,29 +632,24 @@ int cerver_listen(cerver_server_t *srv) {
527
632
  void cerver_shutdown(cerver_server_t *srv) {
528
633
  srv->running = 0;
529
634
 
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);
534
-
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);
635
+ if (srv->workers) {
636
+ for (int i = 0; i < srv->worker_count; i++) {
637
+ #ifdef __linux__
638
+ if (srv->workers[i].listen_fd != srv->sock_fd &&
639
+ srv->workers[i].listen_fd >= 0)
640
+ close(srv->workers[i].listen_fd);
641
+ #endif
642
+ if (srv->workers[i].event_fd >= 0)
643
+ close(srv->workers[i].event_fd);
539
644
  }
540
- free(srv->threads);
541
- srv->threads = NULL;
645
+ free(srv->workers);
646
+ srv->workers = NULL;
542
647
  }
543
648
 
544
- /* Close listener socket */
545
- if (srv->sock_fd >= 0) {
546
- close(srv->sock_fd);
547
- srv->sock_fd = -1;
548
- }
649
+ if (srv->sock_fd >= 0) { close(srv->sock_fd); srv->sock_fd = -1; }
549
650
 
550
- /* Destroy synchronization primitives */
551
- pthread_mutex_destroy(&srv->tq_mutex);
552
- pthread_cond_destroy(&srv->tq_cond);
651
+ cq_destroy(&g_conn_queue);
652
+ pthread_mutex_destroy(&srv->stat_cache.lock);
553
653
 
554
654
  printf("\ncerver: server stopped\n");
555
655
  }