@velox0/cerver 0.4.1 → 0.4.3

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/router.c CHANGED
@@ -11,6 +11,7 @@
11
11
  #include <stdio.h>
12
12
  #include <stdlib.h>
13
13
  #include <string.h>
14
+ #include <strings.h>
14
15
 
15
16
  /* ------------------------------------------------------------------ */
16
17
  /* Request accessor helpers */
@@ -18,8 +19,15 @@
18
19
 
19
20
  const char* cerver_req_param(const cerver_request_t* req, const char* key) {
20
21
  for (int i = 0; i < req->params_count; i++) {
21
- if (strcmp(req->params[i].key, key) == 0) {
22
- return req->params[i].value;
22
+ const char* pkey = req->params[i].key;
23
+ if (pkey && pkey[0] == key[0]) {
24
+ int j = 0;
25
+ while (key[j] && pkey[j] == key[j]) {
26
+ j++;
27
+ }
28
+ if (key[j] == '\0' && (pkey[j] == '\0' || pkey[j] == '/')) {
29
+ return req->params[i].value;
30
+ }
23
31
  }
24
32
  }
25
33
  return "";
@@ -84,7 +92,7 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
84
92
  }
85
93
 
86
94
  const char* pattern = route->pattern;
87
- const char* path = req->path;
95
+ const char* path = req->path;
88
96
 
89
97
  /* Fast path: exact match */
90
98
  if (strcmp(pattern, path) == 0) {
@@ -101,6 +109,8 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
101
109
  const char* rp = path; /* request path pointer */
102
110
 
103
111
  int saved_params = req->params_count;
112
+ char* param_slashes[CERVER_MAX_PARAMS];
113
+ int param_slash_count = 0;
104
114
 
105
115
  /* Skip leading '/' */
106
116
  if (*pp == '/') pp++;
@@ -121,10 +131,12 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
121
131
  /* Dynamic segment — extract parameter */
122
132
  if (req->params_count < CERVER_MAX_PARAMS) {
123
133
  req->params[req->params_count].key = pp_seg + 1;
124
- /* Temporarily NUL-terminate the key at the slash */
125
- /* The key points into the route pattern (static/const) */
134
+ /* The key points into the route pattern (static/const, not NUL-terminated) */
126
135
  req->params[req->params_count].value = rp_seg;
127
136
  req->params_count++;
137
+ if (*rp == '/') {
138
+ param_slashes[param_slash_count++] = (char*)rp;
139
+ }
128
140
  }
129
141
  } else {
130
142
  /* Static segment — must match exactly */
@@ -145,6 +157,11 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
145
157
  return 0;
146
158
  }
147
159
 
160
+ /* Match succeeded, NUL-terminate extracted values in-place inside req->path */
161
+ for (int i = 0; i < param_slash_count; i++) {
162
+ *param_slashes[i] = '\0';
163
+ }
164
+
148
165
  return 1;
149
166
  }
150
167
 
