Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+227
View File
@@ -0,0 +1,227 @@
module notify
import time
import os
#insert "@VEXEROOT/vlib/os/notify/kqueue.h"
pub struct C.kevent {
mut:
ident u32
filter i16
flags u16
fflags u32
data int
udata voidptr
}
fn C.kqueue() i32
fn C.__kevent__(i32, voidptr, i32, voidptr, i32, voidptr) i32
fn C.EV_SET(voidptr, u32, i16, u16, u32, i32, voidptr)
// KqueueNotifier provides methods that implement FdNotifier using the
// kqueue I/O event notification facility (macos, freeBSD, xxxBSD...unix only)
struct KqueueNotifier {
kqueue_fd int
}
// KqueueEvent describes an event that occurred for a file descriptor in
// the watch list
struct KqueueEvent {
pub:
fd int
kind FdEventType
}
// new creates a new KqueueNotifier.
// The FdNotifier interface is returned to allow OS specific
// implementations without exposing the concrete type
pub fn new() !FdNotifier {
fd := C.kqueue()
if fd == -1 {
return error(os.posix_get_error_msg(C.errno))
}
// Needed to circumvent V limitations
x := &KqueueNotifier{
kqueue_fd: fd
}
return x
}
// filter types
const kqueue_read = i16(C.EVFILT_READ)
const kqueue_write = i16(C.EVFILT_WRITE)
const kqueue_aio = i16(C.EVFILT_AIO)
const kqueue_vnode = i16(C.EVFILT_VNODE)
const kqueue_proc = i16(C.EVFILT_PROC)
const kqueue_signal = i16(C.EVFILT_SIGNAL)
const kqueue_timer = i16(C.EVFILT_TIMER)
const kqueue_machport = i16(C.EVFILT_MACHPORT)
const kqueue_fs = i16(C.EVFILT_FS)
const kqueue_user = i16(C.EVFILT_USER)
const kqueue_vm = i16(C.EVFILT_VM)
const kqueue_exception = i16(C.EVFILT_EXCEPT)
const kqueue_syscount = i16(C.EVFILT_SYSCOUNT)
// actions
const kqueue_add = u16(C.EV_ADD)
const kqueue_delete = u16(C.EV_DELETE)
const kqueue_enable = u16(C.EV_ENABLE)
const kqueue_disable = u16(C.EV_DISABLE)
// flags
const kqueue_oneshot = u16(C.EV_ONESHOT)
const kqueue_edge_trigger = u16(C.EV_CLEAR) // kqueue_clear
const kqueue_receipt = u16(C.EV_RECEIPT)
const kqueue_dispatch = u16(C.EV_DISPATCH)
const kqueue_udata_specific = u16(C.EV_UDATA_SPECIFIC)
const kqueue_dispatch2 = u16(C.EV_DISPATCH | C.EV_UDATA_SPECIFIC)
const kqueue_vanished = u16(C.EV_VANISHED)
const kqueue_sysflags = u16(C.EV_SYSFLAGS)
const kqueue_flag0 = u16(C.EV_FLAG0)
const kqueue_flag1 = u16(C.EV_FLAG1)
// returned values
const kqueue_eof = u16(C.EV_EOF)
const kqueue_error = u16(C.EV_ERROR)
// ctl is a helper method for add, modify, and remove
fn (mut kn KqueueNotifier) ctl(fd int, filter i16, flags u16) ! {
event := [1]C.kevent{}
C.EV_SET(&event[0], fd, filter, flags, 0, 0, unsafe { nil })
if C.__kevent__(kn.kqueue_fd, &event[0], 1, unsafe { nil }, 0, unsafe { nil }) == -1 {
return error(os.posix_get_error_msg(C.errno))
}
}
// add adds a file descriptor to the watch list
fn (mut kn KqueueNotifier) add(fd int, events FdEventType, conf ...FdConfigFlags) ! {
filter := filter_to_mask(events)
flags := flags_to_mask(...conf)
kn.ctl(fd, filter, flags)!
}
// modify sets an existing entry in the watch list to the provided events and configuration
fn (mut kn KqueueNotifier) modify(fd int, events FdEventType, conf ...FdConfigFlags) ! {
kn.add(fd, events, ...conf)!
}
// remove removes a file descriptor from the watch list
fn (mut kn KqueueNotifier) remove(fd int) ! {
filter := kqueue_read | kqueue_write | kqueue_exception
flags := kqueue_delete
kn.ctl(fd, filter, flags)!
}
// wait waits to be notified of events on the watch list,
// returns at most 512 events
fn (mut kn KqueueNotifier) wait(timeout time.Duration) []FdEvent {
// arbitrary 512 limit; events will round robin on successive
// waits if the number exceeds this
// NOTE: we use a fixed size array here for stack allocation; this has
// the added bonus of making KqueueNotifier thread safe
events := [512]C.kevent{}
// populate events with the new events
to := &C.timespec{0, timeout.nanoseconds()}
count := C.__kevent__(kn.kqueue_fd, unsafe { nil }, 0, &events[0], events.len, to)
if count > 0 {
mut arr := []FdEvent{cap: count}
for i := 0; i < count; i++ {
fd := int(events[i].ident)
kind := event_mask_to_flag(events[i].filter, events[i].flags)
if kind.is_empty() {
// NOTE: tcc only reports the first event for some
// reason, leaving subsequent structs in the array as 0
// (or possibly garbage)
panic('encountered an empty event kind; this is most likely due to using tcc')
}
arr << &KqueueEvent{
fd: fd
kind: kind
}
}
return arr
}
return []
}
// close closes the KqueueNotifier,
// any successive calls to add, modify, remove, and wait should fail
fn (mut kn KqueueNotifier) close() ! {
if C.close(kn.kqueue_fd) == -1 {
return error(os.posix_get_error_msg(C.errno))
}
}
// event_mask_to_flag is a helper function that converts a bitmask
// returned by kevent() wait to FdEventType
fn event_mask_to_flag(filter i16, flags u16) FdEventType {
mut res := unsafe { FdEventType(0) }
if filter & kqueue_read != 0 {
res.set(.read)
}
if filter & kqueue_write != 0 {
res.set(.write)
}
if filter & kqueue_exception != 0 {
res.set(.exception)
}
if flags & kqueue_eof != 0 {
res.set(.hangup)
}
if flags & kqueue_error != 0 {
res.set(.error)
}
return res
}
// filter_to_mask is a helper function that converts FdEventType
// to a bitmask used by the C functions
fn filter_to_mask(events FdEventType) i16 {
mut mask := i16(0)
if events.has(.read) {
mask |= kqueue_read
}
if events.has(.write) {
mask |= kqueue_write
}
if events.has(.exception) {
mask |= kqueue_exception
}
if events.has(.peer_hangup) {
panic("Kqueue does not support 'peer_hangup' event type.")
}
if events.has(.error) {
panic("Kqueue does not support 'error' event type.")
}
if events.has(.hangup) {
panic("Kqueue does not support 'hangup' event type.")
}
return mask
}
// flags_to_mask is a helper function that converts FdConfigFlags
// to a bitmask used by the C functions
fn flags_to_mask(confs ...FdConfigFlags) u16 {
mut mask := kqueue_add | kqueue_enable
for conf in confs {
if conf.has(.edge_trigger) {
mask |= kqueue_edge_trigger
}
if conf.has(.one_shot) {
mask |= kqueue_oneshot
}
if conf.has(.wake_up) {
panic("Kqueue does not support 'wake_up' flag.")
}
if conf.has(.exclusive) {
panic("Kqueue does not support 'exclusive' flag.")
}
}
return mask
}
@@ -0,0 +1,6 @@
module notify
// new returns an error when the current platform has no notifier backend.
pub fn new() !FdNotifier {
return error('os.notify is not supported on this platform')
}
+202
View File
@@ -0,0 +1,202 @@
module notify
import time
import os
#include <sys/epoll.h>
pub struct C.epoll_event {
events u32
data C.epoll_data_t
}
@[typedef]
union C.epoll_data_t {
ptr voidptr
fd int
u32 u32
u64 u64
}
fn C.epoll_create1(i32) i32
fn C.epoll_ctl(i32, i32, i32, &C.epoll_event) i32
fn C.epoll_wait(i32, &C.epoll_event, i32, i32) i32
// EpollNotifier provides methods that implement FdNotifier using the
// epoll I/O event notification facility (linux only)
struct EpollNotifier {
epoll_fd int
}
// EpollEvent describes an event that occurred for a file descriptor in
// the watch list
struct EpollEvent {
pub:
fd int
kind FdEventType
}
// new creates a new EpollNotifier.
// The FdNotifier interface is returned to allow OS specific
// implementations without exposing the concrete type
pub fn new() !FdNotifier {
fd := C.epoll_create1(0) // 0 indicates default behavior
if fd == -1 {
return error(os.posix_get_error_msg(C.errno))
}
// Needed to circumvent V limitations
x := &EpollNotifier{
epoll_fd: fd
}
return x
}
const epoll_read = u32(C.EPOLLIN)
const epoll_write = u32(C.EPOLLOUT)
const epoll_peer_hangup = u32(C.EPOLLRDHUP)
const epoll_exception = u32(C.EPOLLPRI)
const epoll_error = u32(C.EPOLLERR)
const epoll_hangup = u32(C.EPOLLHUP)
const epoll_edge_trigger = u32(C.EPOLLET)
const epoll_one_shot = u32(C.EPOLLONESHOT)
const epoll_wake_up = u32(C.EPOLLWAKEUP)
const epoll_exclusive = u32(C.EPOLLEXCLUSIVE)
// ctl is a helper method for add, modify, and remove
fn (mut en EpollNotifier) ctl(fd int, op int, mask u32) ! {
event := C.epoll_event{
events: mask
data: C.epoll_data_t{
fd: fd
}
}
if C.epoll_ctl(en.epoll_fd, op, fd, &event) == -1 {
return error(os.posix_get_error_msg(C.errno))
}
}
// add adds a file descriptor to the watch list
fn (mut en EpollNotifier) add(fd int, events FdEventType, conf ...FdConfigFlags) ! {
mask := flags_to_mask(events, ...conf)
en.ctl(fd, C.EPOLL_CTL_ADD, mask)!
}
// modify sets an existing entry in the watch list to the provided events and configuration
fn (mut en EpollNotifier) modify(fd int, events FdEventType, conf ...FdConfigFlags) ! {
mask := flags_to_mask(events, ...conf)
en.ctl(fd, C.EPOLL_CTL_MOD, mask)!
}
// remove removes a file descriptor from the watch list
fn (mut en EpollNotifier) remove(fd int) ! {
en.ctl(fd, C.EPOLL_CTL_DEL, 0)!
}
// wait waits to be notified of events on the watch list,
// returns at most 512 events
fn (mut en EpollNotifier) wait(timeout time.Duration) []FdEvent {
// arbitrary 512 limit; events will round robin on successive
// waits if the number exceeds this
// NOTE: we use a fixed size array here for stack allocation; this has
// the added bonus of making EpollNotifier thread safe
events := [512]C.epoll_event{}
// populate events with the new events
to := timeout.sys_milliseconds()
count := C.epoll_wait(en.epoll_fd, &events[0], events.len, to)
if count > 0 {
mut arr := []FdEvent{cap: count}
for i := 0; i < count; i++ {
fd := unsafe { events[i].data.fd }
kind := event_mask_to_flag(events[i].events)
if kind.is_empty() {
// NOTE: tcc only reports the first event for some
// reason, leaving subsequent structs in the array as 0
// (or possibly garbage)
panic('encountered an empty event kind; this is most likely due to using tcc')
}
arr << &EpollEvent{
fd: fd
kind: kind
}
}
return arr
}
return []
}
// close closes the EpollNotifier,
// any successive calls to add, modify, remove, and wait should fail
fn (mut en EpollNotifier) close() ! {
if C.close(en.epoll_fd) == -1 {
return error(os.posix_get_error_msg(C.errno))
}
}
// event_mask_to_flag is a helper function that converts a bitmask
// returned by epoll_wait to FdEventType
fn event_mask_to_flag(mask u32) FdEventType {
mut flags := unsafe { FdEventType(0) }
if mask & epoll_read != 0 {
flags.set(.read)
}
if mask & epoll_write != 0 {
flags.set(.write)
}
if mask & epoll_peer_hangup != 0 {
flags.set(.peer_hangup)
}
if mask & epoll_exception != 0 {
flags.set(.exception)
}
if mask & epoll_error != 0 {
flags.set(.error)
}
if mask & epoll_hangup != 0 {
flags.set(.hangup)
}
return flags
}
// flags_to_mask is a helper function that converts FdEventType and
// FdConfigFlags to a bitmask used by the C functions
fn flags_to_mask(events FdEventType, confs ...FdConfigFlags) u32 {
mut mask := u32(0)
if events.has(.read) {
mask |= epoll_read
}
if events.has(.write) {
mask |= epoll_write
}
if events.has(.peer_hangup) {
mask |= epoll_peer_hangup
}
if events.has(.exception) {
mask |= epoll_exception
}
if events.has(.error) {
mask |= epoll_error
}
if events.has(.hangup) {
mask |= epoll_hangup
}
for conf in confs {
if conf.has(.edge_trigger) {
mask |= epoll_edge_trigger
}
if conf.has(.one_shot) {
mask |= epoll_one_shot
}
if conf.has(.wake_up) {
mask |= epoll_wake_up
}
if conf.has(.exclusive) {
mask |= epoll_exclusive
}
}
return mask
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef __KQUEUE_H
#define __KQUEUE_H
#include <sys/event.h>
// Due to the renaming of 'struct kevent' and function 'kevent',
// they are wrapped here to avoid conflicts.
int __kevent__(int handle, const struct kevent* changelist, int nchanges, struct kevent* eventlist, int nevents, const struct timespec* timeout) {
return kevent(handle, changelist, nchanges, eventlist, nevents, timeout);
}
#endif
+36
View File
@@ -0,0 +1,36 @@
module notify
import time
// Backends should provide a `new()?FdNotifier` function
pub interface FdNotifier {
mut:
add(fd int, events FdEventType, conf ...FdConfigFlags) !
modify(fd int, events FdEventType, conf ...FdConfigFlags) !
remove(fd int) !
wait(timeout time.Duration) []FdEvent
close() !
}
pub interface FdEvent {
fd int
kind FdEventType
}
@[flag]
pub enum FdEventType {
read
write
peer_hangup
exception
error
hangup
}
@[flag]
pub enum FdConfigFlags {
edge_trigger
one_shot
wake_up
exclusive
}
+169
View File
@@ -0,0 +1,169 @@
// vtest flaky: true
// vtest retry: 3
import os
import os.notify
fn test_level_trigger() {
// currently only linux and macos are supported
$if linux || macos {
mut notifier := notify.new()!
mut pipe := os.pipe()!
defer {
pipe.close()
notifier.close() or {}
}
notifier.add(pipe.read_fd, .read)!
pipe.write_string('foobar')!
mut n := &notifier
check_read_event(mut n, pipe.read_fd, 'foo')
check_read_event(mut n, pipe.read_fd, 'bar')
assert notifier.wait(0).len == 0
}
}
fn test_edge_trigger() {
// currently only linux and macos are supported
$if linux || macos {
mut notifier := notify.new()!
mut pipe := os.pipe()!
defer {
pipe.close()
notifier.close() or {}
}
notifier.add(pipe.read_fd, .read, .edge_trigger)!
mut n := &notifier
pipe.write_string('foobar')!
check_read_event(mut n, pipe.read_fd, 'foo')
$if linux {
assert notifier.wait(0).len == 0
}
$if macos {
/*
In the kqueue of macos, EV_CLEAR flag represents a clear event,
which is mainly used for pipeline and socket class events. When this flag is set,
kqueue will trigger the corresponding event when the data is readable or writable,
but it is not guaranteed that the event will only be triggered once.
Compared to EPOLLET, EV_CLEAR's behavior varies. In epoll, the edge triggered mode only triggers
an event once when the state changes from unreadable/non writable to readable/writable,
that is, when the data changes from unreadable to readable,
or when the data changes from unreadable to writable. In the kqueue of macos,
EV_CLEAR does not possess this precise edge triggering behavior.
Therefore, in the kqueue of macos, even if the data is not completely read,
it is possible to continue triggering read events. This means that if you don't process all the data,
the next kqueue event notification may still be triggered
*/
// notifier.wait(0).len == 1 or 0
}
pipe.write_string('baz')!
check_read_event(mut n, pipe.read_fd, 'barbaz')
// we do not get an event because there is still data
// to be read
// assert notifier.wait(0).len == 0
// TODO: investigage why the above assert suddenly started failing on the latest Ubuntu kernel update:
// 5.11.0-37-generic #41~20.04.2-Ubuntu SMP Fri Sep 24 09:06:38 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
}
}
fn test_one_shot() {
$if linux || macos {
mut notifier := notify.new()!
mut pipe := os.pipe()!
defer {
pipe.close()
notifier.close() or {}
}
notifier.add(pipe.read_fd, .read, .one_shot)!
mut n := &notifier
pipe.write_string('foobar')!
check_read_event(mut n, pipe.read_fd, 'foo')
pipe.write_string('baz')!
assert notifier.wait(0).len == 0
// rearm
notifier.modify(pipe.read_fd, .read)!
check_read_event(mut n, pipe.read_fd, 'barbaz')
}
}
// Kqueue does not support 'hangup' event type.
fn test_hangup() {
$if linux {
mut notifier := notify.new()!
mut pipe := os.pipe()!
defer {
os.fd_close(pipe.read_fd)
notifier.close() or {}
}
notifier.add(pipe.read_fd, .hangup)!
assert notifier.wait(0).len == 0
// closing on the writer end of the pipe will
// cause a hangup on the reader end
os.fd_close(pipe.write_fd)
events := notifier.wait(0)
assert events.len == 1
assert events[0].fd == pipe.read_fd
assert events[0].kind.has(.hangup)
}
}
fn test_write() {
$if linux || macos {
mut notifier := notify.new()!
mut pipe := os.pipe()!
defer {
pipe.close()
notifier.close() or {}
}
notifier.add(pipe.read_fd, .write)!
assert notifier.wait(0).len == 0
notifier.add(pipe.write_fd, .write)!
events := notifier.wait(0)
assert events.len == 1
assert events[0].fd == pipe.write_fd
assert events[0].kind.has(.write)
}
}
fn test_remove() {
$if linux || macos {
mut notifier := notify.new()!
mut pipe := os.pipe()!
defer {
pipe.close()
notifier.close() or {}
}
// level triggered - will keep getting events while
// there is data to read
notifier.add(pipe.read_fd, .read)!
pipe.write_string('foobar')!
assert notifier.wait(0).len == 1
assert notifier.wait(0).len == 1
notifier.remove(pipe.read_fd)!
assert notifier.wait(0).len == 0
}
}
fn check_read_event(mut notifier notify.FdNotifier, reader_fd int, expected string) {
events := notifier.wait(0)
assert events.len == 1
assert events[0].fd == reader_fd
assert events[0].kind.has(.read)
s, _ := os.fd_read(events[0].fd, expected.len)
assert s == expected
}