Skip to main content

Module scenario

Module scenario 

Source
Expand description

Your workflows under the simulator.

A Scenario registers your workflow functions, the starts and signals a client sends, and checks on the results. Each seed runs it on a simulated machine: the real shard runtime and Rust SDK, a seeded scheduler, a disk that tears unflushed writes and fails flushes, process crashes at random moments, delayed messages between shards, and a clock that skips ahead whenever nothing is runnable, so a workflow that sleeps for a month finishes in milliseconds. The seed also picks the shard count, park and checkpoint timing, segment size, log format and payload encryption, so a range of seeds covers configurations as well as interleavings.

After every recovery and at the end the simulator checks what the engine promises: an acknowledged start or signal is never lost, a reported result never changes, no run stops on a determinism error when its journal is replayed (nondeterministic workflow code shows up here), a stream reader (Setup::read_stream) never sees a chunk change or go away, and once faults stop every workflow finishes. Your checks then see the final results and whatever state your step bodies kept.

use fusee::{Ctx, Error};
use fusee_sim::scenario::Scenario;
use std::sync::{Arc, Mutex};
use std::time::Duration;

let scenario = Scenario::new(|sim| {
    // State that outlives crashes, like an external system: the ledger.
    let ledger = Arc::new(Mutex::new(Vec::<(String, u64)>::new()));
    let l = ledger.clone();
    sim.register("pay", move |ctx: Ctx, amount: u64| {
        let l = l.clone();
        async move {
            ctx.sleep("a-day", Duration::from_secs(86_400)).await?;
            ctx.step("charge", || {
                let l = l.clone();
                let key = ctx.key();
                async move {
                    fusee_sim::sleep(Duration::from_millis(3)).await;
                    let mut l = l.lock().unwrap();
                    if !l.iter().any(|(k, _)| *k == key) {
                        l.push((key, amount));
                    }
                    Ok::<_, Error>(amount)
                }
            })
            .await
        }
    });
    for i in 0..20 {
        let at = Duration::from_millis(sim.random(500));
        sim.start(at, &format!("pay-{i}"), "pay", &(i * 10));
    }
    sim.check(move |out| {
        for i in 0..20u64 {
            assert_eq!(out.result::<u64>(&format!("pay-{i}")), Some(Ok(i * 10)));
        }
        assert_eq!(ledger.lock().unwrap().len(), 20);
        Ok(())
    });
});
scenario.run_seeds(0..200).unwrap();

Step bodies run on the simulator’s single thread: wait with crate::sleep, not tokio::time::sleep, and stand in for networks and databases with in-memory state like the ledger above. Workflow code itself needs no change; the same async fn can be registered with [fusee::Engine::workflow] and here.

Structs§

CallReply
The answer to one object call (Setup::call).
Outcome
The final results of one seed.
Scenario
Your workflows and requests, run under seeds. See the module docs.
Setup
What a scenario’s setup function builds for one seed: the workflows, the client’s requests and the checks.
Summary
Totals over the seeds Scenario::run_seeds ran.
UserSetup
A scenario’s setup function, carried by SimConfig::user.

Type Aliases§

Check
A check on the outcome of one seed. An Err (or a panic) fails the seed.