Proposal for a new event- and I/O dispatching system
The handling of events in the CHICKEN runtime is currently widely distributed among various modules, using different scheduling mechanisms and implementations. The code is relatively ad-hoc, grown out of features added to the core system while it was developed. All of these events must be dispatched and scheduled, and a single, central mechanism could support this in a much simpler, more robust, easer to maintain and probably more efficient manner.
Currently we have the following types of events:
- blocking on Input from file descriptor (file, socket)
- blocking on output to file descriptor (file, socket)
- sleep timeout (thread, process)
- UNIX signal (child process termination, explicit signals, user-interrupt)
- thread termination/join
- thread timeslice end/schedule
What suggests itself is to use a central event handling facilities, similar to APIs like libevent. As Scheme provides first class continuations and CHICKEN a very efficient implementation of the same, the usual "callback hell" problem can be addressed, as continuations allow an inversion of the control flow, to remove the orchestration of multiple callbacks competing for handling of events.
Note that the base scheduling mechanism can be thread-agnostic - a dispatcher which takes event specificiations and continuations is sufficient to serve multiple independent threads of execution, as long as the current execution context can be fully stored (this must include objects that are conceptually thread-local, even in the absence of a "real" threading system). Threading APIs are many, and one should be able to build on top of the basic scheduling/dispatching mechanism that this document proposes.
Thread-local state could be unified to "parameters", adding standard I/O ports and the exception handler, thus removing the current special handling these objects.
To summarize: what would be needed is
a) A representation of an execution context (below the thread level), including a continuation and a set of parameters.
b) An internal API to register, unregister and test pairs of event-specifications and execution contexts.
Existing facilities could then be built on top of this, e.g.
- (Green) threads, provided a low-level timer test facility exists, which requires polling. Alternatively one could use SIGALARM for this, reducing the need to poll. Again, a general mechanism would allow trying out both, at the Scheme level.
- UNIX signal handling (a mess in itself)
- Efficient handling of multiple, concurrent, blocking I/O sources/sinks.
- I/O filtering, i.e. wrapping I/O in decoding/encoding wrapper streams.
- All sorts of timers.
- Finalization handlers.
Note that to handle I/O efficiently, it may be necessary to use direct UNIX I/O (or the appropriate Windows API equjvalent) instead of buffering libc stdio, requirung to handle (or not handle) buffering.
Running finalization handlers in a separate execution context is dearly needed, as currently this code runs in some arbitrary thread.
Since parameters are the sole facility to isolate context-local information, some emphasis has to be put on a simple and effective implementation and semantics that do not collide with existing practice and SRFIs.
Operating systems have varying support for scheduling from multiple event sources. BSD systems have kqueue(2) which is a natural and convenient mechanism. Epoll(2) for Linux appears to be suboptimal since it doesn't seem to handle normal files well, the Windows API has WaitForMultipleObjects. Low-level implementations for all these cases (*BSD/Mac, Linux, Win32) need to be provided, covering scheduling of signals, timers, I/O events and sub-process completion in a uniform manner.
The programming interface should have roughly these elements:
- register an execution context for one or more events, returning an identifier.
- unregister a previously registered context, given an identifier.
- yield execution until an event is available and dispatch to the triggered execution handlers for the next event.
- allow for composition of I/O processing (buffering, codecs).
Possible extensions:
- register an execution context to be added to a previously registered context, this allows for monitoring specific events, perhaps even allowing before/after/around wrapping of handlers (or perhaps not).
- explicit waiting + testing for events.
- more?
The development can be done in several stages:
- Implement the core polling, scheduling and dispatching logic for BSD + Linux
- Change the way thread-local variables are handled (parameterize all thread-local state)
- Implement the basic handling of independent execution contexts
- Devise and implement an internal low-level API
- Test the core functionality
- Implement a version using the Windows API
- Change basic console + file I/O to use this facility
- Run finalizers in distinct execution contexts
- Hook interrupt- and signal-handling into the scheduling facility
- Add a non-threaded timer API
- Fix timer-interrupt handling to use the scheduling facility
- Modify SRFI-18 to use the internal API + timer interrupts