From 1a7938871aa541f2773c279b58cbf02265d75442 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Wed, 30 Nov 2022 13:48:09 +0100 Subject: [PATCH] Flesh out core loop implementation Including some bug fixes identified through testing --- ev/+linux/file.ha | 15 ++++++++++++++- ev/+linux/io.ha | 22 ++++++++++++++-------- ev/+linux/loop.ha | 49 +++++++++++++++++++++++++++++++++---------------- diff --git a/ev/+linux/file.ha b/ev/+linux/file.ha index 161bd71e43c77adb93fe16562394dc45ae6f00fa..8205365f51e98f6100b21e746d97814573140ea5 100644 --- a/ev/+linux/file.ha +++ b/ev/+linux/file.ha @@ -51,12 +51,25 @@ }; // Unregisters a file object with an event loop and frees resources associated // with it. Does not close the underlying file descriptor. -export fn unregister(loop: *loop, file: *file) void = { +export fn unregister(file: *file) void = { + const loop = file.ev; // The only way that this could fail is in the event of a use-after-free // or if the user fucks around and constructs a custom [[file]] which // was never registered, so assert on error. rt::epoll_ctl(loop.fd, rt::EPOLL_CTL_DEL, file.fd, null)!; free(file); +}; + +// Returns the file descriptor for a given file. Note that ev assumes that it +// will be responsible for all I/O on the file and any user modifications may +// cause the event loop to enter an invalid state. +export fn file_getfd(file: *file) io::file = { + return file.fd; +}; + +// Returns the event loop for a given file. +export fn file_getloop(file: *file) *loop = { + return file.ev; }; // Modifies the epoll events for a given file. For internal use. diff --git a/ev/+linux/io.ha b/ev/+linux/io.ha index 0e20b4d468d2049f221c6076856914f779be0153..9572c300043d7c99c8608418c12975459d1cc18f 100644 --- a/ev/+linux/io.ha +++ b/ev/+linux/io.ha @@ -2,7 +2,7 @@ use io; use rt; // A callback for a [[read]] or [[readv]] operation. -export type readcb = *fn(file: *file, result: (size | io::EOF | io::error)) void; +export type readcb = fn(file: *file, result: (size | io::EOF | io::error)) void; // Schedules a read operation on a file object. export fn read( @@ -26,21 +26,27 @@ assert(file.op == op::NONE); file.op = op::READV; file.cb = cb; file.vec = vec; - filemod(file, rt::EPOLLOUT); + filemod(file, rt::EPOLLIN | rt::EPOLLHUP); return req { ... }; }; fn readv_finish(file: *file, ev: *rt::epoll_event) void = { - assert(file.op == op::READV && ev.events & rt::EPOLLIN != 0); + assert(file.op == op::READV); + assert(ev.events & (rt::EPOLLIN | rt::EPOLLHUP) != 0); assert(file.cb != null); - const r = io::readv(file.fd, file.vec...); const cb = file.cb: *readcb; - cb(file, r); file.op = op::NONE; + + if (ev.events & rt::EPOLLHUP != 0) { + cb(file, io::EOF); + } else { + const r = io::readv(file.fd, file.vec...); + cb(file, r); + }; }; // A callback for a [[write]] or [[writev]] operation. -export type writecb = *fn(file: *file, result: (size | io::error)) void; +export type writecb = fn(file: *file, result: (size | io::error)) void; // Schedules a write operation on a file object. export fn write( @@ -66,7 +72,7 @@ assert(file.op == op::NONE); file.op = op::WRITEV; file.cb = cb; file.vec = vec; - filemod(file, rt::EPOLLOUT); + filemod(file, rt::EPOLLOUT | rt::EPOLLHUP); return req { ... }; }; @@ -75,6 +81,6 @@ assert(file.op == op::WRITEV && ev.events & rt::EPOLLOUT != 0); assert(file.cb != null); const r = io::writev(file.fd, file.vec...); const cb = file.cb: *writecb; - cb(file, r); file.op = op::NONE; + cb(file, r); }; diff --git a/ev/+linux/loop.ha b/ev/+linux/loop.ha index 17f658f30a787306a38c8c59004bbffc7c2562f9..1fa6c9a054acdb87b22a8eedd989ac0fef248b85 100644 --- a/ev/+linux/loop.ha +++ b/ev/+linux/loop.ha @@ -7,10 +7,11 @@ export type loop = struct { fd: io::file, events: []rt::epoll_event, + stop: bool, }; -// Creates a new event loop. The user must pass the return value to -// [[loop_finish]] to free associated resources when done using the loop. +// Creates a new event loop. The user must pass the return value to [[finish]] +// to free associated resources when done using the loop. export fn newloop() (loop | errors::error) = { const fd = match (rt::epoll_create1(rt::EPOLL_CLOEXEC)) { case let fd: int => @@ -21,13 +22,17 @@ }; return loop { fd = fd, - events = [], + // XXX: Should the number of events be customizable? + events = alloc([rt::epoll_event { ... }...], 256), + stop = false, }; }; // Frees resources associated with an event loop. Must only be called once per -// event loop object. -export fn loop_finish(loop: *loop) void = { +// event loop object. Calling finish invalidates all I/O objects associated with +// the event loop. +export fn finish(loop: *loop) void = { + free(loop.events); io::close(loop.fd)!; }; @@ -39,15 +44,16 @@ export fn loop_file(loop: *loop) io::file = { return loop.fd; }; -// Dispatches the event loop. A timeout of 0 specifies an indefinite timeout, -// and will cause dispatch to block until the next event is available. A timeout -// of -1 will return immediately if no events are available. +// Dispatches the event loop, waiting for new events and calling their callbacks +// as appropriate. // -// Portable use of the timeout argument supports only millisecond granularity of -// up to 24 days (INT_MAX milliseconds). Negative values other than -1 will -// cause the program to abort. +// A timeout of -1 will block indefinitely until the next event occurs. A +// timeout of 0 will cause dispatch to return immediately if no events are +// available to process. Portable use of the timeout argument supports only +// millisecond granularity of up to 24 days (INT_MAX milliseconds). Negative +// values other than -1 will cause the program to abort. // -// Returns false if there are no events to process. +// Returns false if the loop has been stopped via [[stop]], or true otherwise. export fn dispatch( loop: *loop, timeout: time::duration, @@ -59,17 +65,21 @@ abort("ev::dispatch: invalid timeout"); } else { yield (timeout / time::MILLISECOND): int; }; - if (len(loop.events) == 0) { + if (loop.stop) { return false; }; + if (len(loop.events) == 0) { + return true; + }; + // TODO: Deal with signals const maxev = len(loop.events); assert(maxev <= types::INT_MAX: size, "ev::dispatch: too many events"); - const events = rt::epoll_pwait( + const nevent = rt::epoll_pwait( loop.fd, &loop.events[0], maxev: int, millis, null)!; - for (let i = 0z; i < maxev; i += 1) { + for (let i = 0; i < nevent; i += 1) { const ev = &loop.events[i]; const file = ev.data.ptr: *file; if (ev.events == 0) { @@ -85,5 +95,12 @@ writev_finish(file, ev); }; }; - return events != 0; + return !loop.stop; +}; + +// Signals the loop to stop processing events. If called during a callback, it +// will cause that invocation of [[dispatch]] to return false. Otherwise, false +// will be returned only upon the next call to [[dispatch]]. +export fn stop(loop: *loop) void = { + loop.stop = true; }; -- 2.48.1