Skip to content

Commit a43064a

Browse files
authored
picoev, picohttparser: reimplement in V (#18506)
1 parent 045adb6 commit a43064a

File tree

16 files changed

+1651
-258
lines changed

16 files changed

+1651
-258
lines changed

cmd/tools/check_os_api_parity.v

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ const (
2424
'crypto.rand',
2525
'os.bare',
2626
'os2',
27-
'picohttpparser',
28-
'picoev',
2927
'szip',
3028
'v.eval',
3129
]

cmd/tools/modules/testing/common.v

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,9 +625,8 @@ pub fn prepare_test_session(zargs string, folder string, oskipped []string, main
625625
continue
626626
}
627627
$if windows {
628-
// skip pico and process/command examples on windows
629-
if fnormalised.ends_with('examples/pico/pico.v')
630-
|| fnormalised.ends_with('examples/process/command.v') {
628+
// skip process/command examples on windows
629+
if fnormalised.ends_with('examples/process/command.v') {
631630
continue
632631
}
633632
}

examples/pico/pico.v

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import picoev
33
import picohttpparser
44

55
const (
6-
port = 8088
6+
port = 8089
77
)
88

99
struct Message {
@@ -24,21 +24,25 @@ fn hello_response() string {
2424
}
2525

2626
fn callback(data voidptr, req picohttpparser.Request, mut res picohttpparser.Response) {
27-
if picohttpparser.cmpn(req.method, 'GET ', 4) {
28-
if picohttpparser.cmp(req.path, '/t') {
27+
if req.method == 'GET' {
28+
if req.path == '/t' {
2929
res.http_ok()
3030
res.header_server()
3131
res.header_date()
3232
res.plain()
3333
res.body(hello_response())
34-
} else if picohttpparser.cmp(req.path, '/j') {
34+
} else if req.path == '/j' {
3535
res.http_ok()
3636
res.header_server()
3737
res.header_date()
3838
res.json()
3939
res.body(json_response())
4040
} else {
41-
res.http_404()
41+
res.http_ok()
42+
res.header_server()
43+
res.header_date()
44+
res.html()
45+
res.body('Hello Picoev!\n')
4246
}
4347
} else {
4448
res.http_405()
@@ -48,5 +52,6 @@ fn callback(data voidptr, req picohttpparser.Request, mut res picohttpparser.Res
4852

4953
fn main() {
5054
println('Starting webserver on http://127.0.0.1:${port}/ ...')
51-
picoev.new(port: port, cb: &callback).serve()
55+
mut server := picoev.new(port: port, cb: callback)
56+
server.serve()
5257
}

vlib/picoev/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
## Description:
22

3-
`picoev` is a thin wrapper over [picoev](https://github.com/kazuho/picoev),
3+
`picoev` is a V implementation of [picoev](https://github.com/kazuho/picoev),
44
which in turn is "A tiny, lightning fast event loop for network applications".

vlib/picoev/loop_default.c.v

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
module picoev
2+
3+
$if windows {
4+
#include <winsock2.h>
5+
#include <ws2tcpip.h>
6+
} $else {
7+
#include <sys/select.h>
8+
}
9+
10+
pub struct SelectLoop {
11+
mut:
12+
id int
13+
now i64
14+
}
15+
16+
type LoopType = SelectLoop
17+
18+
// create_select_loop creates a `SelectLoop` struct with `id`
19+
pub fn create_select_loop(id int) !&SelectLoop {
20+
return &SelectLoop{
21+
id: id
22+
}
23+
}
24+
25+
[direct_array_access]
26+
fn (mut pv Picoev) update_events(fd int, events int) int {
27+
// check if fd is in range
28+
assert fd < max_fds
29+
30+
pv.file_descriptors[fd].events = u32(events & picoev_readwrite)
31+
return 0
32+
}
33+
34+
[direct_array_access]
35+
fn (mut pv Picoev) poll_once(max_wait int) int {
36+
readfds, writefds, errorfds := C.fd_set{}, C.fd_set{}, C.fd_set{}
37+
38+
// setup
39+
C.FD_ZERO(&readfds)
40+
C.FD_ZERO(&writefds)
41+
C.FD_ZERO(&errorfds)
42+
43+
mut maxfd := 0
44+
45+
// find the maximum socket for `select` and add sockets to the fd_sets
46+
for target in pv.file_descriptors {
47+
if target.loop_id == pv.loop.id {
48+
if target.events & picoev_read != 0 {
49+
C.FD_SET(target.fd, &readfds)
50+
if maxfd < target.fd {
51+
maxfd = target.fd
52+
}
53+
}
54+
if target.events & picoev_write != 0 {
55+
C.FD_SET(target.fd, &writefds)
56+
if maxfd < target.fd {
57+
maxfd = target.fd
58+
}
59+
}
60+
}
61+
}
62+
63+
// select and handle sockets if any
64+
tv := C.timeval{
65+
tv_sec: u64(max_wait)
66+
tv_usec: 0
67+
}
68+
r := C.@select(maxfd + 1, &readfds, &writefds, &errorfds, &tv)
69+
if r == -1 {
70+
// timeout
71+
return -1
72+
} else if r > 0 {
73+
for target in pv.file_descriptors {
74+
if target.loop_id == pv.loop.id {
75+
// vfmt off
76+
read_events := (
77+
(if C.FD_ISSET(target.fd, &readfds) { picoev_read } else { 0 })
78+
|
79+
(if C.FD_ISSET(target.fd, &writefds) { picoev_write } else { 0 })
80+
)
81+
// vfmt on
82+
if read_events != 0 {
83+
$if trace_fd ? {
84+
eprintln('do callback ${target.fd}')
85+
}
86+
87+
// do callback!
88+
unsafe { target.cb(target.fd, read_events, &pv) }
89+
}
90+
}
91+
}
92+
}
93+
94+
return 0
95+
}

vlib/picoev/loop_freebsd.c.v

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
module picoev
2+
3+
#include <errno.h>
4+
#include <sys/types.h>
5+
#include <sys/event.h>
6+
7+
fn C.kevent(int, changelist voidptr, nchanges int, eventlist voidptr, nevents int, timout &C.timespec) int
8+
fn C.kqueue() int
9+
fn C.EV_SET(kev voidptr, ident int, filter i16, flags u16, fflags u32, data voidptr, udata voidptr)
10+
11+
pub struct C.kevent {
12+
pub mut:
13+
ident int
14+
// uintptr_t
15+
filter i16
16+
flags u16
17+
fflags u32
18+
data voidptr
19+
// intptr_t
20+
udata voidptr
21+
}
22+
23+
[heap]
24+
pub struct KqueueLoop {
25+
mut:
26+
id int
27+
now i64
28+
kq_id int
29+
// -1 if not changed
30+
changed_fds int
31+
events [1024]C.kevent
32+
changelist [256]C.kevent
33+
}
34+
35+
type LoopType = KqueueLoop
36+
37+
// create_kqueue_loop creates a new kernel event queue with loop_id=`id`
38+
pub fn create_kqueue_loop(id int) !&KqueueLoop {
39+
mut loop := &KqueueLoop{
40+
id: id
41+
}
42+
43+
loop.kq_id = C.kqueue()
44+
if loop.kq_id == -1 {
45+
return error('could not create kqueue loop!')
46+
}
47+
loop.changed_fds = -1
48+
return loop
49+
}
50+
51+
// ev_set sets a new `kevent` with file descriptor `index`
52+
[inline]
53+
pub fn (mut pv Picoev) ev_set(index int, operation int, events int) {
54+
// vfmt off
55+
filter := i16(
56+
(if events & picoev_read != 0 { C.EVFILT_READ } else { 0 })
57+
|
58+
(if events & picoev_write != 0 { C.EVFILT_WRITE } else { 0 })
59+
)
60+
// vfmt on
61+
C.EV_SET(&pv.loop.changelist[index], pv.loop.changed_fds, filter, operation, 0, 0,
62+
0)
63+
}
64+
65+
// backend_build uses the lower 8 bits to store the old events and the higher 8
66+
// bits to store the next file descriptor in `Target.backend`
67+
[inline]
68+
fn backend_build(next_fd int, events u32) int {
69+
return int((u32(next_fd) << 8) | (events & 0xff))
70+
}
71+
72+
// get the lower 8 bits
73+
[inline]
74+
fn backend_get_old_events(backend int) int {
75+
return backend & 0xff
76+
}
77+
78+
// get the higher 8 bits
79+
[inline]
80+
fn backend_get_next_fd(backend int) int {
81+
return backend >> 8
82+
}
83+
84+
// apply pending processes all changes for the file descriptors and updates `loop.changelist`
85+
// if `aplly_all` is `true` the changes are immediately applied
86+
fn (mut pv Picoev) apply_pending_changes(apply_all bool) int {
87+
mut total, mut nevents := 0, 0
88+
89+
for pv.loop.changed_fds != -1 {
90+
mut target := pv.file_descriptors[pv.loop.changed_fds]
91+
old_events := backend_get_old_events(target.backend)
92+
if target.events != old_events {
93+
// events have been changed
94+
if old_events != 0 {
95+
pv.ev_set(total, C.EV_DISABLE, old_events)
96+
total++
97+
}
98+
if target.events != 0 {
99+
pv.ev_set(total, C.EV_ADD | C.EV_ENABLE, int(target.events))
100+
total++
101+
}
102+
// Apply the changes if the total changes exceed the changelist size
103+
if total + 1 >= pv.loop.changelist.len {
104+
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL,
105+
0, C.NULL)
106+
assert nevents == 0
107+
total = 0
108+
}
109+
}
110+
111+
pv.loop.changed_fds = backend_get_next_fd(target.backend)
112+
target.backend = -1
113+
}
114+
115+
if apply_all && total != 0 {
116+
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL, 0, C.NULL)
117+
assert nevents == 0
118+
total = 0
119+
}
120+
121+
return total
122+
}
123+
124+
[direct_array_access]
125+
fn (mut pv Picoev) update_events(fd int, events int) int {
126+
// check if fd is in range
127+
assert fd < max_fds
128+
129+
mut target := pv.file_descriptors[fd]
130+
131+
// initialize if adding the fd
132+
if events & picoev_add != 0 {
133+
target.backend = -1
134+
}
135+
136+
// return if nothing to do
137+
if (events == picoev_del && target.backend == -1)
138+
|| (events != picoev_del && events & picoev_readwrite == target.events) {
139+
return 0
140+
}
141+
142+
// add to changed list if not yet being done
143+
if target.backend == -1 {
144+
target.backend = backend_build(pv.loop.changed_fds, target.events)
145+
pv.loop.changed_fds = fd
146+
}
147+
148+
// update events
149+
target.events = u32(events & picoev_readwrite)
150+
// apply immediately if is a DELETE
151+
if events & picoev_del != 0 {
152+
pv.apply_pending_changes(true)
153+
}
154+
155+
return 0
156+
}
157+
158+
[direct_array_access]
159+
fn (mut pv Picoev) poll_once(max_wait int) int {
160+
ts := C.timespec{
161+
tv_sec: max_wait
162+
tv_nsec: 0
163+
}
164+
165+
mut total, mut nevents := 0, 0
166+
// apply changes later when the callback is called.
167+
total = pv.apply_pending_changes(false)
168+
169+
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, &pv.loop.events, pv.loop.events.len,
170+
&ts)
171+
if nevents == -1 {
172+
// the errors we can only rescue
173+
assert C.errno == C.EACCES || C.errno == C.EFAULT || C.errno == C.EINTR
174+
return -1
175+
}
176+
177+
for i := 0; i < nevents; i++ {
178+
event := pv.loop.events[i]
179+
target := pv.file_descriptors[event.ident]
180+
181+
// changelist errors are fatal
182+
assert event.flags & C.EV_ERROR == 0
183+
184+
if pv.loop.id == target.loop_id && event.filter & (C.EVFILT_READ | C.EVFILT_WRITE) != 0 {
185+
read_events := match int(event.filter) {
186+
C.EVFILT_READ {
187+
picoev_read
188+
}
189+
C.EVFILT_WRITE {
190+
picoev_write
191+
}
192+
else {
193+
0
194+
}
195+
}
196+
197+
// do callback!
198+
unsafe { target.cb(target.fd, read_events, &pv) }
199+
}
200+
}
201+
202+
return 0
203+
}

0 commit comments

Comments
 (0)