@velox0/cerver 0.4.0 → 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
@@ -28,63 +28,62 @@
28
28
  #include <pthread.h>
29
29
 
30
30
  #if defined(__APPLE__) || defined(__FreeBSD__)
31
- #define CERVER_USE_KQUEUE 1
32
- #include <sys/event.h>
31
+ #define CERVER_USE_KQUEUE 1
32
+ #include <sys/event.h>
33
33
  #elif defined(__linux__)
34
- #define CERVER_USE_EPOLL 1
35
- #include <sys/epoll.h>
36
- #include <sys/sendfile.h>
34
+ #define CERVER_USE_EPOLL 1
35
+ #include <sys/epoll.h>
36
+ #include <sys/sendfile.h>
37
37
  #else
38
- #define CERVER_USE_SELECT 1
39
- #include <sys/select.h>
38
+ #define CERVER_USE_SELECT 1
39
+ #include <sys/select.h>
40
40
  #endif
41
41
 
42
42
  #ifdef __linux__
43
- #include <sched.h>
43
+ #include <sched.h>
44
44
  #endif
45
45
 
46
46
  /* Connection pool sizing */
47
- #define CERVER_CONN_POOL_SIZE 128
48
- #define CERVER_CONN_QUEUE_SIZE 4096
47
+ #define CERVER_CONN_POOL_SIZE 128
48
+ #define CERVER_CONN_QUEUE_SIZE 4096
49
49
 
50
50
  /* ------------------------------------------------------------------ */
51
51
  /* memmem fallback */
52
52
  /* ------------------------------------------------------------------ */
53
53
 
54
54
  #if !defined(__APPLE__) && !defined(__linux__) && !defined(_GNU_SOURCE)
55
- static void *cerver_memmem(const void *hay, size_t haylen,
56
- const void *needle, size_t nlen) {
57
- if (nlen == 0) return (void *)hay;
58
- if (nlen > haylen) return NULL;
59
- const char *p = (const char *)hay;
60
- const char *end = p + haylen - nlen;
61
- for (; p <= end; p++) {
62
- if (memcmp(p, needle, nlen) == 0) return (void *)p;
63
- }
64
- return NULL;
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;
65
64
  }
66
65
  #define memmem cerver_memmem
67
66
  #endif
68
67
 
69
68
  /* Global for signal handler */
70
- static cerver_server_t *g_srv = NULL;
69
+ static cerver_server_t* g_srv = NULL;
71
70
 
72
71
  static void signal_handler(int sig) {
73
- (void)sig;
74
- if (g_srv) g_srv->running = 0;
72
+ (void)sig;
73
+ if (g_srv) g_srv->running = 0;
75
74
  }
76
75
 
77
76
  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);
77
+ int flags = fcntl(fd, F_GETFL, 0);
78
+ if (flags < 0) return -1;
79
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
81
80
  }
82
81
 
83
82
  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;
83
+ long n = sysconf(_SC_NPROCESSORS_ONLN);
84
+ if (n < 1) n = 1;
85
+ if (n > 64) n = 64;
86
+ return (int)n;
88
87
  }
89
88
 
90
89
  /* ------------------------------------------------------------------ */
@@ -92,178 +91,246 @@ static int get_cpu_count(void) {
92
91
  /* ------------------------------------------------------------------ */
93
92
 
94
93
  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;
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;
101
100
  } conn_queue_t;
102
101
 
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);
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);
108
107
  }
109
108
 
110
- static void cq_destroy(conn_queue_t *q) {
111
- pthread_mutex_destroy(&q->lock);
112
- pthread_cond_destroy(&q->not_empty);
109
+ static void cq_destroy(conn_queue_t* q) {
110
+ pthread_mutex_destroy(&q->lock);
111
+ pthread_cond_destroy(&q->not_empty);
113
112
  }
114
113
 
115
114
  /* 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);
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) {
126
118
  pthread_mutex_unlock(&q->lock);
127
- return 0;
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;
128
127
  }
129
128
 
130
129
  /* 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--;
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) {
147
140
  pthread_mutex_unlock(&q->lock);
148
- return fd;
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;
149
148
  }
150
149
 
151
150
  /* Global connection queue */
152
151
  static conn_queue_t g_conn_queue;
153
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};
180
+
154
181
  /* ------------------------------------------------------------------ */
