docs/pdds/draft/pdd24_events.pod - Parrot Events


This document defines the requirements and implementation strategy for Parrot's event subsystem.




- Events are objects

- Events are typed

- Events can be fatal or non-fatal

- Event handlers are code objects

- Event handlers can be final or non-final


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.

Event API ^

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:

  1. The type of the event
  2. The priority of the event
  3. A PMC containing any additional data associated with the event, which may be used by the event handler.

In addition to the attributes, an event uses a private PMC flag to mark itself as fatal or non-fatal.

Event Vtable Entries


    STRING * get_string()
Returns a simple string name for the event, suitable for printing in error messages.


  PMC * get_pmc()
Returns the data attribute of the event, or PMCNULL if the event has no data.


Returns true or false if the event is of the type passed in the string parameter, or has a parent of that type.

Event Handler API ^

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:

  1. The type of event it handles
  2. A minimum threshhold of priority, below which it will ignore the event even if it is the right type. The default threshhold is 0, accepting all event priorities.
  3. The core code object of the event handler.
  4. A pointer to the interpreter that registered the event handler. The default is the interpreter in which the event handler was created.

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).

Event Handler Vtable Entries


    void init_pmc(PMC *)
Initializes the PMC, either with a single sub object for the core code, or with a hash object containing arguments for the code object, type, priority threshold, and interpreter object for the handler.


    void set_string_native(STRING *)
Set the type of event this handler responds to. {{NOTE: These basic attribute setting operations could be set_attr_str and get_attr_str instead.}}


    STRING* get_string()
Retrieve the type of event this handler responds to.


Set the minimum threshhold of priority.

set_pmc(PMC *)

Set the interpreter object for this event handler.


    opcode_t * invoke(void *)
Invoke the event handler.

Opcodes ^

The following opcodes are used with the event system:


  $P1 = new 'Event'
  $P1 = new 'EventHandler'
  $P1 = new 'EventHandler', $P2
Creates a new event or event handler.


  $P0 = new 'Event'
  # set attributes
  schedule $P0
Register an event with the concurrency scheduler. If the concurrency scheduler for this interpreter (thread/clustered instance) is linked to another "primary" concurrency scheduler, this will pass along the event to the primary. All details about the event (its type, whether it's fatal or non-fatal, whether it's a timer, etc) are stored within the event PMC.

{{NOTE: other possibilities 'raise' and 'fire' by KJS}}


  $P0 = new 'EventHandler'
  # set attributes
  addhandler $P0
Register an event handler with the concurrency scheduler. If the concurrency scheduler for this interpreter (thread/clustered instance) is linked to another "primary" concurrency scheduler, this will pass along the event handler to the primary.

Event Type Hierarchy ^

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.







Signals ^

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.

Timers ^

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.