vxx2 / vlib / fasthttp / fasthttp_bsd.c.v
732 lines · 664 sloc · 20.96 KB · b15632730f263e4586e84ba9c2f19d2f7d45a82d
Raw
1module fasthttp
2
3import net
4import sync.stdatomic
5import time
6
7#include <sys/event.h>
8#include <sys/stat.h>
9#include <sys/types.h>
10#include <sys/socket.h>
11#include <sys/uio.h>
12#include <signal.h>
13
14fn C.signal(sig int, handler voidptr) voidptr
15
16const buf_size = max_connection_size
17const kqueue_max_events = 128
18const backlog = max_connection_size
19const kqueue_wait_timeout_ms = 100
20
21// send_flags is OR'd into every C.send() call in this file. On OpenBSD,
22// which lacks the per-socket SO_NOSIGPIPE option, we pass MSG_NOSIGNAL
23// instead so writes to a disconnected peer return EPIPE rather than
24// killing the process with SIGPIPE. Other BSDs use SO_NOSIGPIPE set on
25// the socket at accept time (see accept_clients).
26const send_flags = $if openbsd { int(C.MSG_NOSIGNAL) } $else { 0 }
27
28fn C.kevent(kq i32, changelist &C.kevent, nchanges i32, eventlist &C.kevent, nevents i32, timeout &C.timespec) i32
29fn C.kqueue() i32
30fn C.fstat(fd i32, buf &C.stat) i32
31
32// send_file_bytes has three implementations across BSD-family OSes:
33// macOS: int sendfile(int fd, int s, off_t offset, off_t *len, sf_hdtr *hdtr, int flags);
34// (len is in/out: caller sets bytes-to-send, kernel writes bytes-actually-sent)
35// FreeBSD/NetBSD/DragonFly:
36// int sendfile(int fd, int s, off_t offset, size_t nbytes, sf_hdtr *hdtr, off_t *sbytes, int flags);
37// (nbytes is input, sbytes is the separate out-param for bytes actually sent)
38// OpenBSD: no sendfile(2) syscall at all. We fall back to a single
39// pread(2) + send(2) pair per call, using a bounded stack
40// buffer. The outer send_pending() loop will call us again
41// until the file is drained or the socket blocks.
42
43const sendfile_fallback_buf_size = 16384
44
45// send_file_bytes asks the kernel to send up to `nbytes` bytes from `file_fd` at
46// `offset` into socket `sock_fd`, in a single non-blocking operation.
47// Returns (ret, sent) where `ret` is 0 on success or -1 on error (errno set),
48// and `sent` is the number of bytes transferred this call (may be >0 even when ret==-1).
49fn send_file_bytes(file_fd i32, sock_fd i32, offset i64, nbytes i64) (int, i64) {
50 $if macos {
51 mut len := nbytes
52 ret := C.sendfile(file_fd, sock_fd, offset, &len, unsafe { nil }, 0)
53 return int(ret), len
54 } $else $if openbsd {
55 // No sendfile(2) on OpenBSD; pread into a stack buffer, then send.
56 // Cap one call at sendfile_fallback_buf_size so we don't starve
57 // other connections in the kqueue loop.
58 mut buf := [sendfile_fallback_buf_size]u8{}
59 mut want := nbytes
60 if want > sendfile_fallback_buf_size {
61 want = sendfile_fallback_buf_size
62 }
63 nread := C.pread(file_fd, &buf[0], usize(want), offset)
64 if nread <= 0 {
65 // nread == 0 is EOF (shouldn't happen given write_pos < file_len
66 // guards in send_pending, but treat it as an error to close the
67 // connection); nread < 0 propagates errno for EAGAIN handling.
68 return -1, i64(0)
69 }
70 nsent := C.send(sock_fd, &buf[0], usize(nread), send_flags)
71 if nsent < 0 {
72 return -1, i64(0)
73 }
74 return 0, i64(nsent)
75 } $else {
76 mut sbytes := i64(0)
77 ret := C.sendfile(file_fd, sock_fd, offset, usize(nbytes), unsafe { nil }, &sbytes, 0)
78 return int(ret), sbytes
79 }
80}
81
82$if macos {
83 // int sendfile(int fd, int s, off_t offset, off_t *len, struct sf_hdtr *hdtr, int flags);
84 fn C.sendfile(fd i32, s i32, offset i64, len &i64, hdtr voidptr, flags i32) i32
85} $else $if openbsd {
86 // ssize_t pread(int fd, void *buf, size_t nbyte, off_t offset);
87 fn C.pread(fd i32, buf voidptr, nbyte usize, offset i64) isize
88} $else {
89 // int sendfile(int fd, int s, off_t offset, size_t nbytes, struct sf_hdtr *hdtr, off_t *sbytes, int flags);
90 fn C.sendfile(fd i32, s i32, offset i64, nbytes usize, hdtr voidptr, sbytes &i64, flags i32) i32
91}
92
93struct C.kevent {
94 ident u64
95 filter i16
96 flags u16
97 fflags u32
98 data isize
99 udata voidptr
100}
101
102// Helper to set fields of a kevent struct.
103fn ev_set(mut ev C.kevent, ident u64, filter i16, flags u16, fflags u32, data isize, udata voidptr) {
104 ev.ident = ident
105 ev.filter = filter
106 ev.flags = flags
107 ev.fflags = fflags
108 ev.data = data
109 ev.udata = udata
110}
111
112struct Conn {
113 fd int
114 user_data voidptr
115mut:
116 read_buf [buf_size]u8
117 read_len int
118 read_extra []u8 // dynamic overflow buffer for large requests (e.g. chunked uploads)
119 write_buf []u8
120 write_pos int
121 request_active bool
122 read_start i64 // monotonic timestamp (in microseconds) when first data was received
123
124 // Sendfile state
125 file_fd int = -1
126 file_len i64
127 file_pos i64
128 should_close bool
129 request_arena voidptr
130}
131
132fn (mut c Conn) free_write_buf() {
133 if c.write_buf.cap > 0 {
134 unsafe { c.write_buf.free() }
135 c.write_buf = []u8{}
136 }
137}
138
139fn (mut c Conn) free_request_arena() {
140 $if prealloc {
141 if c.request_arena != unsafe { nil } {
142 unsafe { prealloc_scope_free_after(c.request_arena) }
143 c.request_arena = unsafe { nil }
144 }
145 }
146}
147
148pub struct Server {
149pub mut:
150 family net.AddrFamily = .ip6
151 port int
152 max_request_buffer_size int = 8192
153 timeout_in_seconds int = 30
154 socket_fd int = -1
155 poll_fd int = -1 // kqueue fd
156 user_data voidptr
157 request_handler fn (HttpRequest) !HttpResponse @[required]
158 running &stdatomic.AtomicVal[bool] = stdatomic.new_atomic(false)
159 shutting_down &stdatomic.AtomicVal[bool] = stdatomic.new_atomic(false)
160 stopped &stdatomic.AtomicVal[bool] = stdatomic.new_atomic(true)
161 active_requests &stdatomic.AtomicVal[int] = stdatomic.new_atomic(0)
162}
163
164// new_server creates and initializes a new Server instance.
165pub fn new_server(config ServerConfig) !&Server {
166 mut server := &Server{
167 family: config.family
168 port: config.port
169 max_request_buffer_size: config.max_request_buffer_size
170 timeout_in_seconds: config.timeout_in_seconds
171 user_data: config.user_data
172 request_handler: config.handler
173 running: stdatomic.new_atomic(false)
174 shutting_down: stdatomic.new_atomic(false)
175 stopped: stdatomic.new_atomic(true)
176 active_requests: stdatomic.new_atomic(0)
177 }
178 return server
179}
180
181fn set_nonblocking(fd int) {
182 flags := C.fcntl(fd, C.F_GETFL, 0)
183 if flags == -1 {
184 return
185 }
186 C.fcntl(fd, C.F_SETFL, flags | C.O_NONBLOCK)
187}
188
189fn add_event(kq int, ident u64, filter i16, flags u16, udata voidptr) int {
190 mut ev := C.kevent{}
191 ev_set(mut &ev, ident, filter, flags, u32(0), isize(0), udata)
192 return C.kevent(kq, &ev, 1, unsafe { nil }, 0, unsafe { nil })
193}
194
195fn delete_event(kq int, ident u64, filter i16, udata voidptr) {
196 mut ev := C.kevent{}
197 ev_set(mut &ev, ident, filter, u16(C.EV_DELETE), u32(0), isize(0), udata)
198 C.kevent(kq, &ev, 1, unsafe { nil }, 0, unsafe { nil })
199}
200
201fn close_conn(server &Server, kq int, c_ptr voidptr, mut clients map[int]voidptr) {
202 mut c := unsafe { &Conn(c_ptr) }
203 clients.delete(c.fd)
204 delete_event(kq, u64(c.fd), i16(C.EVFILT_READ), c)
205 delete_event(kq, u64(c.fd), i16(C.EVFILT_WRITE), c)
206 C.close(c.fd)
207 if c.request_active {
208 server.end_request()
209 c.request_active = false
210 }
211 c.free_write_buf()
212 c.free_request_arena()
213 if c.read_extra.cap > 0 {
214 unsafe { c.read_extra.free() }
215 }
216 if c.file_fd != -1 {
217 C.close(c.file_fd)
218 c.file_fd = -1
219 }
220 unsafe { free(c_ptr) }
221}
222
223fn send_pending(c_ptr voidptr) bool {
224 mut c := unsafe { &Conn(c_ptr) }
225
226 // 1. Send memory buffer (headers or small response)
227 if c.write_pos < c.write_buf.len {
228 remaining := c.write_buf.len - c.write_pos
229 write_ptr := unsafe { &c.write_buf[0] + c.write_pos }
230 sent := C.send(c.fd, write_ptr, remaining, send_flags)
231 if sent > 0 {
232 c.write_pos += int(sent)
233 }
234 if sent < 0 {
235 if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK {
236 return true
237 }
238 c.should_close = true
239 return false
240 }
241 }
242
243 // 2. Send file if buffer is fully sent
244 if c.write_pos >= c.write_buf.len && c.file_fd != -1 {
245 remaining := c.file_len - c.file_pos
246 ret, sent := send_file_bytes(c.file_fd, c.fd, c.file_pos, remaining)
247 if sent > 0 {
248 c.file_pos += sent
249 }
250 if ret == -1 {
251 if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK {
252 return true
253 }
254 C.close(c.file_fd)
255 c.file_fd = -1
256 c.should_close = true
257 return false
258 }
259 if c.file_pos >= c.file_len {
260 C.close(c.file_fd)
261 c.file_fd = -1
262 } else {
263 return true
264 }
265 }
266
267 return !(c.write_pos >= c.write_buf.len && c.file_fd == -1)
268}
269
270const status_408_response = 'HTTP/1.1 408 Request Timeout\r\nContent-Type: text/plain\r\nContent-Length: 19\r\nConnection: close\r\n\r\n408 Request Timeout'.bytes()
271
272fn send_bad_request(fd int) {
273 C.send(fd, tiny_bad_request_response.data, tiny_bad_request_response.len, send_flags)
274}
275
276fn send_request_timeout(fd int) {
277 C.send(fd, status_408_response.data, status_408_response.len, send_flags)
278}
279
280fn handle_write(server &Server, kq int, c_ptr voidptr, mut clients map[int]voidptr) {
281 if send_pending(c_ptr) {
282 return
283 }
284 complete_response(server, kq, c_ptr, mut clients, true)
285}
286
287fn complete_response(server &Server, kq int, c_ptr voidptr, mut clients map[int]voidptr, remove_write_event bool) {
288 mut c := unsafe { &Conn(c_ptr) }
289 if remove_write_event {
290 delete_event(kq, u64(c.fd), i16(C.EVFILT_WRITE), c)
291 }
292 if server.is_shutting_down() || c.should_close {
293 close_conn(server, kq, c_ptr, mut clients)
294 return
295 }
296 if c.request_active {
297 server.end_request()
298 c.request_active = false
299 }
300 c.free_write_buf()
301 c.free_request_arena()
302 c.write_pos = 0
303 c.read_len = 0
304 if c.read_extra.cap > 0 {
305 unsafe { c.read_extra.free() }
306 c.read_extra = []u8{}
307 }
308 c.read_start = 0
309 c.should_close = false
310 add_event(kq, u64(c.fd), i16(C.EVFILT_READ), u16(C.EV_ADD | C.EV_ENABLE | C.EV_CLEAR), c)
311}
312
313// process_request handles a complete HTTP request: decodes, calls the handler,
314// sends the response (or handles takeover/sendfile).
315fn process_request(server &Server, kq int, c_ptr voidptr, mut clients map[int]voidptr) {
316 mut c := unsafe { &Conn(c_ptr) }
317 mut request_arena := voidptr(unsafe { nil })
318 $if prealloc {
319 request_arena = unsafe { prealloc_scope_begin() }
320 }
321
322 mut req_buf := c.get_full_request_data()
323 if c.read_extra.cap > 0 {
324 unsafe { c.read_extra.free() }
325 c.read_extra = []u8{}
326 }
327
328 mut decoded := decode_http_request(req_buf) or {
329 send_bad_request(c.fd)
330 end_request_arena_current_thread(request_arena)
331 close_conn(server, kq, c_ptr, mut clients)
332 return
333 }
334 $if trace_prealloc ? {
335 unsafe { prealloc_scope_checkpoint(c'fasthttp decoded request') }
336 }
337 server.begin_request()
338 c.request_active = true
339 decoded.client_conn_fd = c.fd
340 decoded.user_data = server.user_data
341
342 mut resp := server.request_handler(decoded) or {
343 send_bad_request(c.fd)
344 end_request_arena_current_thread(request_arena)
345 close_conn(server, kq, c_ptr, mut clients)
346 return
347 }
348 $if trace_prealloc ? {
349 unsafe { prealloc_scope_checkpoint(c'fasthttp handler returned') }
350 }
351 resp.attach_request_arena_if_empty(request_arena)
352
353 match resp.takeover_mode {
354 .manual {
355 // The handler has taken ownership of the connection.
356 // Remove from kqueue and tracking before ending the request, but do
357 // NOT close the fd.
358 clients.delete(c.fd)
359 delete_event(kq, u64(c.fd), i16(C.EVFILT_READ), c)
360 delete_event(kq, u64(c.fd), i16(C.EVFILT_WRITE), c)
361 if c.request_active {
362 server.end_request()
363 c.request_active = false
364 }
365 resp.free_owned_content()
366 resp.abandon_request_arena_current_thread()
367 unsafe { free(c_ptr) }
368 return
369 }
370 .reusable {
371 set_nonblocking(c.fd)
372 c.read_len = 0
373 c.read_extra.clear()
374 c.read_start = 0
375 if c.request_active {
376 server.end_request()
377 c.request_active = false
378 }
379 resp.free_owned_content()
380 resp.end_request_arena_current_thread()
381 if server.is_shutting_down() || resp.should_close {
382 close_conn(server, kq, c_ptr, mut clients)
383 }
384 return
385 }
386 .none {}
387 }
388
389 c.should_close = resp.should_close
390 c.free_write_buf()
391 c.free_request_arena()
392 c.request_arena = resp.take_request_arena()
393 c.write_buf = resp.take_or_clone_content()
394 $if trace_prealloc ? {
395 unsafe { prealloc_scope_checkpoint(c'fasthttp response retained') }
396 }
397 leave_request_arena_current_thread(c.request_arena)
398 if resp.file_path != '' {
399 fd := C.open(resp.file_path.str, C.O_RDONLY, 0)
400 if fd != -1 {
401 mut st := C.stat{}
402 if C.fstat(fd, &st) == 0 {
403 c.file_fd = fd
404 c.file_len = st.st_size
405 c.file_pos = 0
406 } else {
407 C.close(fd)
408 }
409 }
410 }
411
412 c.write_pos = 0
413 c.read_len = 0
414 c.read_extra.clear()
415 c.read_start = 0
416
417 if send_pending(c_ptr) {
418 add_event(kq, u64(c.fd), i16(C.EVFILT_WRITE), u16(C.EV_ADD | C.EV_ENABLE | C.EV_CLEAR), c)
419 return
420 }
421
422 complete_response(server, kq, c_ptr, mut clients, false)
423}
424
425// total_read_len returns the total number of request bytes received so far,
426// including both the fixed read_buf and the dynamic read_extra overflow.
427fn (c &Conn) total_read_len() int {
428 return c.read_len + c.read_extra.len
429}
430
431// get_full_request_data copies the complete received data into a single []u8.
432fn (c &Conn) get_full_request_data() []u8 {
433 total := c.total_read_len()
434 mut req_buf := []u8{cap: total}
435 unsafe {
436 req_buf.push_many(&c.read_buf[0], c.read_len)
437 }
438 if c.read_extra.len > 0 {
439 req_buf << c.read_extra
440 }
441 return req_buf
442}
443
444fn handle_read(server &Server, kq int, c_ptr voidptr, mut clients map[int]voidptr) {
445 mut c := unsafe { &Conn(c_ptr) }
446
447 // Drain the socket for this kqueue notification. EV_CLEAR only rearms once
448 // all readable data has been consumed.
449 for {
450 if c.read_len < buf_size {
451 n := C.recv(c.fd, &c.read_buf[c.read_len], buf_size - c.read_len, 0)
452 if n < 0 {
453 if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK {
454 break
455 }
456 C.send(c.fd, status_444_response.data, status_444_response.len, send_flags)
457 close_conn(server, kq, c_ptr, mut clients)
458 return
459 }
460 if n == 0 {
461 if c.total_read_len() == 0 {
462 close_conn(server, kq, c_ptr, mut clients)
463 return
464 }
465 break
466 }
467 c.read_len += int(n)
468 } else {
469 // Fixed buffer is full, read the rest into dynamic overflow.
470 mut tmp := []u8{len: 65536}
471 n := C.recv(c.fd, tmp.data, tmp.len, 0)
472 if n < 0 {
473 if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK {
474 break
475 }
476 C.send(c.fd, status_444_response.data, status_444_response.len, send_flags)
477 close_conn(server, kq, c_ptr, mut clients)
478 return
479 }
480 if n == 0 {
481 if c.total_read_len() == 0 {
482 close_conn(server, kq, c_ptr, mut clients)
483 return
484 }
485 break
486 }
487 c.read_extra << tmp[..int(n)]
488 }
489 }
490
491 total := c.total_read_len()
492 if total == 0 {
493 return
494 }
495
496 // Enforce the configured header limit without capping large request bodies.
497 mut header_end := -1
498 mut full_data := []u8{}
499 if c.read_extra.len > 0 {
500 full_data = c.get_full_request_data()
501 header_end = find_header_end_in_buf(full_data.data, full_data.len)
502 } else {
503 header_end = find_header_end_in_buf(&c.read_buf[0], c.read_len)
504 }
505 if (header_end == -1 && total >= server.max_request_buffer_size)
506 || header_end > server.max_request_buffer_size {
507 C.send(c.fd, status_413_response.data, status_413_response.len, send_flags)
508 close_conn(server, kq, c_ptr, mut clients)
509 return
510 }
511
512 // Record when we first started receiving data for this request
513 if c.read_start == 0 {
514 c.read_start = time.sys_mono_now()
515 }
516
517 // Check if the full body has been received.
518 if c.read_extra.len > 0 {
519 if !has_complete_body(full_data.data, full_data.len) {
520 elapsed_ns := time.sys_mono_now() - c.read_start
521 timeout_ns := i64(server.timeout_in_seconds) * 1_000_000_000
522 if elapsed_ns >= timeout_ns {
523 send_request_timeout(c.fd)
524 close_conn(server, kq, c_ptr, mut clients)
525 }
526 return
527 }
528 } else if !has_complete_body(&c.read_buf[0], c.read_len) {
529 // Body not complete yet - check for timeout
530 elapsed_ns := time.sys_mono_now() - c.read_start
531 timeout_ns := i64(server.timeout_in_seconds) * 1_000_000_000
532 if elapsed_ns >= timeout_ns {
533 send_request_timeout(c.fd)
534 close_conn(server, kq, c_ptr, mut clients)
535 }
536 // Otherwise wait for more data on the next kqueue event
537 return
538 }
539
540 process_request(server, kq, c_ptr, mut clients)
541}
542
543fn accept_clients(kq int, listen_fd int, mut clients map[int]voidptr) {
544 for {
545 client_fd := C.accept(listen_fd, unsafe { nil }, unsafe { nil })
546 if client_fd < 0 {
547 if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK {
548 break
549 }
550 C.perror(c'accept')
551 break
552 }
553 set_nonblocking(client_fd)
554 // Prevent SIGPIPE on writes to disconnected clients. macOS and
555 // FreeBSD/NetBSD/DragonFly expose the per-socket SO_NOSIGPIPE
556 // option; OpenBSD does not, and instead expects MSG_NOSIGNAL on
557 // each send(2) call (handled via the `send_flags` const above).
558 $if !openbsd {
559 nosigpipe_opt := 1
560 C.setsockopt(client_fd, C.SOL_SOCKET, C.SO_NOSIGPIPE, &nosigpipe_opt, sizeof(int))
561 }
562 mut c := &Conn{
563 fd: client_fd
564 user_data: unsafe { nil }
565 file_fd: -1
566 }
567 add_event(kq, u64(client_fd), i16(C.EVFILT_READ), u16(C.EV_ADD | C.EV_ENABLE | C.EV_CLEAR),
568 c)
569 clients[client_fd] = c
570 }
571}
572
573fn close_all_conns(server &Server, kq int, mut clients map[int]voidptr) {
574 for client_fd in clients.keys() {
575 c_ptr := clients[client_fd] or { continue }
576 close_conn(server, kq, c_ptr, mut clients)
577 }
578}
579
580fn (mut s Server) stop_accepting() {
581 if s.poll_fd >= 0 && s.socket_fd >= 0 {
582 delete_event(s.poll_fd, u64(s.socket_fd), i16(C.EVFILT_READ), unsafe { nil })
583 }
584 if s.socket_fd >= 0 {
585 C.close(s.socket_fd)
586 s.socket_fd = -1
587 }
588}
589
590// run starts the server and enters the main event loop (Kqueue version).
591pub fn (mut s Server) run() ! {
592 // Ignore SIGPIPE process-wide. Writing to a disconnected socket raises
593 // SIGPIPE by default, which kills the process. We suppress it per-send
594 // (SO_NOSIGPIPE on macos/freebsd/netbsd/dragonfly, MSG_NOSIGNAL on
595 // openbsd), but this signal handler is a safety net for any code path
596 // that might miss it (e.g. spawned SSE/WebSocket threads using
597 // TcpConn.write).
598 C.signal(C.SIGPIPE, C.SIG_IGN)
599
600 s.socket_fd = C.socket(i32(s.family), i32(net.SocketType.tcp), 0)
601 if s.socket_fd < 0 {
602 C.perror(c'socket')
603 return error('socket creation failed')
604 }
605
606 opt := 1
607 C.setsockopt(s.socket_fd, C.SOL_SOCKET, C.SO_REUSEADDR, &opt, sizeof(int))
608
609 addr := if s.family == .ip6 {
610 net.new_ip6(u16(s.port), [u8(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]!)
611 } else {
612 net.new_ip(u16(s.port), [u8(0), 0, 0, 0]!)
613 }
614 alen := addr.len()
615
616 if C.bind(s.socket_fd, voidptr(&addr), alen) < 0 {
617 C.perror(c'bind')
618 return error('socket bind failed')
619 }
620 if C.listen(s.socket_fd, backlog) < 0 {
621 C.perror(c'listen')
622 return error('socket listen failed')
623 }
624
625 set_nonblocking(s.socket_fd)
626
627 s.poll_fd = C.kqueue()
628 if s.poll_fd < 0 {
629 C.perror(c'kqueue')
630 return error('kqueue creation failed')
631 }
632
633 add_event(s.poll_fd, u64(s.socket_fd), i16(C.EVFILT_READ),
634 u16(C.EV_ADD | C.EV_ENABLE | C.EV_CLEAR), unsafe { nil })
635
636 listen_fd := s.socket_fd
637 s.mark_running()
638 println('listening on http://0.0.0.0:${s.port}/')
639
640 mut events := [kqueue_max_events]C.kevent{}
641 mut clients := map[int]voidptr{}
642 for {
643 if s.is_shutting_down() && s.active_request_count() == 0 {
644 close_all_conns(s, s.poll_fd, mut clients)
645 break
646 }
647 timeout := C.timespec{
648 tv_sec: 0
649 tv_nsec: kqueue_wait_timeout_ms * 1_000_000
650 }
651 nev := C.kevent(s.poll_fd, unsafe { nil }, 0, &events[0], kqueue_max_events, &timeout)
652 if nev < 0 {
653 if C.errno == C.EINTR {
654 // kevent may return EINTR when the process receives a signal
655 // (e.g. SIGCHLD from an exec'd subprocess). Treat like a timeout.
656 continue
657 }
658 if s.is_shutting_down() {
659 continue
660 }
661 C.perror(c'kevent')
662 break
663 }
664
665 for i := 0; i < nev; i++ {
666 event := events[i]
667 if event.flags & u16(C.EV_ERROR) != 0 {
668 if event.ident == u64(listen_fd) {
669 C.perror(c'listener error')
670 continue
671 }
672 if event.udata != unsafe { nil } {
673 close_conn(s, s.poll_fd, event.udata, mut clients)
674 }
675 continue
676 }
677
678 if event.ident == u64(listen_fd) {
679 if s.is_shutting_down() {
680 continue
681 }
682 accept_clients(s.poll_fd, listen_fd, mut clients)
683 continue
684 }
685
686 if event.udata == unsafe { nil } {
687 continue
688 }
689
690 if event.flags & u16(C.EV_EOF) != 0 {
691 close_conn(s, s.poll_fd, event.udata, mut clients)
692 continue
693 }
694
695 if event.filter == i16(C.EVFILT_READ) {
696 if s.is_shutting_down() {
697 close_conn(s, s.poll_fd, event.udata, mut clients)
698 continue
699 }
700 handle_read(s, s.poll_fd, event.udata, mut clients)
701 } else if event.filter == i16(C.EVFILT_WRITE) {
702 handle_write(s, s.poll_fd, event.udata, mut clients)
703 }
704 }
705 // Sweep for connections waiting for body data that have timed out
706 if s.timeout_in_seconds > 0 {
707 now := time.sys_mono_now()
708 timeout_ns := i64(s.timeout_in_seconds) * 1_000_000_000
709 for client_fd in clients.keys() {
710 c_ptr := clients[client_fd] or { continue }
711 c := unsafe { &Conn(c_ptr) }
712 if c.read_start > 0 && c.read_len > 0 && !c.request_active {
713 elapsed := now - c.read_start
714 if elapsed >= timeout_ns {
715 send_request_timeout(c.fd)
716 close_conn(s, s.poll_fd, c_ptr, mut clients)
717 }
718 }
719 }
720 }
721 }
722
723 if s.socket_fd >= 0 {
724 C.close(s.socket_fd)
725 s.socket_fd = -1
726 }
727 if s.poll_fd >= 0 {
728 C.close(s.poll_fd)
729 s.poll_fd = -1
730 }
731 s.mark_stopped()
732}
733