155
182
  /* Buffered read */
156
183
  /* ------------------------------------------------------------------ */
157
184
 
158
- static char *read_full_request(int fd, size_t *out_len) {
159
- size_t cap = CERVER_READ_BUF;
160
- size_t len = 0;
161
- char *buf = malloc(cap + 1);
162
- if (!buf) return NULL;
163
-
164
- while (len < (size_t)CERVER_READ_BUF_MAX) {
165
- ssize_t n = read(fd, buf + len, cap - len);
166
- if (n <= 0) break;
167
- len += (size_t)n;
168
- if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4)) break;
169
- if (len == cap) {
170
- size_t newcap = cap * 2;
171
- if (newcap > (size_t)CERVER_READ_BUF_MAX)
172
- newcap = (size_t)CERVER_READ_BUF_MAX;
173
- char *tmp = realloc(buf, newcap + 1);
174
- if (!tmp) { free(buf); return NULL; }
175
- buf = tmp;
176
- cap = newcap;
177
- }
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) {
201
+ free(buf);
202
+ return NULL;
203
+ }
204
+ buf = tmp;
205
+ cap = newcap;
178
206
  }
207
+ }
179
208
 
180
- if (len == 0) { free(buf); return NULL; }
181
- buf[len] = '\0';
182
- *out_len = len;
183
- return buf;
209
+ if (len == 0) {
210
+ free(buf);
211
+ return NULL;
212
+ }
213
+ buf[len] = '\0';
214
+ *out_len = len;
215
+ return buf;
184
216
  }
185
217
 
186
218
  /* ------------------------------------------------------------------ */
187
219
  /* Handle connection with keep-alive */
188
220
  /* ------------------------------------------------------------------ */
189
221
 
190
- static void handle_connection(cerver_server_t *srv, int client_fd) {
191
- int flags = fcntl(client_fd, F_GETFL, 0);
192
- if (flags >= 0 && (flags & O_NONBLOCK))
193
- fcntl(client_fd, F_SETFL, flags & ~O_NONBLOCK);
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);
194
225
 
195
- int nodelay = 1;
196
- setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
226
+ int nodelay = 1;
227
+ setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
197
228
 
198
- int request_count = 0;
199
- int keepalive = 1;
229
+ int request_count = 0;
230
+ int keepalive = 1;
200
231
 
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));
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;
236
+ setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
206
237
 
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
- }
238
+ size_t req_len = 0;
239
+ char* buf = read_full_request(client_fd, &req_len);
240
+ if (!buf || req_len == 0) {
241
+ if (buf) free(buf);
242
+ break;
243
+ }
213
244
 
214
- cerver_request_t req;
215
- memset(&req, 0, sizeof(req));
245
+ cerver_request_t req;
246
+ memset(&req, 0, sizeof(req));
216
247
 
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;
223
- }
248
+ if (cerver_parse_request(buf, req_len, &req) < 0) {
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;
255
+ }
224
256
 
225
- request_count++;
226
- keepalive = !cerver_req_wants_close(&req);
257
+ request_count++;
258
+ keepalive = !cerver_req_wants_close(&req);
227
259
 
228
- cerver_response_t res;
229
- memset(&res, 0, sizeof(res));
260
+ cerver_response_t res;
261
+ memset(&res, 0, sizeof(res));
230
262
 
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
- }
263
+ if (cerver_serve_static(srv, &req, &res) < 0) {
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
+ }
270
+ }
239
271
 
240
- if (res._force_close) keepalive = 0;
272
+ if (res._force_close) keepalive = 0;
241
273
 
