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