Cmd Execution
When we run a CmdExecution
, possible outcomes are:
- In a
CmdBlock
, failure within one or more items. - In a
CmdBlock
, failure within block code. - Interruption / partial execution.
- Successful execution of all blocks.
Each CmdBlock
has its own block outcome type, and its own .
Framework
Use Cases: Framework Implementor
*Cmd
Impls
- It would be nice to not have to write interruption handling code in every
CmdBlock
. - Also, having
CmdBlock
s abstracted allows timings to be collected per block in a common place.
What does a CmdExecution
implementor want to do?
-
DiffCmd
:StateDiscoveryBlock
: Return item failures to caller.StateDiscoveryBlock
: Return block failure / interruption to caller.DiffBlock
: Return item failures to caller.DiffBlock
: Return block failure / interruption to caller.
-
EnsureCmd
:-
StateDiscoveryBlock
: Return item failures to caller. -
StateDiscoveryBlock
: Return block failure / interruption to caller. -
DiffBlock
: Return item failures to caller. -
DiffBlock
: Return block failure / interruption to caller. -
EnsureBlock
: SerializeStates
on item failure, returnStates
and item failures to caller. -
EnsureBlock
: Return block failure / interruption to caller. -
CleanBlock
: SerializeStates
on item failure, returnStates
and item failures to caller. -
CleanBlock
: Return block failure / interruption to caller. -
EnsureBlock
: ditto. -
CleanBlock
: ditto. The desired return type would be: -
StatesEnsured
on success.
-
-
How should we reply to the caller?
CmdBlock
block level error.IndexMap<ItemId, E>
item level error. Error handlers perCmdBlock
will do any serialization work if desired.
Use Cases: Automation Developer / End User
- Store, retrieve, and display execution report.
How do we do this in code?
What it could look like conceptually:
DiffCmd
#![allow(unused)] fn main() { let cmd_execution = CmdExecution::builder() .with_block(StatesDiscoverCmdBlock::<CurrentAndGoal>::new()) .with_block(DiffCmdBlock::new()) .build() .await; }
EnsureCmd
-- "simple" version
#![allow(unused)] fn main() { let cmd_execution = CmdExecution::builder() .with_block(StatesCurrentReadCmdBlock::new()) .with_block(StatesDiscoverCmdBlock::new(Discover::Current)) // Compares current with stored, and fails if // current stored doesn't match current discovered. .with_block(ApplyGuardCmdBlock::new()) // `Outcome` and `OutcomeAcc` are `()` .with_block(ApplyCmdBlock::new()) // state_goal, diff and apply. .build() .await; }
EnsureCmd
-- "complex" version
#![allow(unused)] fn main() { let cmd_execution = CmdExecution::builder() .with_block(StatesCurrentReadCmdBlock::new()) .with_block(StatesDiscoverCmdBlock::new(Discover::Current)) .with_block(ApplyGuardCmdBlock::new()) .with_block(EnsureCmdBlock::new(item_ids_no_blockage)) // state_goal, diff and apply. .with_block(CleanCmdBlock::new(item_ids_blocking)) // state_clean, diff and apply. .with_block(EnsureCmdBlock::new(item_ids_unblocked)) // state_goal, diff and apply. .with_block(CleanCmdBlock::new(item_ids_obsolete)) // state_clean, diff and apply. .build() .await; // `item_ids_unblocked` is a superset of the `item_ids_blocking`. // // ⚠️ We could have a more granular design version that starts ensuring unblocked items // while `cleaning` is happening. }
Error Handling and Interruptions
When a CmdBlock
fails, we need to consider what to do with:
CmdBlock::OutcomeAcc
.- item errors.
CmdBlock
error.- interruptions.
Originally an additional closure argument next to each CmdBlock
argument was considered:
|_input, _outcome_acc, cmd_block_error| async move { cmd_block_error }
For example:
|(_states_current, _states_goal), states_ensured_mut, apply_error| async move {
let serialize_result = StatesSerializer::serialize(states_ensured_mut).await;
apply_error.or(serialize_result)
},
However, most usages would just pass through the error.
Before this CmdExecution
design, ApplyCmd
would:
-
In the producer, send the intermediate result to the outcome collator and stop execution (apply_cmd.rs#L699-L710).
-
In the collator:
- Intermediate
CmdBlock
errors are returned as is. (apply_cmd.rs#L521-L525). - Intermediate discovery errors are collated into the
CmdExecution
outcome type (apply_cmd.rs#L526-L558).
- Intermediate
From the use cases, what we care about are:
- Returning
CmdBlock
errors to the caller. - Returning item errors to the caller.
- Collating intermediate
CmdBlock
item values into theCmdExecution
outcome type if possible. - Serializing
States
to storage:EnsureCmdBlock
andCleanCmdBlock
should still serialize each item's current state, on failure / interruption / success. - Serializing the
CmdBlock::Outcome
in the execution outcome report.
Deferred:
- Serializing the
CmdBlock::OutcomeAcc
in the execution outcome report. - Deserializing the
CmdBlock::OutcomeAcc
to be displayed.
Interruptions
- Interrupt before execution.
- Interrupt within a block.
- Interrupt between blocks.
- Interrupt after last block.