Skip to content

Commit 30f34b9

Browse files
committed
fasthttp: implement notify
1 parent 9035f43 commit 30f34b9

4 files changed

Lines changed: 254 additions & 600 deletions

File tree

vlib/fasthttp/README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ The `fasthttp` module is a high-performance HTTP server library for V that provi
44

55
## Features
66

7-
- **High Performance**: Uses platform-specific I/O multiplexing:
8-
- `epoll` on Linux for efficient connection handling
9-
- `kqueue` on macOS for high-performance event notification
7+
- **High Performance**: Uses `os.notify` for portable I/O multiplexing
8+
(epoll on Linux, kqueue on macOS)
109
- **Non-blocking I/O**: Handles multiple concurrent connections efficiently
1110
- **Simple API**: Easy-to-use request handler pattern
1211
- **Cross-platform**: Supports Linux and macOS
@@ -157,8 +156,7 @@ detailed server implementation with multiple routes and controllers.
157156

158157
## Platform Support
159158

160-
- **Linux**: Uses `epoll` for high-performance I/O multiplexing
161-
- **macOS**: Uses `kqueue` for event notification
159+
- **Linux/macOS**: Uses `os.notify` for portable high-performance I/O
162160
- **Windows**: Currently not supported
163161

164162
## Performance Considerations

vlib/fasthttp/fasthttp.v

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module fasthttp
55

66
import runtime
77
import net
8+
import os.notify
9+
import time
810

