next up previous index
Next: Errors Up: Events and Interrupts Previous: Events and Interrupts   Index

Subsections


Events

Event Identifiers

Events are identified by names or by small numbers. User defined events always have names (atoms), while the ECLiPSe system uses events with a numerical identifier to raise errors (The error numbers are listed in appendix C).

Handling Events

When an event occurs, a call to the appropriate handler is inserted into the resolvent (the sequence of executing goals). The handler will be executed as soon as possible, which means at the next synchronous point in execution, which is usually just before the next regular predicate is invoked. Note that there are a few built-in predicates that can run for a long time and will not allow handlers to be executed until they return (e.g. read/1, sort/4).

A handler is defined using a call like this

my_handler(Event) :-
    <code to deal with Event>

:- set_event_handler(hello, my_handler/1).
The handler's first argument is the event identifier, in this case the atom 'hello'.

The handler for a defined event can be obtained by get_event_handler/3.

Note that to ensure the handling of all events, an event handler should not directly fail or raise an exception. This is because the system will backtrack if the handler fails or raise an exception, and any other raised events that has not yet been handled will not be handled, and thus the system will seem to `forget' about such events. The event handler itself should also be run at the highest priority (1), and if failure is desired, this can be done indirectly through triggering a suspended goal which runs at a lower priority.

Raising Events

Events are normally posted to the ECLiPSe engine from its software environment, e.g. from a C program using
ec_post_event(ec_atom(ec_did("hello",0)));
This works both when the foreign code is called from ECLiPSe or when ECLiPSe has been called from the foreign code.

It is also possible to post an event from within an interrupt handler by setting the interrupt handler to event/1. This is the recommended mechanism to translate an asynchronous interrupt into a synchronous event. E.g.

:- set_interrupt_handler(alrm, event/1).
:- set_event_handler(alrm, handle_alarm/1).

An event can also be raised by the running program itself, using event/1:

    ..., event(hello), ...
However, this is mainly useful for test purposes, since it is almost the same as calling the handler directly.


Timed Events (after events)

ECLiPSe provides support for setting up an event which is then triggered after a specified amount of elapsed time. The event is then handled sychronously by ECLiPSe. Previous to version 4.2, the user can program this functionality using the (now obsolete) low level OS dependent set_timer/2 primitives. From version 4.2, a higher level interface is provided, allowing for multiple independent timed events to be set up in a standardised way. These events are known as after events, as they are set up so that the event occurs after a certain amount of elapsed time. The elapsed time used is either elapsed real time (real) or elapsed user CPU time (virtual). They are setup by two predicates:


event_after(+Name, +Time)

This sets up an event Name so that the event is raised once after Time seconds of elapsed time from when the predicate is executed. Name is an atom and Time is a positive number.


event_after_every(+Name, +Time)

This sets up an event Name so that the event is raised every Time seconds has elapsed from when the predicate is executed.


events_after(+EventList)

This sets up a series of after events specified in EventList, which is list of events in the form Name-Time, or Name-every(Time), specifying a single event or a repeated event respectively.

Once an after event has been set up, it is pending until it is raised. In the case of event_after_every/2, the event will always be pending because it is rasied repeatedly. A pending event can be cancelled so that it will not be raised:


cancel_after_event(+Name)

This cancels the pending after event Name. If Name is not a pending after event, the predicate fails.


current_after_event(?Name)

This can be used to test if Name is a pending (if Name is an atom) after event, or it can be used to return the list of all pending after events (if Name is a variable). This predicate can only be called from within an after event handler, when the after event timer is frozen (see next section).

More details on after events

More precisely, Time is actually the minimum of elapsed time before the event is raised. Factors constraining the actual time of raising of the event include the granularity of the system clock, and also that ECLiPSe must be in a state where it can synchronously process the event - it needs to be where it can make a procedure call.

The event is raised and executed at priority 1, so that it cannot be interrupted by execution of woken goals in the middle of handling the event. In addition, the after event timer is frozen (i.e. the countdown is stopped) during the execution of the event handler, so that no new pending after events will be raised while handling the after events. The event handling code is executed once when the event is triggered, i.e. it would not be backtracked into. Failures in the event handling code are not propagated beyond the handler, and exceptions (aborts) should also not be raised during the event handling, as this would prevent other already raised events from being processed. It is advisable that the event handling code be as simple as possible - if more complex actions needs to be performed, it should be done via the event handler triggering a suspended goal, executing at a lower priority than 1, and for which the event timer would not be frozen, and where exceptions and failures can be propagated.

By default, the after event feature uses the real timer. The timer can be switched to the virtual timer, in which case the elapsed time measured is user CPU time13.1 This default is specified by the ECLiPSe environment flag after_event_timer (see get_flag/2, set_flag/2). Note that if the timer is changed while some after event is still pending, these events will no longer be processed. The after event mechanism is also set to the unfrozen state. It is strongly recommended that the user does not change the timer once after events are initiated.

Currently, the virtual timer is not available on the Windows platform. In addition, the user should should not make use of these timers for their own purpose if they plan on using the after event mechanism.

The after event mechanism allows multiple events to make use of the timing mechanism independently of each other. However, the same event can be setup multiple times with multiple calls to event_after/2 and event_after_every/2. The cancel_after_event/1 will cancel all instances of an event.

Using the suspension and event handling mechanisms, the user can cause a goal to be added to the resolvent which would then be executed after a defined elapsed time. The goal will be suspended and attached to a symbolic trigger, which is triggered by the event handler. The goal behaves `logically', in that if the execution backtracks pass the point in which the suspended goal is created, the goal will disappear from the resolvent as expected and thus not be executed. The event will still be raised, but there will not be a suspended goal to wake up. Note that if the execution finishes before the suspended goal is due to be woken up, it will also not enter the resolvent and is thus not executed.

The following is an example for waking a goal with a timed event. Once monitor(X) is called, the current value of X will be printed every second while the query monitor(X) is part of is executing:

:- set_event_handler(monvar, trigger/1).

monitor(Var) :-
     suspend(m(Var), 3, trigger(monvar)),
     event_after_every(monvar, 1).

:- demon m/1.
m(Var) :- writeln(Var).

:- monitor(Var), <do_something>.
Note the need to declare m/1 as a demon: otherwise, once m/1 is woken up once, it will disappear from the resolvent and the next monvar event will not have a suspended m/1 to wake up. Note also that it is necessary to connect the event machanism to the waking mechanism by setting the event handler to trigger/1.


next up previous index
Next: Errors Up: Events and Interrupts Previous: Events and Interrupts   Index
Warwick Harvey
2004-08-07