@velox0/cerver 0.6.2 → 0.6.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/Makefile +21 -2
- package/WINDOWS.md +6 -12
- package/lib/commands/build.js +1 -1
- package/lib/compiler/compile.js +2 -41
- package/package.json +1 -1
- package/runtime/cerver.h +16 -17
- package/runtime/fetch.c +22 -5
- package/runtime/http_parser.c +4 -0
- package/runtime/http_writer.c +2 -0
- package/runtime/mime.c +4 -0
- package/runtime/router.c +3 -0
- package/runtime/server.c +207 -177
- package/runtime/tests/runtime_tests.c +77 -38
- package/runtime/win_compat.h +269 -15
package/runtime/server.c
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* server.c — Hybrid event-loop +
|
|
2
|
+
* server.c — Hybrid event-loop + connection-worker server for the cerver runtime.
|
|
3
3
|
*
|
|
4
4
|
* Cross-platform: Linux (epoll), macOS/BSD (kqueue), Windows (select/IOCP-ready),
|
|
5
5
|
* and any other POSIX platform (select fallback).
|
|
6
6
|
*
|
|
7
7
|
* Architecture:
|
|
8
8
|
* - 1 acceptor thread per core with its own kqueue/epoll (accept only)
|
|
9
|
-
* - Shared connection
|
|
9
|
+
* - Shared connection worker pool (configurable size, default 128)
|
|
10
10
|
* - Acceptors never block — they push fds into a shared queue guarded by a mutex/condvar
|
|
11
|
-
* -
|
|
11
|
+
* - Connection workers handle full request lifecycle including keep-alive
|
|
12
12
|
* - On Linux: SO_REUSEPORT per acceptor; on macOS: shared listener
|
|
13
|
-
* - On Windows: Winsock2, select-based acceptor,
|
|
13
|
+
* - On Windows: Winsock2, select-based acceptor, native Windows threads
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
#include "win_compat.h" /* Must come first on Windows */
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
#include <errno.h>
|
|
23
23
|
#include <signal.h>
|
|
24
24
|
#include <time.h>
|
|
25
|
-
#include <pthread.h>
|
|
26
25
|
|
|
27
26
|
#if !CERVER_PLATFORM_WINDOWS
|
|
28
27
|
#include <unistd.h>
|
|
@@ -56,9 +55,9 @@
|
|
|
56
55
|
#include <sched.h>
|
|
57
56
|
#endif // __linux__
|
|
58
57
|
|
|
59
|
-
/* Connection
|
|
60
|
-
#define
|
|
61
|
-
#define CERVER_CONN_QUEUE_SIZE
|
|
58
|
+
/* Connection worker sizing */
|
|
59
|
+
#define CERVER_CONNECTION_WORKER_POOL_MIN 128
|
|
60
|
+
#define CERVER_CONN_QUEUE_SIZE 4096
|
|
62
61
|
|
|
63
62
|
/* ------------------------------------------------------------------ */
|
|
64
63
|
/* memmem fallback (also defined in win_compat.h for Windows) */
|
|
@@ -117,88 +116,88 @@ static void best_effort_write(cerver_sock_t fd, const char* buf, size_t len) {
|
|
|
117
116
|
static void cerver_platform_close(cerver_sock_t fd) { cerver_close_sock(fd); }
|
|
118
117
|
|
|
119
118
|
/* ------------------------------------------------------------------ */
|
|
120
|
-
/* Connection queue (shared between acceptors and
|
|
119
|
+
/* Connection queue (shared between acceptors and connection workers) */
|
|
121
120
|
/* ------------------------------------------------------------------ */
|
|
122
121
|
|
|
123
122
|
typedef struct {
|
|
124
|
-
cerver_sock_t
|
|
125
|
-
int
|
|
126
|
-
int
|
|
127
|
-
int
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
cerver_sock_t fds[CERVER_CONN_QUEUE_SIZE];
|
|
124
|
+
int head;
|
|
125
|
+
int tail;
|
|
126
|
+
int count;
|
|
127
|
+
cerver_mutex_t lock;
|
|
128
|
+
cerver_cond_t not_empty;
|
|
130
129
|
} conn_queue_t;
|
|
131
130
|
|
|
132
131
|
static void cq_init(conn_queue_t* q) {
|
|
133
132
|
for (int i = 0; i < CERVER_CONN_QUEUE_SIZE; i++) q->fds[i] = CERVER_INVALID_SOCK;
|
|
134
133
|
q->head = q->tail = q->count = 0;
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
cerver_mutex_init(&q->lock, NULL);
|
|
135
|
+
cerver_cond_init(&q->not_empty, NULL);
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
static void cq_destroy(conn_queue_t* q) {
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
cerver_mutex_destroy(&q->lock);
|
|
140
|
+
cerver_cond_destroy(&q->not_empty);
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
/* Returns 0 on success, -1 if queue is full. */
|
|
145
144
|
static int cq_push(conn_queue_t* q, cerver_sock_t fd) {
|
|
146
|
-
|
|
145
|
+
cerver_mutex_lock(&q->lock);
|
|
147
146
|
if (q->count >= CERVER_CONN_QUEUE_SIZE) {
|
|
148
|
-
|
|
147
|
+
cerver_mutex_unlock(&q->lock);
|
|
149
148
|
return -1;
|
|
150
149
|
}
|
|
151
150
|
q->fds[q->tail] = fd;
|
|
152
151
|
q->tail = (q->tail + 1) % CERVER_CONN_QUEUE_SIZE;
|
|
153
152
|
q->count++;
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
cerver_cond_signal(&q->not_empty);
|
|
154
|
+
cerver_mutex_unlock(&q->lock);
|
|
156
155
|
return 0;
|
|
157
156
|
}
|
|
158
157
|
|
|
159
158
|
/* Returns fd, or CERVER_INVALID_SOCK on shutdown. */
|
|
160
159
|
static cerver_sock_t cq_pop(conn_queue_t* q, volatile int* running) {
|
|
161
|
-
|
|
160
|
+
cerver_mutex_lock(&q->lock);
|
|
162
161
|
while (q->count == 0 && *running) {
|
|
163
162
|
struct timespec ts;
|
|
164
163
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
165
164
|
ts.tv_sec += 1;
|
|
166
|
-
|
|
165
|
+
cerver_cond_timedwait(&q->not_empty, &q->lock, &ts);
|
|
167
166
|
}
|
|
168
167
|
if (q->count == 0) {
|
|
169
|
-
|
|
168
|
+
cerver_mutex_unlock(&q->lock);
|
|
170
169
|
return CERVER_INVALID_SOCK;
|
|
171
170
|
}
|
|
172
171
|
cerver_sock_t fd = q->fds[q->head];
|
|
173
172
|
q->head = (q->head + 1) % CERVER_CONN_QUEUE_SIZE;
|
|
174
173
|
q->count--;
|
|
175
|
-
|
|
174
|
+
cerver_mutex_unlock(&q->lock);
|
|
176
175
|
return fd;
|
|
177
176
|
}
|
|
178
177
|
|
|
179
178
|
static conn_queue_t g_conn_queue;
|
|
180
179
|
|
|
181
|
-
/*
|
|
180
|
+
/* Connection worker / acceptor readiness tracking */
|
|
182
181
|
typedef struct {
|
|
183
|
-
int
|
|
184
|
-
int
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
182
|
+
int connection_workers_ready;
|
|
183
|
+
int connection_workers_expected;
|
|
184
|
+
cerver_mutex_t lock;
|
|
185
|
+
cerver_cond_t ready_cv;
|
|
186
|
+
} connection_worker_readiness_t;
|
|
188
187
|
|
|
189
|
-
static
|
|
190
|
-
|
|
188
|
+
static connection_worker_readiness_t g_connection_worker_readiness = {
|
|
189
|
+
0, 0, CERVER_MUTEX_INITIALIZER, CERVER_COND_INITIALIZER};
|
|
191
190
|
|
|
192
191
|
typedef struct {
|
|
193
|
-
int
|
|
194
|
-
int
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
192
|
+
int acceptors_ready;
|
|
193
|
+
int start_accepting;
|
|
194
|
+
cerver_mutex_t lock;
|
|
195
|
+
cerver_cond_t ready_cv;
|
|
196
|
+
cerver_cond_t start_cv;
|
|
198
197
|
} acceptor_readiness_t;
|
|
199
198
|
|
|
200
199
|
static acceptor_readiness_t g_acceptor_readiness = {
|
|
201
|
-
0, 0,
|
|
200
|
+
0, 0, CERVER_MUTEX_INITIALIZER, CERVER_COND_INITIALIZER, CERVER_COND_INITIALIZER};
|
|
202
201
|
|
|
203
202
|
/* ------------------------------------------------------------------ */
|
|
204
203
|
/* Buffered read */
|
|
@@ -210,6 +209,11 @@ static char* read_full_request(cerver_sock_t fd, size_t* out_len) {
|
|
|
210
209
|
char* buf = malloc(cap + 1);
|
|
211
210
|
if (!buf) return NULL;
|
|
212
211
|
|
|
212
|
+
#if CERVER_PLATFORM_WINDOWS
|
|
213
|
+
/* On Windows set a blocking recv timeout via SO_RCVTIMEO (already set
|
|
214
|
+
per-connection in handle_connection). Just read normally. */
|
|
215
|
+
#endif // CERVER_PLATFORM_WINDOWS
|
|
216
|
+
|
|
213
217
|
while (len < (size_t)CERVER_READ_BUF_MAX) {
|
|
214
218
|
ssize_t n = (ssize_t)cerver_sock_read(fd, buf + len, cap - len);
|
|
215
219
|
if (n <= 0) break;
|
|
@@ -327,16 +331,16 @@ static void handle_connection(cerver_server_t* srv, cerver_sock_t client_fd) {
|
|
|
327
331
|
}
|
|
328
332
|
|
|
329
333
|
/* ------------------------------------------------------------------ */
|
|
330
|
-
/* Connection
|
|
334
|
+
/* Connection worker thread */
|
|
331
335
|
/* ------------------------------------------------------------------ */
|
|
332
336
|
|
|
333
|
-
static void*
|
|
337
|
+
static void* connection_worker_loop(void* arg) {
|
|
334
338
|
cerver_server_t* srv = (cerver_server_t*)arg;
|
|
335
339
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
+
cerver_mutex_lock(&g_connection_worker_readiness.lock);
|
|
341
|
+
g_connection_worker_readiness.connection_workers_ready++;
|
|
342
|
+
cerver_cond_broadcast(&g_connection_worker_readiness.ready_cv);
|
|
343
|
+
cerver_mutex_unlock(&g_connection_worker_readiness.lock);
|
|
340
344
|
|
|
341
345
|
while (srv->running) {
|
|
342
346
|
cerver_sock_t fd = cq_pop(&g_conn_queue, &srv->running);
|
|
@@ -346,29 +350,29 @@ static void* conn_pool_worker(void* arg) {
|
|
|
346
350
|
return NULL;
|
|
347
351
|
}
|
|
348
352
|
|
|
349
|
-
static int
|
|
350
|
-
|
|
351
|
-
while (
|
|
352
|
-
|
|
353
|
-
int ready = (
|
|
354
|
-
|
|
353
|
+
static int wait_for_connection_workers_ready(cerver_server_t* srv, int expected) {
|
|
354
|
+
cerver_mutex_lock(&g_connection_worker_readiness.lock);
|
|
355
|
+
while (g_connection_worker_readiness.connection_workers_ready < expected && srv->running)
|
|
356
|
+
cerver_cond_wait(&g_connection_worker_readiness.ready_cv, &g_connection_worker_readiness.lock);
|
|
357
|
+
int ready = (g_connection_worker_readiness.connection_workers_ready >= expected);
|
|
358
|
+
cerver_mutex_unlock(&g_connection_worker_readiness.lock);
|
|
355
359
|
return ready;
|
|
356
360
|
}
|
|
357
361
|
|
|
358
362
|
static int wait_for_acceptors_ready(cerver_server_t* srv, int expected) {
|
|
359
|
-
|
|
363
|
+
cerver_mutex_lock(&g_acceptor_readiness.lock);
|
|
360
364
|
while (g_acceptor_readiness.acceptors_ready < expected && srv->running)
|
|
361
|
-
|
|
365
|
+
cerver_cond_wait(&g_acceptor_readiness.ready_cv, &g_acceptor_readiness.lock);
|
|
362
366
|
int ready = (g_acceptor_readiness.acceptors_ready >= expected);
|
|
363
|
-
|
|
367
|
+
cerver_mutex_unlock(&g_acceptor_readiness.lock);
|
|
364
368
|
return ready;
|
|
365
369
|
}
|
|
366
370
|
|
|
367
371
|
static void release_acceptors(void) {
|
|
368
|
-
|
|
372
|
+
cerver_mutex_lock(&g_acceptor_readiness.lock);
|
|
369
373
|
g_acceptor_readiness.start_accepting = 1;
|
|
370
|
-
|
|
371
|
-
|
|
374
|
+
cerver_cond_broadcast(&g_acceptor_readiness.start_cv);
|
|
375
|
+
cerver_mutex_unlock(&g_acceptor_readiness.lock);
|
|
372
376
|
}
|
|
373
377
|
|
|
374
378
|
/* ------------------------------------------------------------------ */
|
|
@@ -392,11 +396,11 @@ static cerver_sock_t create_listener(int port, int reuseport) {
|
|
|
392
396
|
int opt = 1;
|
|
393
397
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, CERVER_SETSOCKOPT_CAST & opt, sizeof(opt));
|
|
394
398
|
|
|
395
|
-
#if defined(
|
|
399
|
+
#if defined(SO_REUSEPORT)
|
|
396
400
|
if (reuseport) setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
|
|
397
401
|
#else
|
|
398
402
|
(void)reuseport;
|
|
399
|
-
#endif //
|
|
403
|
+
#endif // SO_REUSEPORT
|
|
400
404
|
|
|
401
405
|
struct sockaddr_in addr;
|
|
402
406
|
memset(&addr, 0, sizeof(addr));
|
|
@@ -426,11 +430,16 @@ static cerver_sock_t create_listener(int port, int reuseport) {
|
|
|
426
430
|
static cerver_sock_t accept_connection(cerver_sock_t listen_fd) {
|
|
427
431
|
struct sockaddr_in ca;
|
|
428
432
|
socklen_t cl = sizeof(ca);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
433
|
+
cerver_sock_t fd = accept(listen_fd, (struct sockaddr*)&ca, &cl);
|
|
434
|
+
#if !CERVER_PLATFORM_WINDOWS
|
|
435
|
+
#if defined(FD_CLOEXEC)
|
|
436
|
+
if (fd >= 0) {
|
|
437
|
+
int flags = fcntl(fd, F_GETFD, 0);
|
|
438
|
+
if (flags >= 0) fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
439
|
+
}
|
|
440
|
+
#endif // FD_CLOEXEC
|
|
441
|
+
#endif // !CERVER_PLATFORM_WINDOWS
|
|
442
|
+
return fd;
|
|
434
443
|
}
|
|
435
444
|
|
|
436
445
|
/* ------------------------------------------------------------------ */
|
|
@@ -440,28 +449,28 @@ static cerver_sock_t accept_connection(cerver_sock_t listen_fd) {
|
|
|
440
449
|
#if defined(CERVER_USE_KQUEUE)
|
|
441
450
|
|
|
442
451
|
static void* acceptor_loop(void* arg) {
|
|
443
|
-
|
|
444
|
-
cerver_server_t*
|
|
452
|
+
cerver_acceptor_t* acceptor = (cerver_acceptor_t*)arg;
|
|
453
|
+
cerver_server_t* srv = acceptor->srv;
|
|
445
454
|
|
|
446
455
|
int kq = kqueue();
|
|
447
456
|
if (kq < 0) {
|
|
448
457
|
perror("cerver: kqueue");
|
|
449
458
|
return NULL;
|
|
450
459
|
}
|
|
451
|
-
|
|
460
|
+
acceptor->event_fd = kq;
|
|
452
461
|
|
|
453
462
|
struct kevent change;
|
|
454
|
-
EV_SET(&change,
|
|
463
|
+
EV_SET(&change, acceptor->listen_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
|
|
455
464
|
kevent(kq, &change, 1, NULL, 0, NULL);
|
|
456
465
|
|
|
457
466
|
struct kevent events[CERVER_MAX_EVENTS];
|
|
458
467
|
|
|
459
|
-
|
|
468
|
+
cerver_mutex_lock(&g_acceptor_readiness.lock);
|
|
460
469
|
g_acceptor_readiness.acceptors_ready++;
|
|
461
|
-
|
|
470
|
+
cerver_cond_broadcast(&g_acceptor_readiness.ready_cv);
|
|
462
471
|
while (!g_acceptor_readiness.start_accepting && srv->running)
|
|
463
|
-
|
|
464
|
-
|
|
472
|
+
cerver_cond_wait(&g_acceptor_readiness.start_cv, &g_acceptor_readiness.lock);
|
|
473
|
+
cerver_mutex_unlock(&g_acceptor_readiness.lock);
|
|
465
474
|
|
|
466
475
|
while (srv->running) {
|
|
467
476
|
struct timespec ts = {1, 0};
|
|
@@ -471,9 +480,9 @@ static void* acceptor_loop(void* arg) {
|
|
|
471
480
|
break;
|
|
472
481
|
}
|
|
473
482
|
for (int i = 0; i < nev; i++) {
|
|
474
|
-
if ((int)events[i].ident ==
|
|
483
|
+
if ((int)events[i].ident == acceptor->listen_fd) {
|
|
475
484
|
while (1) {
|
|
476
|
-
cerver_sock_t cfd = accept_connection(
|
|
485
|
+
cerver_sock_t cfd = accept_connection(acceptor->listen_fd);
|
|
477
486
|
if (cfd == CERVER_INVALID_SOCK) {
|
|
478
487
|
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
|
|
479
488
|
break;
|
|
@@ -497,27 +506,27 @@ static void* acceptor_loop(void* arg) {
|
|
|
497
506
|
#elif defined(CERVER_USE_EPOLL)
|
|
498
507
|
|
|
499
508
|
static void* acceptor_loop(void* arg) {
|
|
500
|
-
|
|
501
|
-
cerver_server_t*
|
|
509
|
+
cerver_acceptor_t* acceptor = (cerver_acceptor_t*)arg;
|
|
510
|
+
cerver_server_t* srv = acceptor->srv;
|
|
502
511
|
|
|
503
512
|
int ep = epoll_create1(EPOLL_CLOEXEC);
|
|
504
513
|
if (ep < 0) {
|
|
505
514
|
perror("cerver: epoll");
|
|
506
515
|
return NULL;
|
|
507
516
|
}
|
|
508
|
-
|
|
517
|
+
acceptor->event_fd = ep;
|
|
509
518
|
|
|
510
|
-
struct epoll_event ev = {.events = EPOLLIN, .data.fd =
|
|
511
|
-
epoll_ctl(ep, EPOLL_CTL_ADD,
|
|
519
|
+
struct epoll_event ev = {.events = EPOLLIN, .data.fd = acceptor->listen_fd};
|
|
520
|
+
epoll_ctl(ep, EPOLL_CTL_ADD, acceptor->listen_fd, &ev);
|
|
512
521
|
|
|
513
522
|
struct epoll_event events[CERVER_MAX_EVENTS];
|
|
514
523
|
|
|
515
|
-
|
|
524
|
+
cerver_mutex_lock(&g_acceptor_readiness.lock);
|
|
516
525
|
g_acceptor_readiness.acceptors_ready++;
|
|
517
|
-
|
|
526
|
+
cerver_cond_broadcast(&g_acceptor_readiness.ready_cv);
|
|
518
527
|
while (!g_acceptor_readiness.start_accepting && srv->running)
|
|
519
|
-
|
|
520
|
-
|
|
528
|
+
cerver_cond_wait(&g_acceptor_readiness.start_cv, &g_acceptor_readiness.lock);
|
|
529
|
+
cerver_mutex_unlock(&g_acceptor_readiness.lock);
|
|
521
530
|
|
|
522
531
|
while (srv->running) {
|
|
523
532
|
int nev = epoll_wait(ep, events, CERVER_MAX_EVENTS, 1000);
|
|
@@ -526,9 +535,9 @@ static void* acceptor_loop(void* arg) {
|
|
|
526
535
|
break;
|
|
527
536
|
}
|
|
528
537
|
for (int i = 0; i < nev; i++) {
|
|
529
|
-
if (events[i].data.fd ==
|
|
538
|
+
if (events[i].data.fd == acceptor->listen_fd) {
|
|
530
539
|
while (1) {
|
|
531
|
-
cerver_sock_t cfd = accept_connection(
|
|
540
|
+
cerver_sock_t cfd = accept_connection(acceptor->listen_fd);
|
|
532
541
|
if (cfd < 0) {
|
|
533
542
|
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
|
|
534
543
|
break;
|
|
@@ -549,23 +558,23 @@ static void* acceptor_loop(void* arg) {
|
|
|
549
558
|
return NULL;
|
|
550
559
|
}
|
|
551
560
|
|
|
552
|
-
#else
|
|
561
|
+
#else /* SELECT — used on Windows and other POSIX platforms */
|
|
553
562
|
|
|
554
563
|
static void* acceptor_loop(void* arg) {
|
|
555
|
-
|
|
556
|
-
cerver_server_t*
|
|
564
|
+
cerver_acceptor_t* acceptor = (cerver_acceptor_t*)arg;
|
|
565
|
+
cerver_server_t* srv = acceptor->srv;
|
|
557
566
|
|
|
558
|
-
|
|
567
|
+
cerver_mutex_lock(&g_acceptor_readiness.lock);
|
|
559
568
|
g_acceptor_readiness.acceptors_ready++;
|
|
560
|
-
|
|
569
|
+
cerver_cond_broadcast(&g_acceptor_readiness.ready_cv);
|
|
561
570
|
while (!g_acceptor_readiness.start_accepting && srv->running)
|
|
562
|
-
|
|
563
|
-
|
|
571
|
+
cerver_cond_wait(&g_acceptor_readiness.start_cv, &g_acceptor_readiness.lock);
|
|
572
|
+
cerver_mutex_unlock(&g_acceptor_readiness.lock);
|
|
564
573
|
|
|
565
574
|
while (srv->running) {
|
|
566
575
|
fd_set rfds;
|
|
567
576
|
FD_ZERO(&rfds);
|
|
568
|
-
FD_SET(
|
|
577
|
+
FD_SET(acceptor->listen_fd, &rfds);
|
|
569
578
|
|
|
570
579
|
#if CERVER_PLATFORM_WINDOWS
|
|
571
580
|
/* Windows select takes (nfds, ...) but ignores the first arg for sockets */
|
|
@@ -573,7 +582,7 @@ static void* acceptor_loop(void* arg) {
|
|
|
573
582
|
int ret = select(0, &rfds, NULL, NULL, &tv);
|
|
574
583
|
#else
|
|
575
584
|
struct timeval tv = {1, 0};
|
|
576
|
-
int ret = select((int)
|
|
585
|
+
int ret = select((int)acceptor->listen_fd + 1, &rfds, NULL, NULL, &tv);
|
|
577
586
|
#endif // CERVER_PLATFORM_WINDOWS
|
|
578
587
|
if (ret < 0) {
|
|
579
588
|
#if CERVER_PLATFORM_WINDOWS
|
|
@@ -583,9 +592,9 @@ static void* acceptor_loop(void* arg) {
|
|
|
583
592
|
#endif // CERVER_PLATFORM_WINDOWS
|
|
584
593
|
break;
|
|
585
594
|
}
|
|
586
|
-
if (ret > 0 && FD_ISSET(
|
|
595
|
+
if (ret > 0 && FD_ISSET(acceptor->listen_fd, &rfds)) {
|
|
587
596
|
while (1) {
|
|
588
|
-
cerver_sock_t cfd = accept_connection(
|
|
597
|
+
cerver_sock_t cfd = accept_connection(acceptor->listen_fd);
|
|
589
598
|
if (cfd == CERVER_INVALID_SOCK) {
|
|
590
599
|
#if CERVER_PLATFORM_WINDOWS
|
|
591
600
|
if (cerver_would_block_win()) break;
|
|
@@ -610,32 +619,32 @@ static void* acceptor_loop(void* arg) {
|
|
|
610
619
|
|
|
611
620
|
void cerver_stat_cache_init(cerver_stat_cache_t* cache) {
|
|
612
621
|
memset(cache, 0, sizeof(*cache));
|
|
613
|
-
|
|
622
|
+
cerver_mutex_init(&cache->lock, NULL);
|
|
614
623
|
}
|
|
615
624
|
|
|
616
625
|
int cerver_stat_cache_lookup(cerver_stat_cache_t* cache, const char* path, size_t* file_size) {
|
|
617
626
|
time_t now = time(NULL);
|
|
618
|
-
|
|
627
|
+
cerver_mutex_lock(&cache->lock);
|
|
619
628
|
for (int i = 0; i < CERVER_STAT_CACHE_SIZE; i++) {
|
|
620
629
|
cerver_stat_entry_t* e = &cache->entries[i];
|
|
621
630
|
if (e->valid && strcmp(e->path, path) == 0) {
|
|
622
631
|
if (now - e->cached_at < CERVER_STAT_CACHE_TTL) {
|
|
623
632
|
*file_size = e->file_size;
|
|
624
|
-
|
|
633
|
+
cerver_mutex_unlock(&cache->lock);
|
|
625
634
|
return 0;
|
|
626
635
|
}
|
|
627
636
|
e->valid = 0;
|
|
628
637
|
break;
|
|
629
638
|
}
|
|
630
639
|
}
|
|
631
|
-
|
|
640
|
+
cerver_mutex_unlock(&cache->lock);
|
|
632
641
|
return -1;
|
|
633
642
|
}
|
|
634
643
|
|
|
635
644
|
void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_t file_size,
|
|
636
645
|
time_t mtime) {
|
|
637
646
|
time_t now = time(NULL);
|
|
638
|
-
|
|
647
|
+
cerver_mutex_lock(&cache->lock);
|
|
639
648
|
int best = 0;
|
|
640
649
|
time_t oldest = cache->entries[0].cached_at;
|
|
641
650
|
for (int i = 0; i < CERVER_STAT_CACHE_SIZE; i++) {
|
|
@@ -656,7 +665,7 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
|
|
|
656
665
|
slot->mtime = mtime;
|
|
657
666
|
slot->cached_at = now;
|
|
658
667
|
slot->valid = 1;
|
|
659
|
-
|
|
668
|
+
cerver_mutex_unlock(&cache->lock);
|
|
660
669
|
}
|
|
661
670
|
|
|
662
671
|
/* ------------------------------------------------------------------ */
|
|
@@ -665,10 +674,10 @@ void cerver_stat_cache_store(cerver_stat_cache_t* cache, const char* path, size_
|
|
|
665
674
|
|
|
666
675
|
int cerver_init(cerver_server_t* srv, int port, int threads) {
|
|
667
676
|
memset(srv, 0, sizeof(*srv));
|
|
668
|
-
srv->port
|
|
669
|
-
srv->sock_fd
|
|
670
|
-
srv->running
|
|
671
|
-
srv->
|
|
677
|
+
srv->port = port;
|
|
678
|
+
srv->sock_fd = CERVER_INVALID_SOCK;
|
|
679
|
+
srv->running = 0;
|
|
680
|
+
srv->connection_worker_count = (threads > 0) ? threads : get_cpu_count();
|
|
672
681
|
cerver_stat_cache_init(&srv->stat_cache);
|
|
673
682
|
#if CERVER_PLATFORM_WINDOWS
|
|
674
683
|
cerver_wsa_startup();
|
|
@@ -710,10 +719,11 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
710
719
|
srv->running = 1;
|
|
711
720
|
srv->acceptor_count = 0;
|
|
712
721
|
|
|
713
|
-
int cpu_count
|
|
714
|
-
int
|
|
715
|
-
int acceptor_count
|
|
716
|
-
if (acceptor_count >
|
|
722
|
+
int cpu_count = get_cpu_count();
|
|
723
|
+
int configured_connection_worker_count = srv->connection_worker_count;
|
|
724
|
+
int acceptor_count = cpu_count;
|
|
725
|
+
if (acceptor_count > configured_connection_worker_count)
|
|
726
|
+
acceptor_count = configured_connection_worker_count;
|
|
717
727
|
if (acceptor_count < 1) acceptor_count = 1;
|
|
718
728
|
|
|
719
729
|
/* Windows select-based acceptor: limit to 1 until IOCP is wired */
|
|
@@ -721,88 +731,102 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
721
731
|
acceptor_count = 1;
|
|
722
732
|
#endif // CERVER_PLATFORM_WINDOWS
|
|
723
733
|
|
|
724
|
-
int
|
|
725
|
-
if (
|
|
726
|
-
|
|
734
|
+
int connection_worker_thread_count = configured_connection_worker_count * 16;
|
|
735
|
+
if (connection_worker_thread_count < CERVER_CONNECTION_WORKER_POOL_MIN)
|
|
736
|
+
connection_worker_thread_count = CERVER_CONNECTION_WORKER_POOL_MIN;
|
|
737
|
+
if (connection_worker_thread_count > 1024) connection_worker_thread_count = 1024;
|
|
727
738
|
|
|
728
739
|
cq_init(&g_conn_queue);
|
|
729
740
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
741
|
+
cerver_mutex_lock(&g_connection_worker_readiness.lock);
|
|
742
|
+
g_connection_worker_readiness.connection_workers_ready = 0;
|
|
743
|
+
g_connection_worker_readiness.connection_workers_expected = connection_worker_thread_count;
|
|
744
|
+
cerver_mutex_unlock(&g_connection_worker_readiness.lock);
|
|
734
745
|
|
|
735
|
-
|
|
746
|
+
cerver_mutex_lock(&g_acceptor_readiness.lock);
|
|
736
747
|
g_acceptor_readiness.acceptors_ready = 0;
|
|
737
748
|
g_acceptor_readiness.start_accepting = 0;
|
|
738
|
-
|
|
749
|
+
cerver_mutex_unlock(&g_acceptor_readiness.lock);
|
|
739
750
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
751
|
+
cerver_connection_worker_thread_t* connection_workers =
|
|
752
|
+
calloc((size_t)connection_worker_thread_count, sizeof(cerver_connection_worker_thread_t));
|
|
753
|
+
if (!connection_workers) {
|
|
754
|
+
perror("cerver: calloc connection workers");
|
|
743
755
|
return -1;
|
|
744
756
|
}
|
|
745
757
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
758
|
+
cerver_connection_worker_thread_attr_t worker_attr;
|
|
759
|
+
cerver_acceptor_thread_attr_t acceptor_attr;
|
|
760
|
+
cerver_connection_worker_thread_attr_init(&worker_attr);
|
|
761
|
+
cerver_acceptor_thread_attr_init(&acceptor_attr);
|
|
762
|
+
cerver_connection_worker_thread_attr_setstacksize(&worker_attr, 2 * 1024 * 1024);
|
|
763
|
+
cerver_acceptor_thread_attr_setstacksize(&acceptor_attr, 2 * 1024 * 1024);
|
|
749
764
|
|
|
750
|
-
for (int i = 0; i <
|
|
751
|
-
if (
|
|
752
|
-
|
|
765
|
+
for (int i = 0; i < connection_worker_thread_count; i++) {
|
|
766
|
+
if (cerver_connection_worker_thread_create(&connection_workers[i], &worker_attr,
|
|
767
|
+
connection_worker_loop, srv) != 0) {
|
|
768
|
+
perror("cerver: connection worker thread create");
|
|
753
769
|
srv->running = 0;
|
|
754
|
-
for (int j = 0; j < i; j++)
|
|
755
|
-
free(
|
|
756
|
-
|
|
770
|
+
for (int j = 0; j < i; j++) cerver_connection_worker_thread_join(connection_workers[j], NULL);
|
|
771
|
+
free(connection_workers);
|
|
772
|
+
cerver_connection_worker_thread_attr_destroy(&worker_attr);
|
|
773
|
+
cerver_acceptor_thread_attr_destroy(&acceptor_attr);
|
|
757
774
|
return -1;
|
|
758
775
|
}
|
|
759
776
|
}
|
|
760
777
|
|
|
761
|
-
if (!
|
|
778
|
+
if (!wait_for_connection_workers_ready(srv, connection_worker_thread_count)) {
|
|
762
779
|
srv->running = 0;
|
|
763
|
-
for (int i = 0; i <
|
|
764
|
-
|
|
765
|
-
|
|
780
|
+
for (int i = 0; i < connection_worker_thread_count; i++)
|
|
781
|
+
cerver_connection_worker_thread_join(connection_workers[i], NULL);
|
|
782
|
+
free(connection_workers);
|
|
783
|
+
cerver_connection_worker_thread_attr_destroy(&worker_attr);
|
|
784
|
+
cerver_acceptor_thread_attr_destroy(&acceptor_attr);
|
|
766
785
|
return -1;
|
|
767
786
|
}
|
|
768
787
|
|
|
769
788
|
srv->sock_fd = create_listener(srv->port, 1);
|
|
770
789
|
if (srv->sock_fd == CERVER_INVALID_SOCK) {
|
|
771
790
|
srv->running = 0;
|
|
772
|
-
for (int i = 0; i <
|
|
773
|
-
|
|
774
|
-
|
|
791
|
+
for (int i = 0; i < connection_worker_thread_count; i++)
|
|
792
|
+
cerver_connection_worker_thread_join(connection_workers[i], NULL);
|
|
793
|
+
free(connection_workers);
|
|
794
|
+
cerver_connection_worker_thread_attr_destroy(&worker_attr);
|
|
795
|
+
cerver_acceptor_thread_attr_destroy(&acceptor_attr);
|
|
775
796
|
return -1;
|
|
776
797
|
}
|
|
777
798
|
|
|
778
|
-
srv->
|
|
799
|
+
srv->acceptors = calloc((size_t)acceptor_count, sizeof(cerver_acceptor_t));
|
|
779
800
|
srv->acceptor_count = acceptor_count;
|
|
780
|
-
if (!srv->
|
|
801
|
+
if (!srv->acceptors) {
|
|
781
802
|
perror("cerver: calloc acceptors");
|
|
782
803
|
srv->running = 0;
|
|
783
|
-
|
|
784
|
-
for (int i = 0; i <
|
|
785
|
-
|
|
804
|
+
cerver_cond_broadcast(&g_conn_queue.not_empty);
|
|
805
|
+
for (int i = 0; i < connection_worker_thread_count; i++)
|
|
806
|
+
cerver_connection_worker_thread_join(connection_workers[i], NULL);
|
|
807
|
+
free(connection_workers);
|
|
786
808
|
cerver_platform_close(srv->sock_fd);
|
|
787
|
-
|
|
809
|
+
cerver_connection_worker_thread_attr_destroy(&worker_attr);
|
|
810
|
+
cerver_acceptor_thread_attr_destroy(&acceptor_attr);
|
|
788
811
|
return -1;
|
|
789
812
|
}
|
|
790
813
|
|
|
791
814
|
for (int i = 0; i < acceptor_count; i++) {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
815
|
+
cerver_acceptor_t* acceptor = &srv->acceptors[i];
|
|
816
|
+
acceptor->id = i;
|
|
817
|
+
acceptor->srv = srv;
|
|
818
|
+
acceptor->event_fd = -1;
|
|
796
819
|
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
|
797
|
-
|
|
798
|
-
if (
|
|
820
|
+
acceptor->listen_fd = (i == 0) ? srv->sock_fd : create_listener(srv->port, 1);
|
|
821
|
+
if (acceptor->listen_fd == CERVER_INVALID_SOCK) acceptor->listen_fd = srv->sock_fd;
|
|
799
822
|
#else
|
|
800
|
-
|
|
823
|
+
acceptor->listen_fd = srv->sock_fd;
|
|
801
824
|
#endif // __linux__ || __APPLE__ || __FreeBSD__
|
|
802
|
-
if (
|
|
825
|
+
if (cerver_acceptor_thread_create(&acceptor->thread, &acceptor_attr, acceptor_loop, acceptor) !=
|
|
826
|
+
0) {
|
|
803
827
|
perror("cerver: acceptor create");
|
|
804
828
|
srv->running = 0;
|
|
805
|
-
for (int j = 0; j < i; j++)
|
|
829
|
+
for (int j = 0; j < i; j++) cerver_acceptor_thread_join(srv->acceptors[j].thread, NULL);
|
|
806
830
|
break;
|
|
807
831
|
}
|
|
808
832
|
}
|
|
@@ -810,16 +834,19 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
810
834
|
if (!wait_for_acceptors_ready(srv, acceptor_count)) {
|
|
811
835
|
srv->running = 0;
|
|
812
836
|
release_acceptors();
|
|
813
|
-
for (int i = 0; i < acceptor_count; i++)
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
837
|
+
for (int i = 0; i < acceptor_count; i++)
|
|
838
|
+
cerver_acceptor_thread_join(srv->acceptors[i].thread, NULL);
|
|
839
|
+
for (int i = 0; i < connection_worker_thread_count; i++)
|
|
840
|
+
cerver_connection_worker_thread_join(connection_workers[i], NULL);
|
|
841
|
+
free(connection_workers);
|
|
842
|
+
cerver_connection_worker_thread_attr_destroy(&worker_attr);
|
|
843
|
+
cerver_acceptor_thread_attr_destroy(&acceptor_attr);
|
|
817
844
|
return -1;
|
|
818
845
|
}
|
|
819
846
|
|
|
820
847
|
printf("cerver: listening on http://localhost:%d\n", srv->port);
|
|
821
848
|
printf("cerver: %d acceptor(s), %d connection workers, keep-alive max %d req/conn\n",
|
|
822
|
-
acceptor_count,
|
|
849
|
+
acceptor_count, connection_worker_thread_count, CERVER_KEEPALIVE_MAX);
|
|
823
850
|
|
|
824
851
|
for (int i = 0; i < srv->route_count; i++) {
|
|
825
852
|
const char* method = srv->routes[i].method;
|
|
@@ -838,21 +865,24 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
838
865
|
fflush(stdout);
|
|
839
866
|
|
|
840
867
|
release_acceptors();
|
|
841
|
-
|
|
868
|
+
cerver_connection_worker_thread_attr_destroy(&worker_attr);
|
|
869
|
+
cerver_acceptor_thread_attr_destroy(&acceptor_attr);
|
|
842
870
|
|
|
843
871
|
/* Main thread spin — sleep(1) works on all platforms via win_compat.h macro */
|
|
844
872
|
while (srv->running) sleep(1);
|
|
845
873
|
|
|
846
874
|
/* Shutdown */
|
|
847
875
|
srv->running = 0;
|
|
848
|
-
for (int i = 0; i < acceptor_count; i++)
|
|
876
|
+
for (int i = 0; i < acceptor_count; i++)
|
|
877
|
+
cerver_acceptor_thread_join(srv->acceptors[i].thread, NULL);
|
|
849
878
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
879
|
+
cerver_mutex_lock(&g_conn_queue.lock);
|
|
880
|
+
cerver_cond_broadcast(&g_conn_queue.not_empty);
|
|
881
|
+
cerver_mutex_unlock(&g_conn_queue.lock);
|
|
853
882
|
|
|
854
|
-
for (int i = 0; i <
|
|
855
|
-
|
|
883
|
+
for (int i = 0; i < connection_worker_thread_count; i++)
|
|
884
|
+
cerver_connection_worker_thread_join(connection_workers[i], NULL);
|
|
885
|
+
free(connection_workers);
|
|
856
886
|
|
|
857
887
|
cerver_shutdown(srv);
|
|
858
888
|
return 0;
|
|
@@ -865,22 +895,22 @@ int cerver_listen(cerver_server_t* srv) {
|
|
|
865
895
|
void cerver_shutdown(cerver_server_t* srv) {
|
|
866
896
|
srv->running = 0;
|
|
867
897
|
|
|
868
|
-
if (srv->
|
|
898
|
+
if (srv->acceptors) {
|
|
869
899
|
for (int i = 0; i < srv->acceptor_count; i++) {
|
|
870
900
|
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
|
871
|
-
if (srv->
|
|
872
|
-
srv->
|
|
873
|
-
cerver_platform_close(srv->
|
|
901
|
+
if (srv->acceptors[i].listen_fd != srv->sock_fd &&
|
|
902
|
+
srv->acceptors[i].listen_fd != CERVER_INVALID_SOCK)
|
|
903
|
+
cerver_platform_close(srv->acceptors[i].listen_fd);
|
|
874
904
|
#endif // __linux__ || __APPLE__ || __FreeBSD__
|
|
875
|
-
if (srv->
|
|
905
|
+
if (srv->acceptors[i].event_fd >= 0)
|
|
876
906
|
#if !CERVER_PLATFORM_WINDOWS
|
|
877
|
-
close(srv->
|
|
907
|
+
close(srv->acceptors[i].event_fd);
|
|
878
908
|
#else
|
|
879
|
-
closesocket((SOCKET)srv->
|
|
909
|
+
closesocket((SOCKET)srv->acceptors[i].event_fd);
|
|
880
910
|
#endif // !CERVER_PLATFORM_WINDOWS
|
|
881
911
|
}
|
|
882
|
-
free(srv->
|
|
883
|
-
srv->
|
|
912
|
+
free(srv->acceptors);
|
|
913
|
+
srv->acceptors = NULL;
|
|
884
914
|
}
|
|
885
915
|
|
|
886
916
|
if (srv->route_trie) {
|
|
@@ -894,7 +924,7 @@ void cerver_shutdown(cerver_server_t* srv) {
|
|
|
894
924
|
}
|
|
895
925
|
|
|
896
926
|
cq_destroy(&g_conn_queue);
|
|
897
|
-
|
|
927
|
+
cerver_mutex_destroy(&srv->stat_cache.lock);
|
|
898
928
|
|
|
899
929
|
#if CERVER_PLATFORM_WINDOWS
|
|
900
930
|
cerver_wsa_cleanup();
|