diff options
Diffstat (limited to 'v_windows/v/vlib/os/notify')
| -rw-r--r-- | v_windows/v/vlib/os/notify/backend_default.c.v | 6 | ||||
| -rw-r--r-- | v_windows/v/vlib/os/notify/backend_linux.c.v | 206 | ||||
| -rw-r--r-- | v_windows/v/vlib/os/notify/notify.v | 35 | ||||
| -rw-r--r-- | v_windows/v/vlib/os/notify/notify_test.v | 155 | 
4 files changed, 402 insertions, 0 deletions
diff --git a/v_windows/v/vlib/os/notify/backend_default.c.v b/v_windows/v/vlib/os/notify/backend_default.c.v new file mode 100644 index 0000000..1a35c50 --- /dev/null +++ b/v_windows/v/vlib/os/notify/backend_default.c.v @@ -0,0 +1,6 @@ +module notify + +// Implement the API +pub fn new() ?FdNotifier { +	panic('unsupported') +} diff --git a/v_windows/v/vlib/os/notify/backend_linux.c.v b/v_windows/v/vlib/os/notify/backend_linux.c.v new file mode 100644 index 0000000..1913913 --- /dev/null +++ b/v_windows/v/vlib/os/notify/backend_linux.c.v @@ -0,0 +1,206 @@ +module notify + +import time +import os + +#include <sys/epoll.h> + +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(int) int + +fn C.epoll_ctl(int, int, int, &C.epoll_event) int + +fn C.epoll_wait(int, &C.epoll_event, int, int) int + +// EpollNotifier provides methods that implement FdNotifier using the +// epoll I/O event notification facility (linux only) +[noinit] +struct EpollNotifier { +	epoll_fd int +} + +// EpollEvent describes an event that occurred for a file descriptor in +// the watch list +[noinit] +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) +	epoll_write        = u32(C.EPOLLOUT) +	epoll_peer_hangup  = u32(C.EPOLLRDHUP) +	epoll_exception    = u32(C.EPOLLPRI) +	epoll_error        = u32(C.EPOLLERR) +	epoll_hangup       = u32(C.EPOLLHUP) +	epoll_edge_trigger = u32(C.EPOLLET) +	epoll_one_shot     = u32(C.EPOLLONESHOT) +	epoll_wake_up      = u32(C.EPOLLWAKEUP) +	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 := FdEventType{} + +	if mask & notify.epoll_read != 0 { +		flags.set(.read) +	} +	if mask & notify.epoll_write != 0 { +		flags.set(.write) +	} +	if mask & notify.epoll_peer_hangup != 0 { +		flags.set(.peer_hangup) +	} +	if mask & notify.epoll_exception != 0 { +		flags.set(.exception) +	} +	if mask & notify.epoll_error != 0 { +		flags.set(.error) +	} +	if mask & notify.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 |= notify.epoll_read +	} +	if events.has(.write) { +		mask |= notify.epoll_write +	} +	if events.has(.peer_hangup) { +		mask |= notify.epoll_peer_hangup +	} +	if events.has(.exception) { +		mask |= notify.epoll_exception +	} +	if events.has(.error) { +		mask |= notify.epoll_error +	} +	if events.has(.hangup) { +		mask |= notify.epoll_hangup +	} +	for conf in confs { +		if conf.has(.edge_trigger) { +			mask |= notify.epoll_edge_trigger +		} +		if conf.has(.one_shot) { +			mask |= notify.epoll_one_shot +		} +		if conf.has(.wake_up) { +			mask |= notify.epoll_wake_up +		} +		if conf.has(.exclusive) { +			mask |= notify.epoll_exclusive +		} +	} +	return mask +} diff --git a/v_windows/v/vlib/os/notify/notify.v b/v_windows/v/vlib/os/notify/notify.v new file mode 100644 index 0000000..b49dad3 --- /dev/null +++ b/v_windows/v/vlib/os/notify/notify.v @@ -0,0 +1,35 @@ +module notify + +import time + +// Backends should provide a `new() ?FdNotifier` function +pub interface FdNotifier { +	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 +} diff --git a/v_windows/v/vlib/os/notify/notify_test.v b/v_windows/v/vlib/os/notify/notify_test.v new file mode 100644 index 0000000..253c94f --- /dev/null +++ b/v_windows/v/vlib/os/notify/notify_test.v @@ -0,0 +1,155 @@ +import os +import os.notify + +// make a pipe and return the (read, write) file descriptors +fn make_pipe() ?(int, int) { +	$if linux { +		pipefd := [2]int{} +		if C.pipe(&pipefd[0]) != 0 { +			return error('error $C.errno: ' + os.posix_get_error_msg(C.errno)) +		} +		return pipefd[0], pipefd[1] +	} +	return -1, -1 +} + +fn test_level_trigger() ? { +	// currently only linux is supported +	$if linux { +		mut notifier := notify.new() ? +		reader, writer := make_pipe() ? +		defer { +			os.fd_close(reader) +			os.fd_close(writer) +			notifier.close() or {} +		} +		notifier.add(reader, .read) ? + +		os.fd_write(writer, 'foobar') +		check_read_event(notifier, reader, 'foo') +		check_read_event(notifier, reader, 'bar') + +		assert notifier.wait(0).len == 0 +	} +} + +fn test_edge_trigger() ? { +	// currently only linux is supported +	$if linux { +		mut notifier := notify.new() ? +		reader, writer := make_pipe() ? +		defer { +			os.fd_close(reader) +			os.fd_close(writer) +			notifier.close() or {} +		} +		notifier.add(reader, .read, .edge_trigger) ? + +		os.fd_write(writer, 'foobar') +		check_read_event(notifier, reader, 'foo') + +		assert notifier.wait(0).len == 0 + +		os.fd_write(writer, 'baz') +		// we do not get an event because there is still data +		// to be read +		assert notifier.wait(0).len == 0 +	} +} + +fn test_one_shot() ? { +	$if linux { +		mut notifier := notify.new() ? +		reader, writer := make_pipe() ? +		defer { +			os.fd_close(reader) +			os.fd_close(writer) +			notifier.close() or {} +		} +		notifier.add(reader, .read, .one_shot) ? + +		os.fd_write(writer, 'foobar') +		check_read_event(notifier, reader, 'foo') +		os.fd_write(writer, 'baz') + +		assert notifier.wait(0).len == 0 + +		// rearm +		notifier.modify(reader, .read) ? +		check_read_event(notifier, reader, 'barbaz') +	} +} + +fn test_hangup() ? { +	$if linux { +		mut notifier := notify.new() ? +		reader, writer := make_pipe() ? +		defer { +			os.fd_close(reader) +			notifier.close() or {} +		} +		notifier.add(reader, .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(writer) +		events := notifier.wait(0) +		assert events.len == 1 +		assert events[0].fd == reader +		assert events[0].kind.has(.hangup) +	} +} + +fn test_write() ? { +	$if linux { +		mut notifier := notify.new() ? +		reader, writer := make_pipe() ? +		defer { +			os.fd_close(reader) +			os.fd_close(writer) +			notifier.close() or {} +		} + +		notifier.add(reader, .write) ? +		assert notifier.wait(0).len == 0 + +		notifier.add(writer, .write) ? +		events := notifier.wait(0) +		assert events.len == 1 +		assert events[0].fd == writer +		assert events[0].kind.has(.write) +	} +} + +fn test_remove() ? { +	$if linux { +		mut notifier := notify.new() ? +		reader, writer := make_pipe() ? +		defer { +			os.fd_close(reader) +			os.fd_close(writer) +			notifier.close() or {} +		} + +		// level triggered - will keep getting events while +		// there is data to read +		notifier.add(reader, .read) ? +		os.fd_write(writer, 'foobar') +		assert notifier.wait(0).len == 1 +		assert notifier.wait(0).len == 1 + +		notifier.remove(reader) ? +		assert notifier.wait(0).len == 0 +	} +} + +fn check_read_event(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 +}  | 
