Lesson 7: Deferring Termination¶
Coco supports automatic termination of systems. In the generated code, each component stores the number of clients
connected to any of its provided ports. When this reaches zero, a call on unused()
is initiated on the component
resulting in a call on setNextState(Terminated)
, thereby automatically shutting down the component. Termination cascades
through the system from top to bottom due to the following actions performed upon entry to the
Terminated state:
Unsubscribe
from all of its required ports;- Unregister from all of its required ports, which means that they will recursively terminate.
- Call
purge
to delete all of the signals from the its queue.
By default, a component state machine can be called to terminate at any time when it is
idle, which results in the component immediately transitioning to the
Terminated state. This is equivalent to explicitly declaring an
unused transition
that results in the execution of setNextState(Terminated)
at the top level of state
Main. See unused transitions
, and
automatic termination for further details.
In some cases, it may not be desirable to allow the component to immediately terminate in every state, but instead
specify that a component can defer termination after it has completed a series of actions,
for example to ensure that the overall system is in a safe state before it is shutdown. This can be achieved by
explicitly declaring unused()
transitions in a component state machine, which specifies the conditions under which the
state machine eventually terminates.
Note
When an unused()
transition has fired, the component will not receive any further function calls on its provided
ports, reflecting the fact that it no longer has any clients. When unused()
transitions are explicitly declared in
a state machine, then the state machine must eventually transition the Terminated
state. The verification checks that
this is indeed the case, and gives an error otherwise.
The objective of this lesson is to explore Coco’s automatic termination, and how termination can be deferred by
explicitly declaring unused()
transitions in a component state machine. We will use the ValveControllerImpl
component
for this, which currently allows termination to be called in all states, and extend it such that if the valve is moving,
then termination will be deferred until it has finished moving.
Step 1: Declaring an unused()
transition¶
The first step is to identify the states in the ValveControllerImpl
component where the valve is moving, and therefore
any calls to terminate must be deferred until it has finished. There is one such state called Moving
, which is
currently declared as follows:
state Moving {
driver.endOfMovement(result : ValveDriver.Status) = {
setNextState(match (result) {
.Opened => Ready.Opened,
.Closed => Ready.Closed,
.Error => Ready.Unknown,
.Unknown => Ready.Unknown,
.Moving => abort(),
});
client.moveEnded(result);
}
}
Extend this Moving
state with a local boolean variable called shutdown
, and an unused()
transition, as follows:
state Moving {
var shutdown : Bool
unused() = {
shutdown = true;
}
driver.endOfMovement(result : ValveDriver.Status) = {
setNextState(match (result) {
.Opened => Ready.Opened,
.Closed => Ready.Closed,
.Error => Ready.Unknown,
.Unknown => Ready.Unknown,
.Moving => abort(),
});
client.moveEnded(result);
}
}
The variable shutdown
captures whether the component has been called to terminate in state Moving
. Therefore,
when the unused
transition is executed, shutdown
is assigned the value true
to reflect the fact that the component
has been called to terminate.
The Coco for this component is in Example_07B/src/ValveControllerImpl.coco
.
Verifying your Changes (Example_07B)¶
The responsive assertion for ValveControllerImpl
fails the verification with the following counterexample:
Analysis¶
The counterexample above illustrates how ValveControllerImpl
reaches a state where it deadlocks, and is not able
to eventually terminate as required following a call on unused()
.
Following a successful setup, and a call on open()
, ValveControllerImpl
is in the state Moving
. In this state,
unused()
is called to terminate the component, which results in shutdown
being assigned the value true
as expected.
However, when it receives the endOfMovement(.Error)
signal from its required port driver
indicating that the move request failed, ValveControllerImpl
sends the corresponding moveEnded(.Error)
to its client,
and transitions to the state Unknown
. In this state, it will not receive any further function calls on its provided
ports, reflecting the fact that it no longer has any clients, and it is not able to transition to the Terminated
state
as required.
Note
The Coco Platform will present the shortest counterexample that leads to an error. When there are several errors with
the same path length, the counterexample displayed may vary between releases. In this example, the counterexample
displayed may vary between calling open()
or close()
on the ValveControllerImpl
component, or between the
move request succeeding or failing on its required port driver
. All of these paths lead to the same error described
here, and is fixed by the solution we present below.
To fix this, we need to extend ValveControllerImpl
so that it transitions directly to the Terminated
state from Moving
when it receives the signal confirming that the move has finished, and has been called to terminate.
Step 2: Extending ValveControllerImpl
to terminate¶
To ensure that the ValveControllerImpl
component eventually terminates when unused()
is called while the valve is
moving, extend the Move
state declaration as follows:
state Moving {
var shutdown : Bool
unused() = {
shutdown = true;
}
driver.endOfMovement(result : ValveDriver.Status) = if (shutdown) {
setNextState(Terminated)
} else {
setNextState(match (result) {
.Opened => Ready.Opened,
.Closed => Ready.Closed,
.Error => Ready.Unknown,
.Unknown => Ready.Unknown,
.Moving => abort(),
});
client.moveEnded(result);
}
}
When ValveControllerImpl
receives a driver.endOfMovement(…)
signal to indicate the completion of the move,
if shutdown
is true
, then the state machine will transition to Terminated
, otherwise it will transition to
the corresponding state as previously defined.
The Coco for this component is in Example_08A/src/ValveControllerImpl.coco
.
Verifying your changes (Example_08A)¶
All ports and components verify correctly.