911
#include <fcntl.h>
1012
#include <errno.h>
@@ -70,3 +72,252 @@ pub:
7072
handler fn (HttpRequest) ![]u8 @[required]
7173
user_data voidptr
7274
}
75+
76+
struct Server {
77+
pub:
78+
port int = 3000
79+
max_request_buffer_size int = 8192
80+
user_data voidptr
81+
mut:
82+
listen_fds []int = []int{len: max_thread_pool_size, cap: max_thread_pool_size}
83+
threads []thread = []thread{len: max_thread_pool_size, cap: max_thread_pool_size}
84+
request_handler fn (HttpRequest) ![]u8 @[required]
85+
}
86+
87+
// new_server creates and initializes a new Server instance.
88+
pub fn new_server(config ServerConfig) !&Server {
89+
if config.max_request_buffer_size <= 0 {
90+
return error('max_request_buffer_size must be greater than 0')
91+
}
92+
mut server := &Server{
93+
port: config.port
94+
max_request_buffer_size: config.max_request_buffer_size
95+
user_data: config.user_data
96+
request_handler: config.handler
97+
}
98+
unsafe {
99+
server.listen_fds.flags.set(.noslices | .noshrink | .nogrow)
100+
server.threads.flags.set(.noslices | .noshrink | .nogrow)
101+
}
102+
return server
103+
}
104+
105+
fn set_blocking(fd int, blocking bool) {
106+
flags := C.fcntl(fd, C.F_GETFL, 0)
107+
if flags == -1 {
108+
// TODO: better error handling
109+
eprintln(@LOCATION)
110+
return
111+
}
112+
if blocking {
113+
// This removes the O_NONBLOCK flag from flags and set it.
114+
C.fcntl(fd, C.F_SETFL, flags & ~C.O_NONBLOCK)
115+
} else {
116+
// This adds the O_NONBLOCK flag from flags and set it.
117+
C.fcntl(fd, C.F_SETFL, flags | C.O_NONBLOCK)
118+
}
119+
}
120+
121+
fn close_socket(fd int) bool {
122+
ret := C.close(fd)
123+
if ret == -1 {
124+
if C.errno == C.EINTR {
125+
// Interrupted by signal, retry is safe
126+
return close_socket(fd)
127+
}
128+
eprintln('ERROR: close(fd=${fd}) failed with errno=${C.errno}')
129+
return false
130+
}
131+
return true
132+
}
133+
134+
fn create_server_socket(port int) int {
135+
// Create a socket with non-blocking mode
136+
server_fd := C.socket(net.AddrFamily.ip, net.SocketType.tcp, 0)
137+
if server_fd < 0 {
138+
eprintln(@LOCATION)
139+
C.perror(c'Socket creation failed')
140+
return -1
141+
}
142+
143+
set_blocking(server_fd, false)
144+
145+
// Enable SO_REUSEADDR and SO_REUSEPORT
146+
opt := 1
147+
if C.setsockopt(server_fd, C.SOL_SOCKET, C.SO_REUSEADDR, &opt, sizeof(opt)) < 0 {
148+
eprintln(@LOCATION)
149+
C.perror(c'setsockopt SO_REUSEADDR failed')
150+
close_socket(server_fd)
151+
return -1
152+
}
153+
if C.setsockopt(server_fd, C.SOL_SOCKET, C.SO_REUSEPORT, &opt, sizeof(opt)) < 0 {
154+
eprintln(@LOCATION)
155+
C.perror(c'setsockopt SO_REUSEPORT failed')
156+
close_socket(server_fd)
157+
return -1
158+
}
159+
160+
addr := net.new_ip(u16(port), [u8(0), 0, 0, 0]!)
161+
alen := addr.len()
162+
if C.bind(server_fd, voidptr(&addr), alen) < 0 {
163+
eprintln(@LOCATION)
164+
C.perror(c'Bind failed')
165+
close_socket(server_fd)
166+
return -1
167+
}
168+
if C.listen(server_fd, max_connection_size) < 0 {
169+
eprintln(@LOCATION)
170+
C.perror(c'Listen failed')
171+
close_socket(server_fd)
172+
return -1
173+
}
174+
return server_fd
175+
}
176+
177+
fn handle_accept_loop(mut notifier notify.FdNotifier, listen_fd int) {
178+
for {
179+
client_fd := C.accept4(listen_fd, C.NULL, C.NULL, C.SOCK_NONBLOCK)
180+
if client_fd < 0 {
181+
if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK {
182+
break // No more incoming connections; exit loop.
183+
}
184+
eprintln(@LOCATION)
185+
C.perror(c'Accept failed')
186+
break
187+
}
188+
// Enable TCP_NODELAY for lower latency
189+
opt := 1
190+
C.setsockopt(client_fd, C.IPPROTO_TCP, C.TCP_NODELAY, &opt, sizeof(opt))
191+
// Register client socket with notifier
192+
notifier.add(client_fd, .read, .edge_trigger) or {
193+
eprintln('notifier.add failed: ${err}')
194+
close_socket(client_fd)
195+
}
196+
}
197+
}
198+
199+
fn handle_client_closure(mut notifier notify.FdNotifier, client_fd int) {
200+
// Never close the listening socket here
201+
if client_fd == 0 {
202+
return
203+
}
204+
if client_fd <= 0 {
205+
eprintln('ERROR: Invalid FD=${client_fd} for closure')
206+
return
207+
}
208+
notifier.remove(client_fd) or {}
209+
close_socket(client_fd)
210+
}
211+
212+
fn process_events(mut server Server, listen_fd int) {
213+
mut notifier := notify.new() or {
214+
eprintln('Failed to create notifier: ${err}')
215+
return
216+
}
217+
defer {
218+
notifier.close() or {}
219+
}
220+
notifier.add(listen_fd, .read, .edge_trigger) or {
221+
eprintln('Failed to add listen fd to notifier: ${err}')
222+
return
223+
}
224+
mut request_buffer := []u8{len: server.max_request_buffer_size, cap: server.max_request_buffer_size}
225+
unsafe {
226+
request_buffer.flags.set(.noslices | .nogrow | .noshrink)
227+
}
228+
for {
229+
for event in notifier.wait(time.infinite) {
230+
if event.fd == listen_fd {
231+
handle_accept_loop(mut notifier, listen_fd)
232+
continue
233+
}
234+
if event.kind.has(.error) || event.kind.has(.hangup) {
235+
client_fd := event.fd
236+
if client_fd == listen_fd {
237+
eprintln('ERROR: listen fd had HUP/ERR')
238+
continue
239+
}
240+
if client_fd > 0 {
241+
C.send(client_fd, status_444_response.data, status_444_response.len,
242+
C.MSG_NOSIGNAL)
243+
handle_client_closure(mut notifier, client_fd)
244+
} else {
245+
eprintln('ERROR: Invalid FD from notifier: ${client_fd}')
246+
}
247+
continue
248+
}
249+
if event.kind.has(.read) {
250+
client_fd := event.fd
251+
bytes_read := C.recv(client_fd, unsafe { &request_buffer[0] }, server.max_request_buffer_size - 1,
252+
0)
253+
if bytes_read > 0 {
254+
// Check if request exceeds buffer size
255+
if bytes_read >= server.max_request_buffer_size - 1 {
256+
C.send(client_fd, status_413_response.data, status_413_response.len,
257+
C.MSG_NOSIGNAL)
258+
handle_client_closure(mut notifier, client_fd)
259+
continue
260+
}
261+
mut readed_request_buffer := []u8{cap: bytes_read}
262+
unsafe {
263+
readed_request_buffer.push_many(&request_buffer[0], bytes_read)
264+
}
265+
mut decoded_http_request := decode_http_request(readed_request_buffer) or {
266+
eprintln('Error decoding request ${err}')
267+
C.send(client_fd, tiny_bad_request_response.data, tiny_bad_request_response.len,
268+
C.MSG_NOSIGNAL)
269+
handle_client_closure(mut notifier, client_fd)
270+
continue
271+
}
272+
decoded_http_request.client_conn_fd = client_fd
273+
decoded_http_request.user_data = server.user_data
274+
response_buffer := server.request_handler(decoded_http_request) or {
275+
eprintln('Error handling request ${err}')
276+
C.send(client_fd, tiny_bad_request_response.data, tiny_bad_request_response.len,
277+
C.MSG_NOSIGNAL)
278+
handle_client_closure(mut notifier, client_fd)
279+
continue
280+
}
281+
// Send response
282+
sent := C.send(client_fd, response_buffer.data, response_buffer.len,
283+
C.MSG_NOSIGNAL | C.MSG_DONTWAIT)
284+
if sent < 0 && C.errno != C.EAGAIN && C.errno != C.EWOULDBLOCK {
285+
eprintln('ERROR: send() failed with errno=${C.errno}')
286+
handle_client_closure(mut notifier, client_fd)
287+
continue
288+
}
289+
// Leave the connection open; closure is driven by client FIN or errors
290+
} else if bytes_read == 0 {
291+
// Normal client closure (FIN received)
292+
handle_client_closure(mut notifier, client_fd)
293+
} else if bytes_read < 0 && C.errno != C.EAGAIN && C.errno != C.EWOULDBLOCK {
294+
// Unexpected recv error - send 444 No Response
295+
C.send(client_fd, status_444_response.data, status_444_response.len,
296+
C.MSG_NOSIGNAL)
297+
handle_client_closure(mut notifier, client_fd)
298+
}
299+
}
300+
}
301+
}
302+
}
303+
304+
// run starts the server and begins listening for incoming connections.
305+
pub fn (mut server Server) run() ! {
306+
$if windows {
307+
eprintln('Windows is not supported yet')
308+
return
309+
}
310+
for i := 0; i < max_thread_pool_size; i++ {
311+
server.listen_fds[i] = create_server_socket(server.port)
312+
if server.listen_fds[i] < 0 {
313+
return
314+
}
315+
server.threads[i] = spawn process_events(mut server, server.listen_fds[i])
316+
}
317+
318+
println('listening on http://localhost:${server.port}/')
319+
// Main thread waits for workers; accepts are handled in worker epoll loops
320+
for i in 0 .. max_thread_pool_size {
321+
server.threads[i].wait()
322+
}
323+
}

0 commit comments

Comments
 (0)