mod common;
use std::{
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
time::Duration,
};
use common::RunningWorker;
use serde::{Deserialize, Serialize};
use steda::{Error, Queue, Result, RetryStrategy, Step, Task, TaskContext, TaskRef};
#[derive(Debug, Deserialize, Serialize)]
struct EmailReceiptInput {
order_id: String,
address: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct EmailReceiptOutput {
message_id: String,
}
const EMAIL_RECEIPT: Task<EmailReceiptInput, EmailReceiptOutput> = Task::new("email-receipt");
#[derive(Debug, Deserialize, Serialize)]
struct CompleteOrderInput {
order_id: String,
email: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct CompleteOrderOutput {
order_id: String,
receipt_message_id: String,
}
const COMPLETE_ORDER: Task<CompleteOrderInput, CompleteOrderOutput> = Task::new("complete-order");
const SPAWN_RECEIPT: Step<TaskRef<EmailReceiptInput, EmailReceiptOutput>> =
Step::new("spawn-receipt");
async fn complete_order(
input: CompleteOrderInput,
ctx: TaskContext,
child_queue: Queue,
) -> Result<CompleteOrderOutput> {
let order_id = input.order_id.clone();
let email_address = input.email.clone();
let receipt_queue = child_queue.clone();
let child_order_id = order_id.clone();
let child_idempotency_key = format!("receipt:{}", ctx.task_id());
let child = ctx
.step(SPAWN_RECEIPT, async move || {
let child = receipt_queue
.spawn(
EMAIL_RECEIPT,
EmailReceiptInput { order_id: child_order_id.clone(), address: email_address },
)
.idempotency_key(child_idempotency_key)
.await?;
Ok(child.task_ref())
})
.await?;
if ctx.attempt() == 1 {
println!("parent attempt 1 checkpointed the child task, then simulated a restart");
return Err(Error::Other("order worker restarted after spawning receipt".to_owned()));
}
println!("parent attempt 2 reused the checkpointed child task");
let receipt = ctx.await_task(&child).timeout(Duration::from_secs(10)).await?;
Ok(CompleteOrderOutput { order_id: input.order_id, receipt_message_id: receipt.message_id })
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let steda = common::connect().await?;
let orders = steda.queue("example-orders")?;
let email = steda.queue("example-email")?;
orders.create().await?;
email.create().await?;
let receipt_runs = Arc::new(AtomicUsize::new(0));
let worker_receipt_runs = Arc::clone(&receipt_runs);
let email_worker = email
.worker()
.task(EMAIL_RECEIPT, move |input: EmailReceiptInput, _ctx: TaskContext| {
let worker_receipt_runs = Arc::clone(&worker_receipt_runs);
async move {
worker_receipt_runs.fetch_add(1, Ordering::SeqCst);
println!("sending one receipt for {} to {}", input.order_id, input.address);
Ok(EmailReceiptOutput { message_id: "MSG-1001".to_owned() })
}
})
.build()?;
let email_worker = RunningWorker::start(email_worker);
let child_queue = email.clone();
let orders_worker = orders
.worker()
.task(COMPLETE_ORDER, move |input, ctx| complete_order(input, ctx, child_queue.clone()))
.build()?;
let orders_worker = RunningWorker::start(orders_worker);
let task = orders
.spawn(
COMPLETE_ORDER,
CompleteOrderInput {
order_id: "ORD-1001".to_owned(),
email: "buyer@example.invalid".to_owned(),
},
)
.max_attempts(2)
.retry_strategy(RetryStrategy::fixed(Duration::from_millis(250)))
.await?;
let completed = task.result_with_timeout(Duration::from_secs(15)).await?;
assert_eq!(receipt_runs.load(Ordering::SeqCst), 1);
println!("order {} completed", completed.order_id);
println!("receipt: {}", completed.receipt_message_id);
println!("receipt task executions: {}", receipt_runs.load(Ordering::SeqCst));
orders_worker.stop().await?;
email_worker.stop().await?;
Ok(())
}