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, while the ECLiPSe system uses events with a numerical identifier to raise errors (The error numbers are listed in appendix D).

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

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 elasped time. 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 elasped time. 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 elasped 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 elasped from when the predicate is executed.

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 tests if Name is a pending after event. The predicate suceeds if it is, fails if it is not.

More details on after events

More precisely, Time is actually the minimum of elasped 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. However, any other events that are raised during the execution of the event handler will interrupt the execution of the original event handler. It is thus advisable to keep the event handling code as short as possible - if more complex actions needs to be performed, it should be done via the event handler triggering a suspended goal, which will execute at a lower priority than 1.

The after event make use of the vtalrm signal where this signal exists, or the alrm signal if it doesn't, so elasped time is normally measured in elasped user cpu time, or in real time in the case of alrm. Currently, alrm is used only on the Windows platform. The user should not make use of these signals 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 elasped 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.

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:

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

1999-08-06