picoev: renaming, doc (#20567)

This commit is contained in:
Hitalo Souza 2024-02-09 22:54:27 -04:00 committed by GitHub
parent 2d0ed2c1d6
commit ee3cd36760
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 91 additions and 65 deletions

View file

@ -15,13 +15,14 @@ mut:
type LoopType = SelectLoop type LoopType = SelectLoop
// create_select_loop creates a `SelectLoop` struct with `id` // create_select_loop creates a new `SelectLoop` struct with the given `id`
pub fn create_select_loop(id int) !&SelectLoop { pub fn create_select_loop(id int) !&SelectLoop {
return &SelectLoop{ return &SelectLoop{
id: id id: id
} }
} }
// updates the events associated with a file descriptor in the event loop
@[direct_array_access] @[direct_array_access]
fn (mut pv Picoev) update_events(fd int, events int) int { fn (mut pv Picoev) update_events(fd int, events int) int {
// check if fd is in range // check if fd is in range
@ -31,8 +32,10 @@ fn (mut pv Picoev) update_events(fd int, events int) int {
return 0 return 0
} }
// performs a single iteration of the select-based event loop
@[direct_array_access] @[direct_array_access]
fn (mut pv Picoev) poll_once(max_wait int) int { fn (mut pv Picoev) poll_once(max_wait_in_sec int) int {
// Initializes sets for read, write, and error events
readfds, writefds, errorfds := C.fd_set{}, C.fd_set{}, C.fd_set{} readfds, writefds, errorfds := C.fd_set{}, C.fd_set{}, C.fd_set{}
// setup // setup
@ -42,7 +45,7 @@ fn (mut pv Picoev) poll_once(max_wait int) int {
mut maxfd := 0 mut maxfd := 0
// find the maximum socket for `select` and add sockets to the fd_sets // finds the maximum file descriptor and adds sockets to the sets `fd_sets`.
for target in pv.file_descriptors { for target in pv.file_descriptors {
if target.loop_id == pv.loop.id { if target.loop_id == pv.loop.id {
if target.events & picoev_read != 0 { if target.events & picoev_read != 0 {
@ -62,7 +65,7 @@ fn (mut pv Picoev) poll_once(max_wait int) int {
// select and handle sockets if any // select and handle sockets if any
tv := C.timeval{ tv := C.timeval{
tv_sec: u64(max_wait) tv_sec: u64(max_wait_in_sec)
tv_usec: 0 tv_usec: 0
} }
r := C.@select(maxfd + 1, &readfds, &writefds, &errorfds, &tv) r := C.@select(maxfd + 1, &readfds, &writefds, &errorfds, &tv)
@ -70,6 +73,7 @@ fn (mut pv Picoev) poll_once(max_wait int) int {
// timeout // timeout
return -1 return -1
} else if r > 0 { } else if r > 0 {
// Iterates through file descriptors and calls their callbacks for triggered events
for target in pv.file_descriptors { for target in pv.file_descriptors {
if target.loop_id == pv.loop.id { if target.loop_id == pv.loop.id {
// vfmt off // vfmt off

View file

@ -156,9 +156,9 @@ fn (mut pv Picoev) update_events(fd int, events int) int {
} }
@[direct_array_access] @[direct_array_access]
fn (mut pv Picoev) poll_once(max_wait int) int { fn (mut pv Picoev) poll_once(max_wait_in_sec int) int {
ts := C.timespec{ ts := C.timespec{
tv_sec: max_wait tv_sec: max_wait_in_sec
tv_nsec: 0 tv_nsec: 0
} }

View file

@ -101,8 +101,8 @@ fn (mut pv Picoev) update_events(fd int, events int) int {
} }
@[direct_array_access] @[direct_array_access]
fn (mut pv Picoev) poll_once(max_wait int) int { fn (mut pv Picoev) poll_once(max_wait_in_sec int) int {
nevents := C.epoll_wait(pv.loop.epoll_fd, &pv.loop.events, max_fds, max_wait * 1000) nevents := C.epoll_wait(pv.loop.epoll_fd, &pv.loop.events, max_fds, max_wait_in_sec * 1000)
if nevents == -1 { if nevents == -1 {
// timeout has occurred // timeout has occurred

View file

@ -156,9 +156,9 @@ fn (mut pv Picoev) update_events(fd int, events int) int {
} }
@[direct_array_access] @[direct_array_access]
fn (mut pv Picoev) poll_once(max_wait int) int { fn (mut pv Picoev) poll_once(max_wait_in_sec int) int {
ts := C.timespec{ ts := C.timespec{
tv_sec: max_wait tv_sec: max_wait_in_sec
tv_nsec: 0 tv_nsec: 0
} }

View file

@ -4,28 +4,35 @@ import net
import picohttpparser import picohttpparser
import time import time
// maximum number of file descriptors that can be managed
pub const max_fds = 1024 pub const max_fds = 1024
// maximum size of the event queue
pub const max_queue = 4096 pub const max_queue = 4096
// events // event for incoming data ready to be read on a socket
pub const picoev_read = 1 pub const picoev_read = 1
// event for socket ready for writing
pub const picoev_write = 2 pub const picoev_write = 2
// event indicating a timeout has occurred
pub const picoev_timeout = 4 pub const picoev_timeout = 4
// flag for adding a file descriptor to the event loop
pub const picoev_add = 0x40000000 pub const picoev_add = 0x40000000
// flag for removing a file descriptor from the event loop
pub const picoev_del = 0x20000000 pub const picoev_del = 0x20000000
// event read/write
pub const picoev_readwrite = 3 pub const picoev_readwrite = 3
// Target is a data representation of everything that needs to be associated with a single // Target is a data representation of everything that needs to be associated with a single
// file descriptor (connection) // file descriptor (connection)
pub struct Target { pub struct Target {
pub mut: pub mut:
fd int fd int // file descriptor
loop_id int = -1 loop_id int = -1
events u32 events u32
cb fn (int, int, voidptr) = unsafe { nil } cb fn (int, int, voidptr) = unsafe { nil }
@ -33,11 +40,12 @@ pub mut:
backend int backend int
} }
// Config configures the Picoev instance with server settings and callbacks
pub struct Config { pub struct Config {
pub: pub:
port int = 8080 port int = 8080
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) = unsafe { nil } cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) = unsafe { nil }
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_error_callback
raw_cb fn (mut Picoev, int, int) = unsafe { nil } raw_cb fn (mut Picoev, int, int) = unsafe { nil }
user_data voidptr = unsafe { nil } user_data voidptr = unsafe { nil }
timeout_secs int = 8 timeout_secs int = 8
@ -48,16 +56,21 @@ pub:
host string host string
} }
// Core structure for managing the event loop and connections.
// Contains event loop, file descriptor table, timeouts, buffers, and configuration.
@[heap] @[heap]
pub struct Picoev { pub struct Picoev {
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) = unsafe { nil } cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) = unsafe { nil }
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb error_callback fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_error_callback
raw_cb fn (mut Picoev, int, int) = unsafe { nil } raw_callback fn (mut Picoev, int, int) = unsafe { nil }
timeout_secs int timeout_secs int
max_headers int = 100 max_headers int = 100
max_read int = 4096 max_read int = 4096
max_write int = 8192 max_write int = 8192
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_error_callback @[deprecated: 'use `error_callback` instead']
raw_cb fn (mut Picoev, int, int) = unsafe { nil } @[deprecated: 'use `raw_callback` instead']
mut: mut:
loop &LoopType = unsafe { nil } loop &LoopType = unsafe { nil }
file_descriptors [max_fds]&Target file_descriptors [max_fds]&Target
@ -84,19 +97,19 @@ pub fn (mut pv Picoev) init() {
} }
} }
// add adds a file descriptor to the loop // add a file descriptor to the event loop
@[direct_array_access] @[direct_array_access]
pub fn (mut pv Picoev) add(fd int, events int, timeout int, cb voidptr) int { pub fn (mut pv Picoev) add(fd int, events int, timeout int, callback voidptr) int {
assert fd < picoev.max_fds assert fd < picoev.max_fds
mut target := pv.file_descriptors[fd] mut target := pv.file_descriptors[fd]
target.fd = fd target.fd = fd
target.cb = cb target.cb = callback
target.loop_id = pv.loop.id target.loop_id = pv.loop.id
target.events = 0 target.events = 0
if pv.update_events(fd, events | picoev.picoev_add) != 0 { if pv.update_events(fd, events | picoev.picoev_add) != 0 {
pv.del(fd) pv.delete(fd)
return -1 return -1
} }
@ -105,14 +118,21 @@ pub fn (mut pv Picoev) add(fd int, events int, timeout int, cb voidptr) int {
return 0 return 0
} }
// del removes a file descriptor from the loop // del remove a file descriptor from the event loop
@[deprecated: 'use delete() instead']
@[direct_array_access] @[direct_array_access]
pub fn (mut pv Picoev) del(fd int) int { pub fn (mut pv Picoev) del(fd int) int {
return pv.delete(fd)
}
// remove a file descriptor from the event loop
@[direct_array_access]
pub fn (mut pv Picoev) delete(fd int) int {
assert fd < picoev.max_fds assert fd < picoev.max_fds
mut target := pv.file_descriptors[fd] mut target := pv.file_descriptors[fd]
$if trace_fd ? { $if trace_fd ? {
eprintln('delete ${fd}') eprintln('remove ${fd}')
} }
if pv.update_events(fd, picoev.picoev_del) != 0 { if pv.update_events(fd, picoev.picoev_del) != 0 {
@ -125,14 +145,14 @@ pub fn (mut pv Picoev) del(fd int) int {
return 0 return 0
} }
fn (mut pv Picoev) loop_once(max_wait int) int { fn (mut pv Picoev) loop_once(max_wait_in_sec int) int {
pv.loop.now = get_time() pv.loop.now = get_time()
if pv.poll_once(max_wait) != 0 { if pv.poll_once(max_wait_in_sec) != 0 {
return -1 return -1
} }
if max_wait != 0 { if max_wait_in_sec != 0 {
pv.loop.now = get_time() pv.loop.now = get_time()
} }
@ -173,38 +193,39 @@ fn (mut pv Picoev) handle_timeout() {
} }
} }
// accept_callback accepts a new connection from `listen_fd` and adds it to the loop // accept_callback accepts a new connection from `listen_fd` and adds it to the event loop
fn accept_callback(listen_fd int, events int, cb_arg voidptr) { fn accept_callback(listen_fd int, events int, cb_arg voidptr) {
mut pv := unsafe { &Picoev(cb_arg) } mut pv := unsafe { &Picoev(cb_arg) }
newfd := accept(listen_fd) accepted_fd := accept(listen_fd)
if newfd >= picoev.max_fds { if accepted_fd >= picoev.max_fds {
// should never happen // should never happen
close_socket(newfd) close_socket(accepted_fd)
return return
} }
$if trace_fd ? { $if trace_fd ? {
eprintln('accept ${newfd}') eprintln('accept ${accepted_fd}')
} }
if newfd != -1 { if accepted_fd != -1 {
setup_sock(newfd) or { setup_sock(accepted_fd) or {
eprintln('setup_sock failed, fd: ${newfd}, listen_fd: ${listen_fd}, err: ${err.code()}') eprintln('setup_sock failed, fd: ${accepted_fd}, listen_fd: ${listen_fd}, err: ${err.code()}')
pv.err_cb(pv.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{}, pv.error_callback(pv.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{},
err) err)
return return
} }
pv.add(newfd, picoev.picoev_read, pv.timeout_secs, raw_callback) pv.add(accepted_fd, picoev.picoev_read, pv.timeout_secs, raw_callback)
} }
} }
// close_conn closes the socket `fd` and removes it from the loop // close_conn closes the socket `fd` and removes it from the loop
@[inline] @[inline]
pub fn (mut pv Picoev) close_conn(fd int) { pub fn (mut pv Picoev) close_conn(fd int) {
pv.del(fd) pv.delete(fd)
close_socket(fd) close_socket(fd)
} }
// raw_callback handles raw events (read, write, timeout) for a file descriptor
@[direct_array_access] @[direct_array_access]
fn raw_callback(fd int, events int, context voidptr) { fn raw_callback(fd int, events int, context voidptr) {
mut pv := unsafe { &Picoev(context) } mut pv := unsafe { &Picoev(context) }
@ -217,8 +238,8 @@ fn raw_callback(fd int, events int, context voidptr) {
eprintln('timeout ${fd}') eprintln('timeout ${fd}')
} }
if !isnil(pv.raw_cb) { if !isnil(pv.raw_callback) {
pv.raw_cb(mut pv, fd, events) pv.raw_callback(mut pv, fd, events)
return return
} }
@ -226,32 +247,32 @@ fn raw_callback(fd int, events int, context voidptr) {
return return
} else if events & picoev.picoev_read != 0 { } else if events & picoev.picoev_read != 0 {
pv.set_timeout(fd, pv.timeout_secs) pv.set_timeout(fd, pv.timeout_secs)
if !isnil(pv.raw_cb) { if !isnil(pv.raw_callback) {
pv.raw_cb(mut pv, fd, events) pv.raw_callback(mut pv, fd, events)
return return
} }
mut buf := pv.buf mut request_buffer := pv.buf
unsafe { unsafe {
buf += fd * pv.max_read // pointer magic request_buffer += fd * pv.max_read // pointer magic
} }
mut req := picohttpparser.Request{} mut req := picohttpparser.Request{}
// Response init // Response init
mut out := pv.out mut response_buffer := pv.out
unsafe { unsafe {
out += fd * pv.max_write // pointer magic response_buffer += fd * pv.max_write // pointer magic
} }
mut res := picohttpparser.Response{ mut res := picohttpparser.Response{
fd: fd fd: fd
buf_start: out buf_start: response_buffer
buf: out buf: response_buffer
date: pv.date.str date: pv.date.str
} }
for { for {
// Request parsing loop // Request parsing loop
r := req_read(fd, buf, pv.max_read, pv.idx[fd]) // Get data from socket r := req_read(fd, request_buffer, pv.max_read, pv.idx[fd]) // Get data from socket
if r == 0 { if r == 0 {
// connection closed by peer // connection closed by peer
pv.close_conn(fd) pv.close_conn(fd)
@ -267,10 +288,10 @@ fn raw_callback(fd int, events int, context voidptr) {
} }
pv.idx[fd] += r pv.idx[fd] += r
mut s := unsafe { tos(buf, pv.idx[fd]) } mut s := unsafe { tos(request_buffer, pv.idx[fd]) }
pret := req.parse_request(s) or { pret := req.parse_request(s) or {
// Parse error // Parse error
pv.err_cb(pv.user_data, req, mut &res, err) pv.error_callback(pv.user_data, req, mut &res, err)
return return
} }
if pret > 0 { // Success if pret > 0 { // Success
@ -279,8 +300,8 @@ fn raw_callback(fd int, events int, context voidptr) {
assert pret == -2 assert pret == -2
// request is incomplete, continue the loop // request is incomplete, continue the loop
if pv.idx[fd] == sizeof(buf) { if pv.idx[fd] == sizeof(request_buffer) {
pv.err_cb(pv.user_data, req, mut &res, error('RequestIsTooLongError')) pv.error_callback(pv.user_data, req, mut &res, error('RequestIsTooLongError'))
return return
} }
} }
@ -289,27 +310,27 @@ fn raw_callback(fd int, events int, context voidptr) {
pv.cb(pv.user_data, req, mut &res) pv.cb(pv.user_data, req, mut &res)
} else if events & picoev.picoev_write != 0 { } else if events & picoev.picoev_write != 0 {
pv.set_timeout(fd, pv.timeout_secs) pv.set_timeout(fd, pv.timeout_secs)
if !isnil(pv.raw_cb) { if !isnil(pv.raw_callback) {
pv.raw_cb(mut pv, fd, events) pv.raw_callback(mut pv, fd, events)
return return
} }
} }
} }
fn default_err_cb(data voidptr, req picohttpparser.Request, mut res picohttpparser.Response, error IError) { fn default_error_callback(data voidptr, req picohttpparser.Request, mut res picohttpparser.Response, error IError) {
eprintln('picoev: ${error}') eprintln('picoev: ${error}')
res.end() res.end()
} }
// new creates a `Picoev` struct and initializes the main loop // new creates a `Picoev` struct and initializes the main loop
pub fn new(config Config) !&Picoev { pub fn new(config Config) !&Picoev {
listen_fd := listen(config)! listening_socket_fd := listen(config)!
mut pv := &Picoev{ mut pv := &Picoev{
num_loops: 1 num_loops: 1
cb: config.cb cb: config.cb
err_cb: config.err_cb error_callback: config.err_cb
raw_cb: config.raw_cb raw_callback: config.raw_cb
user_data: config.user_data user_data: config.user_data
timeout_secs: config.timeout_secs timeout_secs: config.timeout_secs
max_headers: config.max_headers max_headers: config.max_headers
@ -317,14 +338,14 @@ pub fn new(config Config) !&Picoev {
max_write: config.max_write max_write: config.max_write
} }
if isnil(pv.raw_cb) { if isnil(pv.raw_callback) {
pv.buf = unsafe { malloc_noscan(picoev.max_fds * config.max_read + 1) } pv.buf = unsafe { malloc_noscan(picoev.max_fds * config.max_read + 1) }
pv.out = unsafe { malloc_noscan(picoev.max_fds * config.max_write + 1) } pv.out = unsafe { malloc_noscan(picoev.max_fds * config.max_write + 1) }
} }
// epoll for linux // epoll on linux
// kqueue for macos and bsd // kqueue on macos and bsd
// select for windows and others // select on windows and others
$if linux { $if linux {
pv.loop = create_epoll_loop(0) or { panic(err) } pv.loop = create_epoll_loop(0) or { panic(err) }
} $else $if freebsd || macos { } $else $if freebsd || macos {
@ -335,11 +356,12 @@ pub fn new(config Config) !&Picoev {
pv.init() pv.init()
pv.add(listen_fd, picoev.picoev_read, 0, accept_callback) pv.add(listening_socket_fd, picoev.picoev_read, 0, accept_callback)
return pv return pv
} }
// serve starts the Picoev server // serve starts the event loop for accepting new connections
// See also picoev.new().
pub fn (mut pv Picoev) serve() { pub fn (mut pv Picoev) serve() {
spawn update_date(mut pv) spawn update_date(mut pv)
@ -348,7 +370,7 @@ pub fn (mut pv Picoev) serve() {
} }
} }
// update_date updates `date` on `pv` every second. // update_date updates the date field of the Picoev instance every second for HTTP headers
fn update_date(mut pv Picoev) { fn update_date(mut pv Picoev) {
for { for {
// get GMT (UTC) time for the HTTP Date header // get GMT (UTC) time for the HTTP Date header

View file

@ -582,7 +582,7 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) {
// the connection should be kept open, but removed from the picoev loop. // the connection should be kept open, but removed from the picoev loop.
// This way vweb can continue handling other connections and the user can // This way vweb can continue handling other connections and the user can
// keep the connection open indefinitely // keep the connection open indefinitely
pv.del(fd) pv.delete(fd)
return return
} }