13.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 builtin
event_create/3:
..., event_create(my_other_handler(...), [], Event), ...
The builtin 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).
13.1.2 Raising Events
Events can be raised in the following different ways:
-
Explicitly by the ECLiPSe program itself, using
event/1.
- By foreign code (C/C++) using the ec_post_event() function.
- Via signals/interrupts by setting the interrupt handler to
event/1.
- Via I/O streams (e.g. queues can be configured to raise an event
when they get written into).
- Via timers, so-called after-events
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'(16'edbc0b20))
E = 'EVENT'(16'edbc0b20)
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:
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.
This sets up an event EventId so
that the event is raised every Time seconds has elapsed from when
the predicate is executed.
This sets up a series of after events specified in EventList, which is list
of events in 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 –
it needs to be 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 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:
This finds and cancels all pending after events with name EventId and returns
the actually cancelled ones in a list.
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 time1 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 user should should not make use of these
timers for their own purpose if they plan to use the after event
mechanism.
13.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 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 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.
13.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 resolvent, and the effect will be unpredictable.
It is however sometimes useful to allow an asynchronous event to abort
an execution (via
exit_block/1), e.g.
to implement timeouts2.
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. Events 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.