Up Next

14.1  Events

14.1.1  Event Identifiers and Event Handling

Events are identified by names (atoms) or by anonymous handles.

When an event is raised, 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).

Creating Named Events

A named event is created by defining a handler for it using set_event_handler/2:

:- set_event_handler(hello, my_handler/1).
my_handler(Event) :-
    <code to deal with Event>

A handler for a named event can have zero or one arguments. When invoked, the first argument is the event identifier, in this case the atom hello. It is not possible to pass other information to the handler.

The handler for a defined event can be queried using get_event_handler/3.

Creating Anonymous Events

An anonymous event is created with the built-in event_create/3:

..., event_create(my_other_handler(...), [], Event), ...

The built-in takes a handler goal and creates an anonymous event handle Event. This handle is the only way to identify the event, and therefore must be passed to any program location that wants to raise the event. The handler goal can be of any arity and can take arbitrary arguments. Typically, these arguments would include the Event handle itself and other ground arguments (variables should not be passed because when the event is raised, a copy of the handler goal with fresh variables will be executed).

14.1.2  Raising Events

Events can be raised in the following different ways:

Raising Events Explicitly

To raise an event from within ECLiPSe code, call event/1 with the event identifier as its argument. If no handler has been defined, a warning will be raised:

?- event(hello).
WARNING: no handler for event in hello
Yes (0.00s cpu)

The event can be an anonymous event handle, e.g.,

?- event_create(writeln(handling(E)), [], E), event(E).
handling($&(event,"371bqz"))
E = $&(event,"371bqz")
Yes (0.00s cpu)

Raising events explicitly is mainly useful for test purposes, since it is almost the same as calling the handler directly.

Raising Events from Foreign Code

To raise an event from within foreign C/C++ code, call

ec_post_event(ec_atom(ec_did("hello",0)));

This works both when the foreign code is called from ECLiPSe or when ECLiPSe is embedded into a foreign code host program.

Timed Events (“after” events)

An event can be triggered after a specified amount of elapsed time. The event is then handled sychronously by ECLiPSe. These events are known as “after” events, as they are set up so that the event occurs after a certain amount of elapsed time. They are setup by one of the following predicates:

event_after(+EventId, +Time)
This sets up an event EventId so that the event is raised once after Time seconds of elapsed time from when the predicate is executed. EventId is an event identifier and Time is a positive number.
event_after_every(+EventId, +Time)
This sets up an event EventId so that the event is raised repeatedly every Time seconds: first Time seconds after the invocation of the predicate, then Time seconds after that event was raised, and so on.
events_after(+EventList)
This sets up a series of after events specified in the list EventList, which contains events of the form EventId-Time, or EventId-every(Time) (specifying a single event or a repeated event respectively).

The Time parameter 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, i.e., where it can make a procedure call.

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

cancel_after_event(+EventId, -Cancelled)
This finds and cancels all pending after events with name EventId and returns the actually cancelled ones in a list.
current_after_events(-Events)
This returns a list of all pending after events.

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

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 time.1 This setting 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 timer should therefore not be changed once after events are initiated.

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

14.1.3  Events and Waking

Using the suspension and event handling mechanisms together, a goal can be added to the resolvent and executed after a defined elapsed time. To achieve this, the goal is suspended and attached to a symbolic trigger, which is triggered by an afer-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 will thus not be executed.

The following is an example of waking a goal with a timed event. Once monitor(X) is called, the current value of X will be printed every second until the query finishes or is backtracked over:

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

14.1.4  Aborting an Execution with Events

Typically, event handlers would perform some action and then succeed, letting the interrupted exectuion continue unharmed. Event handlers for asynchronous events should never fail, because the failure will be inserted in a random place in the resolvent, and the effect will be unpredictable. It is however sometimes useful to allow an asynchronous event to abort an execution (via throw/1), e.g., to implement timeouts.2

When dealing with events that occur asynchronously (in particular after-events), and event handlers that cause the execution to abort, it is often a problem that event handlers may be interrupted or preempted by other event handlers. This can be avoided by use of the event-defer mechanism. An event can be declared with the defer-property, which means that all further event handling is temporarily suppressed as soon as the handling of this event begins. In this case, the event handler is responsible for reenabling event handling explicitly before returning by calling events_nodefer/0. For instance:

:- set_event_handler(my_event, defers(my_handler/0)).
my_after_handler :-       % event handling is deferred at this point
        <deal with event>,
        events_nodefer.   % allow other events to be handled again

In the presence of other event handlers which can cause aborts, this will protect the handler code from being preempted.


1
This is time that the CPU spends on executing user code, i.e., the ECLiPSe program.
2
Since implementing reliable timeouts is a nontrivial task, we recommend the use of lib(timeout) for this purpose.

Up Next