Cmd Execution

When we run a CmdExecution, possible outcomes are:

  1. In a CmdBlock, failure within one or more items.
  2. In a CmdBlock, failure within block code.
  3. Interruption / partial execution.
  4. 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 CmdBlocks 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: Serialize States on item failure, return States and item failures to caller.

    • EnsureBlock: Return block failure / interruption to caller.

    • CleanBlock: Serialize States on item failure, return States 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 per CmdBlock 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:

  1. In the producer, send the intermediate result to the outcome collator and stop execution (apply_cmd.rs#L699-L710).

  2. In the collator:

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 the CmdExecution outcome type if possible.
  • Serializing States to storage: EnsureCmdBlock and CleanCmdBlock 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

  1. Interrupt before execution.
  2. Interrupt within a block.
  3. Interrupt between blocks.
  4. Interrupt after last block.