use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use cano::CancellationToken;
use cano::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Step {
Reserve,
Charge,
Ship,
Done,
}
struct Reserve;
struct Charge;
#[saga::task(state = Step)]
impl Reserve {
type Output = u32;
async fn run(&self, _res: &Resources) -> Result<(TaskResult<Step>, u32), CanoError> {
let ticket = 42;
println!("reserve : holding inventory (ticket #{ticket})");
Ok((TaskResult::Single(Step::Charge), ticket))
}
async fn compensate(&self, _res: &Resources, ticket: u32) -> Result<(), CanoError> {
println!("reserve : releasing ticket #{ticket} (rollback)");
Ok(())
}
}
#[saga::task(state = Step)]
impl Charge {
type Output = String;
async fn run(&self, _res: &Resources) -> Result<(TaskResult<Step>, String), CanoError> {
let auth = "auth-XYZ".to_string();
println!("charge : capturing $42.00 (auth {auth})");
Ok((TaskResult::Single(Step::Ship), auth))
}
async fn compensate(&self, _res: &Resources, auth: String) -> Result<(), CanoError> {
println!("charge : refunding auth {auth} (rollback)");
Ok(())
}
}
struct Ship {
started: Arc<AtomicBool>,
}
#[task(state = Step)]
impl Ship {
fn config(&self) -> TaskConfig {
TaskConfig::minimal()
}
async fn run_bare(&self) -> Result<TaskResult<Step>, CanoError> {
println!("ship : dispatching shipment… (a sibling task will cancel this)");
self.started.store(true, Ordering::SeqCst);
tokio::time::sleep(Duration::from_secs(2)).await;
println!("ship : this line should never print");
Ok(TaskResult::Single(Step::Done))
}
}
#[tokio::main]
async fn main() {
let ship_started = Arc::new(AtomicBool::new(false));
let workflow = Workflow::bare()
.register_with_compensation(Step::Reserve, Reserve)
.register_with_compensation(Step::Charge, Charge)
.register(
Step::Ship,
Ship {
started: ship_started.clone(),
},
)
.add_exit_state(Step::Done);
let (handle, token) = CancellationToken::new();
let canceller = tokio::spawn(async move {
while !ship_started.load(Ordering::SeqCst) {
tokio::time::sleep(Duration::from_millis(5)).await;
}
handle.cancel();
});
match workflow.orchestrate(Step::Reserve, token).await {
Ok(state) => println!("\nworkflow completed at {state:?}"),
Err(error) => println!("\nworkflow cancelled, rolled back: {error}"),
}
canceller.await.expect("canceller task panicked");
}