None of this is set in stone, but take a look and see how everyone feels
I'll get to IO after, since the two are pretty tightly intertwined, and
changes to this will affect the IO stuff too.
Events, another design sketch
Events are a terrific idea, as evidenced by the vast array of event
handling libraries there are. (There are at least a half dozen on
CPAN) All of which are the same but different, and incompatible. Of
Proper event handling's the only way to manage asynchronous IO, and
since parrot needs event handling it only makes sense to expose it in
general, so we can avoid the problems of having multiple event
An event is an indication to your program that something has
happened. It may be that a timer has fired, a packet has been
received (or sent) on a socket, a disk read has completed, or that
the user has moved the mouse.
Events are asynchronous--that is they can happen at any time, and
your program has no real control over when they are generated. It
does, however, have control over when they are processed. More on
There are two main classes of events, which we'll call expected and
An expected event is one that your program is specifically expecting
to get as a result of a request for asynchronous action, and each
expected event has a corresponding event handle. User code can wait
for an expected event to complete, cancel an expected event, and
query the status of an expected event. Expected events may also be
either complete or incomplete. An incomplete event is one whose
requested action hasn't yet happened, while a complete event
Unexpected events, on the other hand, happen for reasons outside of
your program. Signals, GUI events, and exceptions for asynchronous
files (which may not be delivered as signals on many platforms) are
examples of unexpected events. Unexpected events, by their nature, are
always complete, since they aren't generated until an action actually
Some event sources may be either expected or unexpected. Network
connections are an example of this--you may have a network filehandle
set either as an expected event source, where your program must
explicitly ask for data, or set as an unexpected event source, where
a monitoring thread captures data as it is received and generates
events for the captured data.
Callbacks and User Data
Each expected event can have a callback routine and piece of user
data associated with it. When the event request is satisfied (The IO
operation completes or the timer fires, for example) the callback
associated with it is invoked. The user data, as well as the event
data, if there is any, is passed into the callback.
Callback signatures are fixed, and of the form:
(Preturndata, Icommandstatus) =
callbacksub(Pevent, Peventdata, Puserdata, Icommand)
The callback is passed in the event PMC, the PMC for the event data,
and the PMC for the user data, if any. (Either or both of these can
be the Null PMC if there is no user or event data) Command is always
0 for callback handling. (Callbacks and IO layer functions are
identical. More detail in the IO section)
The callback must then return the data and a command status. The
status should be 0 for correct completion, a non-zero negative number
for an error, and a non-zero positive number for successful
completion with a change in status for the next handler in the chain,
if there is one.
If a callback function throws an exception the exception itself is
deferred. Expected events defer the exception until the event is
waited on, while unexpected events throw their excecption when their
event functions are done processing. (This does mean that expected
events may have their exceptions effectively ignored if the event is
never waited on)
When there are multiple event handler in a chain, each handler can
alter the data and command passed to the next handler in the
chain. The return data is passed in as the user data to the next
command, and the return status is, if a positive non-zero number,
passed to the next handler in the chain. If the return status is 0,
then the same command is passed into the next handler in the
chain. This allows event handlers to preprocess the event if need be.
Each event source can have a generic handler associated with it. This
handler is called before the event's specific callback is invoked, if
it has one. Generic handlers have the same function signature as
callback functions do, and behave the same way--if they return 0 the
event is passed to the next handler in the chain, and if they return
non-zero then the event stops passing through and is marked as failed.
Note that the handler table is scoped, however due to the inherently
unpredictable nature of asynchronous events it isn't possible to
guarantee that changes in the handler table due to scope changes will
take place immediately so far as event sources are concerned--that
is, the program may leave a scope and change the handler table yet
have an event generated that uses the old handler table.
Events and Exceptions
Event handlers are generally discouraged from throwing exceptions,
however it is sometimes necessary. Exceptions are handled differently
by expected and unexpected events.
With an expected event, if an event handler is terminated by an
unhandled exception, that exception is attached to the event itself,
and when user-level code C<wait>s on the event, the exception is
Unexpected events whose handlers throw exceptions are handled somewhat
differently. In that case, the exception is thrown as soon as the
event is drained from the event queue. This may or may not happen
immediately, depending on how the event in question is currently being
handled. If the event is processed in a separate event handling thread
then the event is put in the main thread's event queue and the
exception will be thrown when the main thread drains the queue and
hits the event. If the event is currently being processed in the main
thread (because there is no event-handling thread, or this event
isn't being handled by one) then as soon as the event is drained from
the event queue the exception is thrown.
How events are handled
There are three steps in event handling.
First, an event is triggered. This puts the event into the event
queue, but otherwise does nothing. (This operation is interrupt-safe,
though there issues in obtaining event PMCs at interrupt level in
Next, a queue runner pulls an event out of the event queue and calls
its handlers. These are a combination of any event class handlers as
well as an event-specific callback, if there is one. This operation
happens as normal user-level code, though it may be done
significantly after the event itself is put in the queue, and may not
happen in the same thread as the event was requested from. [NB: I'm
not sure what to do about event handling threads and shared data yet]
Finally, for expected events, something can wait on the event to get
the event's data and status. This can be done before the event has
completed, in which case the operation will wait (draining the event
queue of pending events)
Note that the queue runner will automatically wait on any unexpected
event that it completes. This puts the event in the done state and
will throw any exception that the event handlers may have
generated. Expected events, however, will be left alone until
user-level code explicitly waits on it.
Events have five states. They are:
* Quiescent - The event object has been newly allocated but hasn't
been associated with anything yet and can't be triggered
* Waiting - The operation the event triggers on hasn't yet happened
* Triggered - The operation the event triggers on has happened, but
the event's handlers haven't been called yet
* Ready - The event has been processed by its handlers but hasn't yet
been waited on to flush out any exception it might have.
* Done - The event has flushed any pending exceptions and can now
only be queried for its status and data
Events are just PMCs, and there are no ops specifically to create
them. To create an event, allocate a PMC of the class Event. These
are array/hash combination PMCs and may be accessed as follows:
0 (CLASS) - The class of the event
1 (EVENT_DATA) - The event data associated with this event
2 (USER_DATA) - The user data associated with the event
3 (CALLBACK) - The callback sub for this event
4 (EXCEPTION) - The pending exception for this event, if any
5 (STATUS) - The status from the exception handlers
6 (STATE) - What state the event is in
7 (LEVEL) - The event level this event is at
The opcodes in this section are a combination of event requests and
event handling ops. It doesn't include the IO ops--those are separate.
Most of the event request ops have two forms, one of which takes a
callback PMC and user data PMC.
Explicitly check to see if there are any events pending in the event queue and,
if so process one.
Wait forever, draining the event queue as it fills. This op only
exits if it encounters a QUEUE_DONE event in the queue.
Wait for the specified event to complete. If the event has already
completed the op doesn't wait. If there is a pending exception it is
thrown. This opcode will drain the pending event queue.
getstat Istatus, Pevent
Returns the status of the event, noting whether it has been completed
yet and, if so, what happened.
getdata Pdata, Pevent
getdata Sdata, Pevent
Get the data associated with the event, if there is any. What the data is
depends on what generated the event. (For filehandle reads this will
be the data read from the filehandle)
getuser Puserdata, Pevent
Get the user data that was passed into this event.
Cancel the event, if possible.
Post an event to the event queue for later handling
sethandler Ieventclass, Phandlersub
Set the default handler for the specified class of events.
Set the current event level. Events of a lower level than this won't
settimer Pevent, Nseconds[, Pcallback, Puserdata]
settimer Pevent, Iseconds[, Pcallback, Puserdata]
Set a timer to go off in C<seconds> seconds.
setalarm Pevent, Nabs_time[, Pcallback, Puserdata]
setalarm Pevent, Iabs_time[, Pcallback, Puserdata]
Set a timer to go off at the absolute time specified.
setinterval Ninterval_seconds[, Pcallback, Puserdata]
setinterval Iinterval_seconds[, Pcallback, Puserdata]
Set an interval timer that goes off every interval_second seconds.
When are events handled?
Parrot does C<not> guarantee immediate delivery of events. That,
unfortunately, is untenable for us, as much as we would like it. The
overhead involved in checking for pending events every op is
excessive for at least some of the run cores.
Instead, parrot guarantees that events get checked with some
regularity. All ops that appear to block are actually waiting for a
wakeup event, and so drain the queue as they wait. Other ops may check
the event queue, and are tagged with the "check_event" property in the
ops files. It's the compiler's job to ensure that at least some
event-checking ops get executed, unfortunately.
Because many events are actually generated from within C code, the
following API is exposed for use:
INTVAL Parrot_ping_event(Parrot_Interp Interpreter, INTVAL type);
Note that an event of type C<type> has just occurred. Returns either
0 on success or 1 on failure. This function may fail if the target
interpreter is unable to post an event to its event queue. This
normally happens because there are no event PMCs available to
allocate for the event.
INTVAL Parrot_post_event(Parrot_Interp Interpreter, PMC *event);
Post the event PMC to the target interpreter's event queue. Returns
success status, 0 on success, 1 on failure. This function may fail if
the event can't be posted to the target interpreter's event queue for
--------------------------------------"it's like this"-------------------
Dan Sugalski even samurai
d...@sidhe.org have teddy bears and even
teddy bears get drunk