@@ -152,6 +169,166 @@ int cerver_route_match(const cerver_route_t* route, cerver_request_t* req) {
152
169
  /* Dispatch: find and return the handler for a request */
153
170
  /* ------------------------------------------------------------------ */
154
171
 
172
+ /* ------------------------------------------------------------------ */
173
+ /* Trie/Radix Route Router */
174
+ /* ------------------------------------------------------------------ */
175
+
176
+ static char* trie_strndup(const char* s, size_t n) {
177
+ char* p = malloc(n + 1);
178
+ if (p) {
179
+ memcpy(p, s, n);
180
+ p[n] = '\0';
181
+ }
182
+ return p;
183
+ }
184
+
185
+ typedef struct trie_node trie_node_t;
186
+
187
+ struct trie_node {
188
+ char* segment;
189
+ int is_param;
190
+ char* param_name;
191
+
192
+ struct {
193
+ const char* method;
194
+ cerver_handler_fn handler;
195
+ } handlers[16];
196
+ int handler_count;
197
+
198
+ trie_node_t** children;
199
+ int children_count;
200
+ int children_cap;
201
+ };
202
+
203
+ void* cerver_trie_create(void) {
204
+ trie_node_t* node = calloc(1, sizeof(trie_node_t));
205
+ return node;
206
+ }
207
+
208
+ static trie_node_t* trie_create_node(const char* segment, size_t len) {
209
+ trie_node_t* node = calloc(1, sizeof(trie_node_t));
210
+ if (node && segment) {
211
+ node->segment = trie_strndup(segment, len);
212
+ if (node->segment[0] == ':') {
213
+ node->is_param = 1;
214
+ node->param_name = trie_strndup(node->segment + 1, len - 1);
215
+ }
216
+ }
217
+ return node;
218
+ }
219
+
220
+ void cerver_trie_insert(void* trie, const char* pattern, const char* method, cerver_handler_fn handler) {
221
+ if (!trie) return;
222
+ trie_node_t* curr = (trie_node_t*)trie;
223
+ const char* p = pattern;
224
+ while (*p == '/') p++;
225
+
226
+ while (*p) {
227
+ const char* seg_start = p;
228
+ while (*p && *p != '/') p++;
229
+ size_t len = (size_t)(p - seg_start);
230
+ if (len == 0) {
231
+ while (*p == '/') p++;
232
+ continue;
233
+ }
234
+
235
+ // Find if child exists
236
+ trie_node_t* child = NULL;
237
+ for (int i = 0; i < curr->children_count; i++) {
238
+ trie_node_t* c = curr->children[i];
239
+ if (strlen(c->segment) == len && memcmp(c->segment, seg_start, len) == 0) {
240
+ child = c;
241
+ break;
242
+ }
243
+ }
244
+
245
+ if (!child) {
246
+ child = trie_create_node(seg_start, len);
247
+ if (curr->children_count >= curr->children_cap) {
248
+ curr->children_cap = curr->children_cap == 0 ? 4 : curr->children_cap * 2;
249
+ curr->children = realloc(curr->children, curr->children_cap * sizeof(trie_node_t*));
250
+ }
251
+ curr->children[curr->children_count++] = child;
252
+ }
253
+
254
+ curr = child;
255
+ while (*p == '/') p++;
256
+ }
257
+
258
+ // Add handler to leaf
259
+ if (curr->handler_count < 16) {
260
+ curr->handlers[curr->handler_count].method = method;
261
+ curr->handlers[curr->handler_count].handler = handler;
262
+ curr->handler_count++;
263
+ }
264
+ }
265
+
266
+ void cerver_trie_free(void* trie) {
267
+ if (!trie) return;
268
+ trie_node_t* node = (trie_node_t*)trie;
269
+ for (int i = 0; i < node->children_count; i++) {
270
+ cerver_trie_free(node->children[i]);
271
+ }
272
+ free(node->children);
273
+ free(node->segment);
274
+ free(node->param_name);
275
+ free(node);
276
+ }
277
+
278
+ static int trie_match_recursive(trie_node_t* node, const char* path, cerver_request_t* req, cerver_handler_fn* out_handler, int param_start_idx) {
279
+ // Skip leading slashes
280
+ while (*path == '/') path++;
281
+
282
+ if (*path == '\0') {
283
+ // Check if node has a handler for req->method
284
+ for (int i = 0; i < node->handler_count; i++) {
285
+ if (strcmp(node->handlers[i].method, req->method) == 0) {
286
+ *out_handler = node->handlers[i].handler;
287
+ req->params_count = param_start_idx;
288
+ return 1;
289
+ }
290
+ }
291
+ return 0;
292
+ }
293
+
294
+ // Extract next segment from path
295
+ const char* seg_start = path;
296
+ while (*path && *path != '/') path++;
297
+ size_t seg_len = (size_t)(path - seg_start);
298
+
299
+ // Try static children first
300
+ for (int i = 0; i < node->children_count; i++) {
301
+ trie_node_t* child = node->children[i];
302
+ if (!child->is_param) {
303
+ if (strlen(child->segment) == seg_len && memcmp(child->segment, seg_start, seg_len) == 0) {
304
+ if (trie_match_recursive(child, path, req, out_handler, param_start_idx)) {
305
+ return 1;
306
+ }
307
+ }
308
+ }
309
+ }
310
+
311
+ // Try parameter/dynamic children next
312
+ for (int i = 0; i < node->children_count; i++) {
313
+ trie_node_t* child = node->children[i];
314
+ if (child->is_param) {
315
+ if (param_start_idx < CERVER_MAX_PARAMS) {
316
+ req->params[param_start_idx].key = child->param_name;
317
+ req->params[param_start_idx].value = seg_start;
318
+ }
319
+ if (trie_match_recursive(child, path, req, out_handler, param_start_idx + 1)) {
320
+ return 1;
321
+ }
322
+ }
323
+ }
324
+
325
+ return 0;
326
+ }
327
+
328
+ /* ------------------------------------------------------------------ */
329
+ /* Dispatch: find and return the handler for a request */
330
+ /* ------------------------------------------------------------------ */
331
+
155
332
  cerver_handler_fn cerver_dispatch(cerver_server_t* srv, cerver_request_t* req) {
156
333
  /* Try the generated compile-time dispatch first */
157
334
  if (srv->dispatch_override) {
@@ -159,13 +336,19 @@ cerver_handler_fn cerver_dispatch(cerver_server_t* srv, cerver_request_t* req) {
159
336
  if (h) return h;
160
337
  }
161
338
 
162
- /* Fall back to generic route table scan */
163
- if (!srv->routes) return NULL;
339
+ /* Fall back to generic route table scan via Trie */
340
+ if (!srv->route_trie) return NULL;
164
341
 
165
- for (int i = 0; i < srv->route_count; i++) {
166
- if (cerver_route_match(&srv->routes[i], req)) {
167
- return srv->routes[i].handler;
342
+ cerver_handler_fn handler = NULL;
343
+ req->params_count = 0;
344
+ if (trie_match_recursive((trie_node_t*)srv->route_trie, req->path, req, &handler, 0)) {
345
+ // NUL-terminate extracted values in-place inside req->path
346
+ for (int i = 0; i < req->params_count; i++) {
347
+ char* val = (char*)req->params[i].value;
348
+ while (*val && *val != '/') val++;
349
+ if (*val == '/') *val = '\0';
168
350
  }
351
+ return handler;
169
352
  }
170
353
 
171
354
  return NULL;
package/runtime/server.c CHANGED
@@ -20,6 +20,7 @@
20
20
  #include <fcntl.h>
21
21
  #include <time.h>
22
22
  #include <sys/mman.h>
23
+ #include <sys/time.h>
23
24
  #include <sys/socket.h>
24
25
  #include <sys/types.h>
25
26
  #include <netinet/in.h>
@@ -55,7 +56,7 @@
55
56
  static void* cerver_memmem(const void* hay, size_t haylen, const void* needle, size_t nlen) {
56
57
  if (nlen == 0) return (void*)hay;
57
58
  if (nlen > haylen) return NULL;
58
- const char* p = (const char*)hay;
59
+ const char* p = (const char*)hay;
59
60
  const char* end = p + haylen - nlen;
60
61
  for (; p <= end; p++) {
61
62
  if (memcmp(p, needle, nlen) == 0) return (void*)p;
@@ -91,12 +92,12 @@ static int get_cpu_count(void) {
91
92
  /* ------------------------------------------------------------------ */
92
93
 
93
94
  typedef struct {
94
- int fds[CERVER_CONN_QUEUE_SIZE];
95
- int head;
96
- int tail;
97
- int count;
95
+ int fds[CERVER_CONN_QUEUE_SIZE];
96
+ int head;
97
+ int tail;
98
+ int count;
98
99
  pthread_mutex_t lock;
99
- pthread_cond_t not_empty;
100
+ pthread_cond_t not_empty;
100
101
  } conn_queue_t;
101
102
 
102
103
  static void cq_init(conn_queue_t* q) {
@@ -119,7 +120,7 @@ static int cq_push(conn_queue_t* q, int fd) {
119
120
  return -1;
120
121
  }
121
122
  q->fds[q->tail] = fd;
122
- q->tail = (q->tail + 1) % CERVER_CONN_QUEUE_SIZE;
123
+ q->tail = (q->tail + 1) % CERVER_CONN_QUEUE_SIZE;
123
124
  q->count++;
124
125
  pthread_cond_signal(&q->not_empty);
125
126
  pthread_mutex_unlock(&q->lock);
@@ -140,7 +141,7 @@ static int cq_pop(conn_queue_t* q, volatile int* running) {
140
141
  pthread_mutex_unlock(&q->lock);
141
142
  return -1;
142
143
  }
143
- int fd = q->fds[q->head];
144
+ int fd = q->fds[q->head];
144
145
  q->head = (q->head + 1) % CERVER_CONN_QUEUE_SIZE;
145
146
  q->count--;
146
147
  pthread_mutex_unlock(&q->lock);
@@ -152,31 +153,31 @@ static conn_queue_t g_conn_queue;
152
153
 
153
154
  /* Global worker readiness tracking */
154
155
  typedef struct {
155
- int workers_ready;
156
- int workers_expected;
156
+ int workers_ready;
157
+ int workers_expected;
157
158
  pthread_mutex_t lock;
158
- pthread_cond_t ready_cv;
159
+ pthread_cond_t ready_cv;
159
160
  } worker_readiness_t;
160
161
 
161
- static worker_readiness_t g_worker_readiness = {.workers_ready = 0,
162
+ static worker_readiness_t g_worker_readiness = {.workers_ready = 0,
162
163
  .workers_expected = 0,
163
- .lock = PTHREAD_MUTEX_INITIALIZER,
164
- .ready_cv = PTHREAD_COND_INITIALIZER};
164
+ .lock = PTHREAD_MUTEX_INITIALIZER,
165
+ .ready_cv = PTHREAD_COND_INITIALIZER};
165
166
 
166
167
  /* Global acceptor startup tracking */
167
168
  typedef struct {
168
- int acceptors_ready;
169
- int start_accepting;
169
+ int acceptors_ready;
170
+ int start_accepting;
170
171
  pthread_mutex_t lock;
171
- pthread_cond_t ready_cv;
172
- pthread_cond_t start_cv;
172
+ pthread_cond_t ready_cv;
173
+ pthread_cond_t start_cv;
173
174
  } acceptor_readiness_t;
174
175
 
175
176
  static acceptor_readiness_t g_acceptor_readiness = {.acceptors_ready = 0,
176
177
  .start_accepting = 0,
177
- .lock = PTHREAD_MUTEX_INITIALIZER,
178
- .ready_cv = PTHREAD_COND_INITIALIZER,
179
- .start_cv = PTHREAD_COND_INITIALIZER};
178
+ .lock = PTHREAD_MUTEX_INITIALIZER,
179
+ .ready_cv = PTHREAD_COND_INITIALIZER,
180
+ .start_cv = PTHREAD_COND_INITIALIZER};
180
181
 
181
182
  /* ------------------------------------------------------------------ */
182
183
  /* Buffered read */
@@ -185,7 +186,7 @@ static acceptor_readiness_t g_acceptor_readiness = {.acceptors_ready = 0,
185
186
  static char* read_full_request(int fd, size_t* out_len) {
186
187
  size_t cap = CERVER_READ_BUF;
187
188
  size_t len = 0;
188
- char* buf = malloc(cap + 1);
189
+ char* buf = malloc(cap + 1);
189
190
  if (!buf) return NULL;
190
191
 
191
192
  while (len < (size_t)CERVER_READ_BUF_MAX) {
@@ -227,16 +228,16 @@ static void handle_connection(cerver_server_t* srv, int client_fd) {
227
228
  setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
228
229
 
229
230
  int request_count = 0;
230
- int keepalive = 1;
231
+ int keepalive = 1;
231
232
 
232
233
  while (keepalive && srv->running && request_count < CERVER_KEEPALIVE_MAX) {
233
234
  struct timeval tv;
234
- tv.tv_sec = (request_count == 0) ? 5 : CERVER_KEEPALIVE_TIMEOUT;
235
+ tv.tv_sec = (request_count == 0) ? 5 : CERVER_KEEPALIVE_TIMEOUT;
235
236
  tv.tv_usec = 0;
236
237
  setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
237
238
 
238
239
  size_t req_len = 0;
239
- char* buf = read_full_request(client_fd, &req_len);
240
+ char* buf = read_full_request(client_fd, &req_len);
240
241
  if (!buf || req_len == 0) {
241
242
  if (buf) free(buf);
242
243
  break;
@@ -277,6 +278,8 @@ static void handle_connection(cerver_server_t* srv, int client_fd) {
277
278
  free((void*)res.body);
278
279
  else if (res._body_owned == 2 && res.body)
279
280
  munmap((void*)res.body, res.body_len);
281
+ else if (res._body_owned == 3 && res._file_fd >= 0)
282
+ close(res._file_fd);
280
283
 
281
284
  free(buf);
282
285
  if (write_err < 0) break;
@@ -347,7 +350,7 @@ static int create_listener(int port, int reuseport) {
347
350
  int opt = 1;
348
351
  setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
349
352
 
350
- #ifdef __linux__
353
+ #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
351
354
  if (reuseport) setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
352
355
  #else
353
356
  (void)reuseport;
@@ -355,9 +358,9 @@ static int create_listener(int port, int reuseport) {
355
358
 
356
359
  struct sockaddr_in addr;
357
360
  memset(&addr, 0, sizeof(addr));
358
- addr.sin_family = AF_INET;
361
+ addr.sin_family = AF_INET;
359
362
  addr.sin_addr.s_addr = INADDR_ANY;
360
- addr.sin_port = htons((uint16_t)port);
363
+ addr.sin_port = htons((uint16_t)port);
361
364
 
362
365
  if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
363
366
  perror("cerver: bind");
@@ -380,7 +383,7 @@ static int create_listener(int port, int reuseport) {
380
383
 
381
384
  static int accept_connection(int listen_fd) {
382
385
  struct sockaddr_in ca;
383
- socklen_t cl = sizeof(ca);
386
+ socklen_t cl = sizeof(ca);
384
387
  #ifdef __linux__
385
388
  return accept4(listen_fd, (struct sockaddr*)&ca, &cl, SOCK_CLOEXEC);
386
389
  #else
@@ -394,7 +397,7 @@ static int accept_connection(int listen_fd) {
394
397
 
395
398
  #if CERVER_USE_KQUEUE
396
399
  static void* acceptor_loop(void* arg) {
397
- cerver_worker_t* w = (cerver_worker_t*)arg;
400
+ cerver_worker_t* w = (cerver_worker_t*)arg;
398
401
  cerver_server_t* srv = w->srv;
399
402
 
400
403
  int kq = kqueue();
@@ -419,8 +422,8 @@ static void* acceptor_loop(void* arg) {
419
422
  pthread_mutex_unlock(&g_acceptor_readiness.lock);
420
423
 
421
424
  while (srv->running) {
422
- struct timespec ts = {1, 0};
423
- int nev = kevent(kq, NULL, 0, events, CERVER_MAX_EVENTS, &ts);
425
+ struct timespec ts = {1, 0};
426
+ int nev = kevent(kq, NULL, 0, events, CERVER_MAX_EVENTS, &ts);
424
427
  if (nev < 0) {
425
428
  if (errno == EINTR) continue;
426
429
  break;
@@ -454,7 +457,7 @@ static void* acceptor_loop(void* arg) {
454
457
 
455
458
  #elif CERVER_USE_EPOLL
456
459
  static void* acceptor_loop(void* arg) {
457
- cerver_worker_t* w = (cerver_worker_t*)arg;
460
+ cerver_worker_t* w = (cerver_worker_t*)arg;
458
461
  cerver_server_t* srv = w->srv;
459
462
 
460
463
  int ep = epoll_create1(EPOLL_CLOEXEC);
@@ -511,7 +514,7 @@ static void* acceptor_loop(void* arg) {
511
514
 
512
515
  #else /* SELECT */
513
516
  static void* acceptor_loop(void* arg) {
514
- cerver_worker_t* w = (cerver_worker_t*)arg;
517
+ cerver_worker_t* w = (cerver_worker_t*)arg;
515
518
  cerver_server_t* srv = w->srv;
516
519
 
517
520
  pthread_mutex_lock(&g_acceptor_readiness.lock);
@@ -526,8 +529,8 @@ static void* acceptor_loop(void* arg) {
526
529
  fd_set rfds;
527
530
  FD_ZERO(&rfds);
528
531
  FD_SET(w->listen_fd, &rfds);
529
- struct timeval tv = {1, 0};
530
- int ret = select(w->listen_fd + 1, &rfds, NULL, NULL, &tv);
532
+ struct timeval tv = {1, 0};
533
+ int ret = select(w->listen_fd + 1, &rfds, NULL, NULL, &tv);
531
534
  if (ret < 0) {
532
535
  if (errno == EINTR) continue;
533
536
  break;
@@ -577,7 +580,7 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
577
580
  time_t mtime) {
578
581
  time_t now = time(NULL);
579
582
  pthread_mutex_lock(&cache->lock);
580
- int best = 0;
583
+ int best = 0;
581
584
  time_t oldest = cache->entries[0].cached_at;
582
585
  for (int i = 0; i < CERVER_STAT_CACHE_SIZE; i++) {
583
586
  cerver_stat_entry_t* e = &cache->entries[i];
@@ -587,16 +590,16 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
587
590
  }
588
591
  if (e->cached_at < oldest) {
589
592
  oldest = e->cached_at;
590
- best = i;
593
+ best = i;
591
594
  }
592
595
  }
593
596
  cerver_stat_entry_t* slot = &cache->entries[best];
594
597
  strncpy(slot->path, path, sizeof(slot->path) - 1);
595
598
  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;
599
+ slot->file_size = file_size;
600
+ slot->mtime = mtime;
601
+ slot->cached_at = now;
602
+ slot->valid = 1;
600
603
  pthread_mutex_unlock(&cache->lock);
601
604
  }
602
605
 
@@ -606,25 +609,32 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
606
609
 
607
610
  int cerver_init(cerver_server_t* srv, int port, int threads) {
608
611
  memset(srv, 0, sizeof(*srv));
609
- srv->port = port;
610
- srv->sock_fd = -1;
611
- srv->running = 0;
612
- srv->public_dir = NULL;
612
+ srv->port = port;
613
+ srv->sock_fd = -1;
614
+ srv->running = 0;
615
+ srv->public_dir = NULL;
613
616
  srv->dispatch_override = NULL;
614
- srv->worker_count = (threads > 0) ? threads : get_cpu_count();
615
- srv->workers = NULL;
617
+ srv->worker_count = (threads > 0) ? threads : get_cpu_count();
618
+ srv->workers = NULL;
616
619
  cerver_stat_cache_init(&srv->stat_cache);
617
620
  return 0;
618
621
  }
619
622
 
620
623
  int cerver_add_routes(cerver_server_t* srv, cerver_route_t* routes, int count) {
621
- srv->routes = routes;
624
+ srv->routes = routes;
622
625
  srv->route_count = count;
626
+
627
+ srv->route_trie = cerver_trie_create();
628
+ if (srv->route_trie) {
629
+ for (int i = 0; i < count; i++) {
630
+ cerver_trie_insert(srv->route_trie, routes[i].pattern, routes[i].method, routes[i].handler);
631
+ }
632
+ }
623
633
  return 0;
624
634
  }
625
635
 
626
636
  int cerver_set_assets(cerver_server_t* srv, cerver_asset_t* assets, int count) {
627
- srv->assets = assets;
637
+ srv->assets = assets;
628
638
  srv->asset_count = count;
629
639
  return 0;
630
640
  }
@@ -646,7 +656,7 @@ int cerver_listen(cerver_server_t* srv) {
646
656
  /* Determine pool and acceptor counts.
647
657
  * Acceptors: min(worker_count, cpu_count) — one per core for accept.
648
658
  * Pool workers: worker_count * 16 — enough to cover concurrent keep-alive. */
649
- int cpu_count = get_cpu_count();
659
+ int cpu_count = get_cpu_count();
650
660
  int acceptor_count = cpu_count;
651
661
  if (acceptor_count > srv->worker_count) acceptor_count = srv->worker_count;
652
662
  if (acceptor_count < 1) acceptor_count = 1;
@@ -660,7 +670,7 @@ int cerver_listen(cerver_server_t* srv) {
660
670
 
661
671
  /* Initialize worker readiness tracking */
662
672
  pthread_mutex_lock(&g_worker_readiness.lock);
663
- g_worker_readiness.workers_ready = 0;
673
+ g_worker_readiness.workers_ready = 0;
664
674
  g_worker_readiness.workers_expected = pool_size;
665
675
  pthread_mutex_unlock(&g_worker_readiness.lock);
666
676
 
@@ -701,7 +711,7 @@ int cerver_listen(cerver_server_t* srv) {
701
711
  return -1;
702
712
  }
703
713
 
704
- srv->sock_fd = create_listener(srv->port, 0);
714
+ srv->sock_fd = create_listener(srv->port, 1);
705
715
  if (srv->sock_fd < 0) {
706
716
  srv->running = 0;
707
717
  for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
@@ -711,7 +721,7 @@ int cerver_listen(cerver_server_t* srv) {
711
721
  }
712
722
 
713
723
  /* Start acceptor threads */
714
- srv->workers = calloc((size_t)acceptor_count, sizeof(cerver_worker_t));
724
+ srv->workers = calloc((size_t)acceptor_count, sizeof(cerver_worker_t));
715
725
  srv->worker_count = acceptor_count;
716
726
  if (!srv->workers) {
717
727
  perror("cerver: calloc acceptors");
@@ -726,12 +736,16 @@ int cerver_listen(cerver_server_t* srv) {
726
736
 
727
737
  for (int i = 0; i < acceptor_count; i++) {
728
738
  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;
739
+ w->id = i;
740
+ w->srv = srv;
741
+ w->event_fd = -1;
742
+ #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
743
+ if (i == 0) {
744
+ w->listen_fd = srv->sock_fd;
745
+ } else {
746
+ w->listen_fd = create_listener(srv->port, 1);
747
+ if (w->listen_fd < 0) w->listen_fd = srv->sock_fd;
748
+ }
735
749
  #else
736
750
  w->listen_fd = srv->sock_fd;
737
751
  #endif
@@ -790,7 +804,7 @@ void cerver_shutdown(cerver_server_t* srv) {
790
804
 
791
805
  if (srv->workers) {
792
806
  for (int i = 0; i < srv->worker_count; i++) {
793
- #ifdef __linux__
807
+ #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
794
808
  if (srv->workers[i].listen_fd != srv->sock_fd && srv->workers[i].listen_fd >= 0)
795
809
  close(srv->workers[i].listen_fd);
796
810
  #endif
@@ -800,6 +814,11 @@ void cerver_shutdown(cerver_server_t* srv) {
800
814
  srv->workers = NULL;
801
815
  }
802
816
 
817
+ if (srv->route_trie) {
818
+ cerver_trie_free(srv->route_trie);
819
+ srv->route_trie = NULL;
820
+ }
821
+
803
822
  if (srv->sock_fd >= 0) {
804
823
  close(srv->sock_fd);
805
824
  srv->sock_fd = -1;