Suspended goals are actually represented by a special opaque data type, called suspension, which can be explicitly manipulated under program control using the primitives defined in this section. Although usually a suspended goal waits for some waking condition in order to be reactivated, the primitives for suspension handling do not enforce this. To provide maximum flexibility of use, the functionalities of suspending and waking/scheduling are separated from the trigger mechanisms that cause the waking.
A suspension represents a goal that is part of the resolvent. Apart from the goal structure proper, it holds information that is used for controlling its execution. The components of a suspension are:
Suspensions which should be woken by the same event are grouped together in a suspension list. Suspension lists are either stored in an attribute of an attributed variable or attached to a symbolic trigger.
The most basic primitive to create a suspension is make_suspension(Goal, Priority, Susp [, Module]) where Goal is the goal structure, Priority is a small integer denoting the priority with which the goal should be woken and Susp is the resulting suspension.
Note that usually make_suspension/3,4 is not used directly, but implicitly via suspend/3,4 (described in section 17.6) which in addition attaches the suspension to a trigger condition.
A suspension which has not yet been scheduled for execution and executed, is called sleeping, a suspension which has already been executed is called executed or dead (since it disappears from the resolvent, but see section 17.9 for an exception). A newly created suspension is always sleeping, however note that due to backtracking, an executed suspension can become sleeping again. Sometimes we use the term waking, which is less precise and denotes the process of both scheduling and eventual execution.
By default, suspensions are printed as follows (the variants with invocation numbers are used when the debugger is active):
’SUSP-_78-susp’ | sleeping suspension with id _78 |
’SUSP-_78-sched’ | scheduled suspension with id _78 |
’SUSP-_78-dead’ | dead suspension with id _78 |
’SUSP-123-susp’ | sleeping suspension with invocation number 123 |
’SUSP-123-sched’ | scheduled suspension with invocation number 123 |
’SUSP-123-dead’ | dead suspension with id invocation number 123 |
It is possible to change the way suspensions are printed by defining a portray/3 transformation for the term type goal.
The following summarises the predicates that can be used to create, test, decompose and destroy suspensions.
goal
, module
, priority
, state
or invoc
(debugger invocation number).priority
and invoc
(debugger invocation number) fields
of a suspension can be changed using this primitive.
If the priority of a sleeping suspension is changed,
this will only have an effect at the time the suspension gets
scheduled. If the suspension is already scheduled, changing
priority has no effect, except for future schedulings of demons
(see 17.9).The system keeps track of all created suspensions and it uses this data e.g. in the built-in predicates delayed_goals/1, suspensions/1, current_suspension/1, subcall/2 and to detect floundering of the query given to the ECLiPSe top-level loop.
Suspensions are attached to variables by means of the attribute mechanism. For this purpose, a variable attribute needs to have one or more slots reserved for suspension lists. Suspensions can then be inserted into one or several of those lists using
For instance,
insert_suspension(Vars, Susp, inst of suspend, suspend)
inserts the suspension into the instlist of the (system-predefined) suspend attribute of all variables that occur in Vars, and
insert_suspension(Vars, Susp, max of fd, fd)
would insert the suspension into the max list of the finite-domain attribute of all variables in Vars.
Note that both predicates find all attributed variables which occur in the general term Vars and for each of them, locate the attribute which corresponds to the current module or the Module argument respectively. This attribute must be a structure, otherwise an error is raised, which means that the attribute has to be initialised before calling insert_suspension/4,3. Finally, the Index’th argument of the attribute is interpreted as a suspension list and the suspension Susp is inserted at the beginning of this list. A more user-friendly interface to access suspension lists is provided by the suspend/3 predicate.
Many important attributes and suspension lists are either provided by the suspend-attribute or by libraries like the interval solver library lib(ic). For those suspension lists, initialisation and waking is taken care of by the library code.
For the implementation of user-defined suspension lists, the following low-level primitives are provided:
A single suspension or a list of suspensions can be attached to a symbolic trigger by using attach_suspensions(+Trigger, +Susps). A symbolic trigger can have an arbitrary name (an atom).
Suspended goals are woken by submitting at least one of the suspension lists in which they occur to the waking scheduler. The waking scheduler which maintains a global priority queue inserts them into this queue according to their priority (see figure 17.1). A suspension list can be passed to the scheduler by either of the predicates schedule_suspensions/1 (for triggers) or schedule_suspensions/2 (for uder-defined suspension lists). A suspension which has been scheduled in this way and awaits its execution is called a scheduled suspension.
Note, however, that scheduling a suspension by means of schedule_suspensions/1 or schedule_suspensions/2 alone does not implicitly start the waking scheduler. Instead, execution continues normally with the next goal in sequence after schedule_suspensions/1,2. The scheduler must be explicitly invoked by calling wake/0. Only then does it start to execute the woken suspensions.
The reason for having wake/0 is to be able to schedule several suspension lists before the priority-driven execution begins5.