use speare::{Actor, Backoff, Ctx, Limit, Node, Supervision};
use std::time::Duration;
use tokio::task;
use tokio::time::Instant;
mod sync_vec;
use sync_vec::SyncVec;
struct Failer;
impl Actor for Failer {
type Props = SyncVec<Instant>;
type Msg = ();
type Err = ();
async fn init(ctx: &mut Ctx<Self>) -> Result<Self, Self::Err> {
ctx.props().push(Instant::now()).await;
Err(())
}
}
#[tokio::test(start_paused = true)]
async fn static_backoff_delays_each_restart_by_fixed_duration() {
let mut node = Node::default();
let timestamps: SyncVec<Instant> = Default::default();
let backoff_dur = Duration::from_secs(1);
node.actor::<Failer>(timestamps.clone())
.supervision(Supervision::Restart {
max: Limit::Amount(4),
backoff: Backoff::Static(backoff_dur),
})
.spawn();
tokio::time::sleep(Duration::from_secs(5)).await;
task::yield_now().await;
let ts = timestamps.clone_vec().await;
assert_eq!(ts.len(), 4);
for i in 1..ts.len() {
let gap = ts[i] - ts[i - 1];
assert_eq!(gap, backoff_dur);
}
}
#[tokio::test(start_paused = true)]
async fn incremental_backoff_increases_delay_linearly() {
let mut node = Node::default();
let timestamps: SyncVec<Instant> = Default::default();
let step = Duration::from_millis(100);
let min = Duration::from_millis(50);
let max = Duration::from_secs(1);
node.actor::<Failer>(timestamps.clone())
.supervision(Supervision::Restart {
max: Limit::Amount(5),
backoff: Backoff::Incremental { min, max, step },
})
.spawn();
tokio::time::sleep(Duration::from_secs(5)).await;
task::yield_now().await;
let ts = timestamps.clone_vec().await;
assert_eq!(ts.len(), 5);
let expected_gaps = [
Duration::from_millis(100),
Duration::from_millis(200),
Duration::from_millis(300),
Duration::from_millis(400),
];
for (i, expected) in expected_gaps.iter().enumerate() {
let gap = ts[i + 1] - ts[i];
assert_eq!(gap, *expected, "gap {i} mismatch");
}
}
#[tokio::test(start_paused = true)]
async fn incremental_backoff_clamps_to_max() {
let mut node = Node::default();
let timestamps: SyncVec<Instant> = Default::default();
let step = Duration::from_millis(500);
let min = Duration::from_millis(100);
let max = Duration::from_millis(800);
node.actor::<Failer>(timestamps.clone())
.supervision(Supervision::Restart {
max: Limit::Amount(5),
backoff: Backoff::Incremental { min, max, step },
})
.spawn();
tokio::time::sleep(Duration::from_secs(10)).await;
task::yield_now().await;
let ts = timestamps.clone_vec().await;
assert_eq!(ts.len(), 5);
let expected_gaps = [
Duration::from_millis(500),
Duration::from_millis(800),
Duration::from_millis(800),
Duration::from_millis(800),
];
for (i, expected) in expected_gaps.iter().enumerate() {
let gap = ts[i + 1] - ts[i];
assert_eq!(gap, *expected, "gap {i} mismatch");
}
}
#[tokio::test(start_paused = true)]
async fn no_backoff_restarts_immediately() {
let mut node = Node::default();
let timestamps: SyncVec<Instant> = Default::default();
node.actor::<Failer>(timestamps.clone())
.supervision(Supervision::Restart {
max: Limit::Amount(3),
backoff: Backoff::None,
})
.spawn();
task::yield_now().await;
task::yield_now().await;
task::yield_now().await;
let ts = timestamps.clone_vec().await;
assert_eq!(ts.len(), 3);
for i in 1..ts.len() {
let gap = ts[i] - ts[i - 1];
assert_eq!(gap, Duration::ZERO);
}
}