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:
- The goal structure
- A term representing the goal itself, e.g., X > Y.
- The goal module
- The module from which the goal was called.
- The scheduling priority
- The priority with which the goal will be scheduled when it becomes woken.
- The run priority
- The priority under which the goal will eventually be executed.
- The state
- This indicates the current position of the suspension within the resolvent. It is either suspended (sleeping), scheduled or executed (dead).
- Additional data
- Debugging information etc.
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)
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 is not used directly, but implicitly via suspend/3,4 (described in section 18.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 18.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.
- make_suspension(Goal, Priority, Susp)
- Create a suspension with a given priority from a given goal. The goal will subsequently show up as a delayed goal.
- is_suspension(Susp)
- Succeeds if Susp is a sleeping or scheduled suspension, fails if it is not a suspension or a suspension that has been already executed.
- type_of(S, goal)
- Succeeds if S is a suspension, no matter if it is sleeping, scheduled or executed.
- get_suspension_data(Susp, Name, Value)
- Extract any of the information contained in the suspension: Name can be one of goal, module, priority, state or invoc (debugger invocation number).
- set_suspension_data(Susp, Name, Value)
- The 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 18.9).
- kill_suspension(Susp)
- Convert the suspension Susp into an executed one, i.e., remove the suspended goal from the resolvent. This predicate is meta-logical as its use may change the semantics of the program.
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 must have one or more slots reserved for suspension lists. Suspensions can then be inserted into one or several of those lists using
- insert_suspension(Vars, Susp, Index)
- Insert the suspension Susp into the Index’th suspension list of all attributed variables occurring in Vars. The current module specifies which of the attributes will be taken.
- insert_suspension(Vars, Susp, Index, Module)
- Similar to the above, but it inserts the suspension into the attribute specified by Module.
For instance,
insert_suspension(Vars, Susp, inst of suspend, suspend)
inserts the suspension into the inst list 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 initialized 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, initialization 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:
- init_suspension_list(+Position, +Attribute)
- Initializes argument Position of Attribute to an empty suspension list.
- merge_suspension_lists(+Pos1, +Attr, +Pos2, +Attr2)
- Appends the first of two suspension lists (argument Pos1 of Attr1) to the end of the second (argument Pos2 of Attr2). NOTE: The append is destructive, i.e., the second list is modified.
- enter_suspension_list(+Pos, +Attr, +Susp)
- Adds the suspension Susp to the suspension list in the argument position Pos of Attr. The suspension list can be pre-existing, or the argument could be uninstantiated, in which case a new suspension list will be created.
- schedule_suspensions(+Position, +Attribute)
- Takes the suspension list on argument position Position within Attribute, and schedule them for execution. As a side effect, the suspension list within Attribute is updated, i.e., suspensions which are no longer useful are removed destructively. See section 18.8.8 for more details on waking.
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 scheduling priority (see figure 18.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 begins.5