242
- int write_err = cerver_write_response(client_fd, &res, keepalive);
274
+ int write_err = cerver_write_response(client_fd, &res, keepalive);
243
275
 
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);
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);
246
280
 
247
- free(buf);
248
- if (write_err < 0) break;
249
- }
281
+ free(buf);
282
+ if (write_err < 0) break;
283
+ }
250
284
 
251
- close(client_fd);
285
+ close(client_fd);
252
286
  }
253
287
 
254
288
  /* ------------------------------------------------------------------ */
255
289
  /* Connection pool worker thread */
256
290
  /* ------------------------------------------------------------------ */
257
291
 
258
- static void *conn_pool_worker(void *arg) {
259
- cerver_server_t *srv = (cerver_server_t *)arg;
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;
307
+ }
260
308
 
261
- while (srv->running) {
262
- int fd = cq_pop(&g_conn_queue, &srv->running);
263
- if (fd < 0) continue;
264
- handle_connection(srv, fd);
265
- }
266
- return NULL;
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
+ }
318
+
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
+ }
328
+
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);
267
334
  }
268
335
 
269
336
  /* ------------------------------------------------------------------ */
@@ -271,34 +338,40 @@ static void *conn_pool_worker(void *arg) {
271
338
  /* ------------------------------------------------------------------ */
272
339
 
273
340
  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; }
341
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
342
+ if (fd < 0) {
343
+ perror("cerver: socket");
344
+ return -1;
345
+ }
276
346
 
277
- int opt = 1;
278
- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
347
+ int opt = 1;
348
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
279
349
 
280
350
  #ifdef __linux__
281
- if (reuseport)
282
- setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
351
+ if (reuseport) setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
283
352
  #else
284
- (void)reuseport;
353
+ (void)reuseport;
285
354
  #endif
286
355
 
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);
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);
292
361
 
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
- }
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
+ }
299
372
 
300
- set_nonblocking(fd);
301
- return fd;
373
+ set_nonblocking(fd);
374
+ return fd;
302
375
  }
303
376
 
304
377
  /* ------------------------------------------------------------------ */
@@ -306,12 +379,12 @@ static int create_listener(int port, int reuseport) {
306
379
  /* ------------------------------------------------------------------ */
307
380
 
308
381
  static int accept_connection(int listen_fd) {
309
- struct sockaddr_in ca;
310
- socklen_t cl = sizeof(ca);
382
+ struct sockaddr_in ca;
383
+ socklen_t cl = sizeof(ca);
311
384
  #ifdef __linux__
312
- return accept4(listen_fd, (struct sockaddr *)&ca, &cl, SOCK_CLOEXEC);
385
+ return accept4(listen_fd, (struct sockaddr*)&ca, &cl, SOCK_CLOEXEC);
313
386
  #else
314
- return accept(listen_fd, (struct sockaddr *)&ca, &cl);
387
+ return accept(listen_fd, (struct sockaddr*)&ca, &cl);
315
388
  #endif
316
389
  }
317
390
 
@@ -320,114 +393,155 @@ static int accept_connection(int listen_fd) {
320
393
  /* ------------------------------------------------------------------ */
321
394
 
322
395
  #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];
336
-
337
- while (srv->running) {
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; }
341
-
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
- }
396
+ static void* acceptor_loop(void* arg) {
397
+ cerver_worker_t* w = (cerver_worker_t*)arg;
398
+ cerver_server_t* srv = w->srv;
399
+
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
+ }
428
+
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
+ }
360
446
  }
447
+ }
361
448
  }
449
+ }
362
450
 
363
- close(kq);
364
- return NULL;
451
+ close(kq);
452
+ return NULL;
365
453
  }
366
454
 
367
455
  #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
- }
456
+ static void* acceptor_loop(void* arg) {
457
+ cerver_worker_t* w = (cerver_worker_t*)arg;
458
+ cerver_server_t* srv = w->srv;
459
+
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;
485
+ }
486
+
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
+ }
402
503
  }
