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