|parrotcode: Parrot Events|
|Contents | Documentation|
docs/pdds/draft/pdd24_events.pod - Parrot Events
This document defines the requirements and implementation strategy for Parrot's event subsystem.
An event is a notification that something has happened: the user has manipulated a GUI element, an I/O request has completed, a signal has been triggered, or a timer has expired. Most systems these days have an event handler (often two or three, which is something of a problem), because handling events is so fundamental to modern GUI programming.
Parrot's event handling system is integrated with the central concurrency scheduler. When an event is created (by a GUI element, etc), it is added to concurrency task list. By default, events have a higher priority in the task list than asynchronous I/O operations or threaded code operations. At predetermined points in the execution cycle (between low-level discrete operations for safety, behind the scenes during "blocking" operations, at the same operational lulls where GC runs are performed, etc), the task list is queried, and tasks are dispatched. Events are dispatched to event handlers.
Event handlers are registered with the concurrency scheduler. When dispatching an event, the concurrency scheduler compares the type of the event to the type of the event handler and selects the closest match. An exact type match is always highest ranked, then a parent type, and finally a general handler designed to handle all types. In addition to the type of an event, an event handler may check the data attribute of an event and decide whether to accept or decline that event based on its data. (For example, a "key press" may contain a data attribute value specifying that it's a "key 'a' press".)
In the simple case, the concurrency scheduler runs within the single interpreter thread. In more complex cases (particularly on multi-processor machines), the concurrency scheduler runs in its own thread. In the multi-threaded case, each individual thread may register an event handler with the concurrency scheduler, and the event that matches the registered handler will be dispatched to the thread that registered it. In the most complex case, each thread runs a lightweight concurrency scheduler that coordinates with the central scheduler (so, for example, the mini-scheduler can decide when to run an event handler dispatched by the central scheduler).
An event handler may mark itself as a final event handler, removing the event from the task list, or it may be a non-final handler, leaving the event in the task list for another thread to collect.
Most events are non-fatal, so if a handler isn't found for them when they're extracted from the task list, they just expire and drop out of the task list. Events can also be fatal, in which case the interpreter will exit if a handler isn't found (essentially the same effect as an exception). When a non-final event handler leaves an event in the task list, it will expire if no further relevant event handlers can be found for the event.
The operation to query the concurrency scheduler and find if it has any tasks to process is as cheap as possible, so it may be queried at regular intervals.
An event is a PMC object that contains a type, data for the event, and a priority.
The type of an event is only used to match the event with an event handler, but is notionally similar to the class of an object.
The data for the event is a PMC and could be any data passed to the event handler by the code that originates the event.
The priority of an event affects when it is processed by the task list. Higher priority events are processed before lower priority events. Age is also a relevant factor, when two events have the same priority, the older one is processed first. An event handler or the scheduler may also be set to ignore events below a certain threshold of priority. When the central scheduler ignores an event because of its priority level, the event remains in the task list until the priority threshold changes.
An instance of the Event PMC has 3 internal attributes, which are:
In addition to the attributes, an event uses a private PMC flag to mark itself as fatal or non-fatal.
STRING * get_string()
PMC * get_pmc()
INTVAL isa(STRING *)
An event handler contains a code object, as well as metainformation about where it was registered. More specifically, it contains a continuation object, capturing the full state of the interpreter where it was created. In many cases, this has no more effect than guaranteeing that the event handler code executes within the appropriate context of the thread that registered the handler.
Because events are handled in mainline code, they don't have the restrictions commonly associated with interrupt-level code. It's safe and acceptable for an event handler to throw an exception, allocate memory, or manipulate thread or global state safely. Event handlers can even acquire locks if they need to, though it's not a good idea to have an event handler blocking on lock acquisition.
An instance of the EventHandler PMC has 3 internal attributes, which are:
The interpreter pointer may be a proxy that simply provides the interface of an interpreter object, and knows how to communicate with its remote interpreter object (in threaded or clustering concurrency).
void init_pmc(PMC *)
void set_string(STRING *)
opcode_t * invoke(void *)
The following opcodes are used with the event system:
$P1 = new 'Event' $P1 = new 'EventHandler' $P1 = new 'EventHandler', $P2
$P0 = new 'Event' # set attributes schedule $P0
$P0 = new 'EventHandler' # set attributes addhandler $P0
Parrot defines a core set of event types. Users may also define their own event types. An event type of 'allevents' is effectively the parent of all event types, and will respond to any event in the task list that isn't handled by other event handlers in a final way.
allevents ioevent packetsent packetreceived fileopened fileclosed readcomplete keyevent keydown keypress keyup mouseevent click doubleclick mousedown mouseup mousemove mouseenter mouseleave windowevent blur focus scroll textselect resize minimize maximize restore signal alarm interrupt childdie
Signals are a special form of event. Parrot presents them as mildly special, as a remnant of Perl's Unix heritage, but under the hood they're not treated any differently from events generated by other sources.
Signals can be divided into two categories, those handled by the operating system, and those passed on to the process.
OS-handled signals are things like SIGKILL, which kills a process, or SIGSEGV, which indicates that the process has tried to access memory that isn't part of your process. Because these signals are handled before they reach the process, there's no way for Parrot to catch them.
Signals Parrot can handle include things like SIGCHLD, indicating that a child process has died, or SIGINT, indicating that the user has hit
^C on the keyboard, or SIGALRM, the timer expiration signal. Parrot turns these signals into events and puts them in the event task list, and will be processed and dispatched to an event handler in order of priority and age just like other events.
A timer is a special kind of event with a time delay. A timer can act as any builtin or custom event type. Timers can be be flagged as repeating. Parrot provides builtin timers for greater portability. Some platforms provide their own implementations of timers, which may be used when performance on a particular platform is more important than portability.