504
+ }
403
505
  }
506
+ }
404
507
 
405
- close(ep);
406
- return NULL;
508
+ close(ep);
509
+ return NULL;
407
510
  }
408
511
 
409
512
  #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
- }
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);
428
540
  }
541
+ }
429
542
  }
430
- return NULL;
543
+ }
544
+ return NULL;
431
545
  }
432
546
  #endif
433
547
 
@@ -435,221 +549,264 @@ static void *acceptor_loop(void *arg) {
435
549
  /* Stat cache */
436
550
  /* ------------------------------------------------------------------ */
437
551
 
438
- void cerver_stat_cache_init(cerver_stat_cache_t *cache) {
439
- memset(cache, 0, sizeof(*cache));
440
- pthread_mutex_init(&cache->lock, NULL);
552
+ void cerver_stat_cache_init(cerver_stat_cache_t* cache) {
553
+ memset(cache, 0, sizeof(*cache));
554
+ pthread_mutex_init(&cache->lock, NULL);
441
555
  }
442
556
 
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
- }
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;
458
570
  }
459
- pthread_mutex_unlock(&cache->lock);
460
- return -1;
571
+ }
572
+ pthread_mutex_unlock(&cache->lock);
573
+ return -1;
461
574
  }
462
575
 
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; }
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;
587
+ }
588
+ if (e->cached_at < oldest) {
589
+ oldest = e->cached_at;
590
+ best = i;
473
591
  }
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);
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);
482
601
  }
483
602
 
484
603
  /* ------------------------------------------------------------------ */
485
604
  /* Server init */
486
605
  /* ------------------------------------------------------------------ */
487
606
 
488
- int cerver_init(cerver_server_t *srv, int port, int threads) {
489
- memset(srv, 0, sizeof(*srv));
490
- srv->port = port;
491
- srv->sock_fd = -1;
492
- srv->running = 0;
493
- srv->public_dir = NULL;
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);
498
- return 0;
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;
499
618
  }
500
619
 
501
- int cerver_add_routes(cerver_server_t *srv, cerver_route_t *routes, int count) {
502
- srv->routes = routes;
503
- srv->route_count = count;
504
- return 0;
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;
505
624
  }
506
625
 
507
- int cerver_set_assets(cerver_server_t *srv, cerver_asset_t *assets, int count) {
508
- srv->assets = assets;
509
- srv->asset_count = count;
510
- return 0;
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;
511
630
  }
512
631
 
513
- void cerver_set_public_dir(cerver_server_t *srv, const char *dir) {
514
- srv->public_dir = dir;
515
- }
632
+ void cerver_set_public_dir(cerver_server_t* srv, const char* dir) { srv->public_dir = dir; }
516
633
 
517
634
  /* ------------------------------------------------------------------ */
518
635
  /* Server listen */
519
636
  /* ------------------------------------------------------------------ */
520
637
 
521
- int cerver_listen(cerver_server_t *srv) {
522
- srv->sock_fd = create_listener(srv->port, 0);
523
- if (srv->sock_fd < 0) return -1;
524
-
525
- g_srv = srv;
526
- signal(SIGINT, signal_handler);
527
- signal(SIGTERM, signal_handler);
528
- signal(SIGPIPE, SIG_IGN);
529
-
530
- srv->running = 1;
531
-
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;
539
-
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;
543
-
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);
547
-
548
- /* Init shared connection queue */
549
- cq_init(&g_conn_queue);
550
-
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; }
554
-
555
- pthread_attr_t attr;
556
- pthread_attr_init(&attr);
557
- pthread_attr_setstacksize(&attr, 2 * 1024 * 1024);
558
-
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;
568
- }
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;
569
693
  }
694
+ }
570
695
 
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);
580
- close(srv->sock_fd);
581
- pthread_attr_destroy(&attr);
582
- return -1;
583
- }
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
+ }
584
703
 
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;
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;
590
732
  #ifdef __linux__
591
- w->listen_fd = create_listener(srv->port, 1);
592
- if (w->listen_fd < 0) w->listen_fd = srv->sock_fd;
733
+ w->listen_fd = create_listener(srv->port, 1);
734
+ if (w->listen_fd < 0) w->listen_fd = srv->sock_fd;
593
735
  #else
594
- w->listen_fd = srv->sock_fd;
736
+ w->listen_fd = srv->sock_fd;
595
737
  #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);
600
- break;
601
- }
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;
602
743
  }
744
+ }
603
745
 
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);
604
752
  pthread_attr_destroy(&attr);
753
+ return -1;
754
+ }
605
755
 
606
- /* Main thread waits for shutdown */
607
- while (srv->running) sleep(1);
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);
608
759
 
609
- /* Shutdown: stop acceptors first, then drain pool */
610
- srv->running = 0;
760
+ release_acceptors();
611
761
 
612
- for (int i = 0; i < acceptor_count; i++)
613
- pthread_join(srv->workers[i].thread, NULL);
762
+ pthread_attr_destroy(&attr);
614
763
 
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);
764
+ /* Main thread waits for shutdown */
765
+ while (srv->running) sleep(1);
619
766
 
620
- for (int i = 0; i < pool_size; i++)
621
- pthread_join(pool_threads[i], NULL);
622
- free(pool_threads);
767
+ /* Shutdown: stop acceptors first, then drain pool */
768
+ srv->running = 0;
769
+
770
+ for (int i = 0; i < acceptor_count; i++) pthread_join(srv->workers[i].thread, NULL);
771
+
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);
623
776
 
624
- cerver_shutdown(srv);
625
- return 0;
777
+ for (int i = 0; i < pool_size; i++) pthread_join(pool_threads[i], NULL);
778
+ free(pool_threads);
779
+
780
+ cerver_shutdown(srv);
781
+ return 0;
626
782
  }
627
783
 
628
784
  /* ------------------------------------------------------------------ */
629
785
  /* Shutdown */
630
786
  /* ------------------------------------------------------------------ */
631
787
 
632
- void cerver_shutdown(cerver_server_t *srv) {
633
- srv->running = 0;
788
+ void cerver_shutdown(cerver_server_t* srv) {
789
+ srv->running = 0;
634
790
 
635
- if (srv->workers) {
636
- for (int i = 0; i < srv->worker_count; i++) {
791
+ if (srv->workers) {
792
+ for (int i = 0; i < srv->worker_count; i++) {
637
793
  #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);
794
+ if (srv->workers[i].listen_fd != srv->sock_fd && srv->workers[i].listen_fd >= 0)
795
+ close(srv->workers[i].listen_fd);
641
796
  #endif
642
- if (srv->workers[i].event_fd >= 0)
643
- close(srv->workers[i].event_fd);
644
- }
645
- free(srv->workers);
646
- srv->workers = NULL;
797
+ if (srv->workers[i].event_fd >= 0) close(srv->workers[i].event_fd);
647
798
  }
799
+ free(srv->workers);
800
+ srv->workers = NULL;
801
+ }
648
802
 
649
- if (srv->sock_fd >= 0) { close(srv->sock_fd); srv->sock_fd = -1; }
803
+ if (srv->sock_fd >= 0) {
804
+ close(srv->sock_fd);
805
+ srv->sock_fd = -1;
806
+ }
650
807
 
651
- cq_destroy(&g_conn_queue);
652
- pthread_mutex_destroy(&srv->stat_cache.lock);
808
+ cq_destroy(&g_conn_queue);
809
+ pthread_mutex_destroy(&srv->stat_cache.lock);
653
810
 
654
- printf("\ncerver: server stopped\n");
811
+ printf("\ncerver: server stopped\n");
655
812
  }