use anyhow::Context;
use std::{convert::Infallible, io, num::NonZeroU32, time::Duration};
use tellus::{
Actor, ActorConfig, ActorContext, ActorSystem, Backoff, Control, Incoming, Nothing,
RestartPolicy, SupervisionStrategy,
};
use thiserror::Error;
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
const TOXIC: u32 = 13;
const MAX_RESTARTS: NonZeroU32 = NonZeroU32::new(3).expect("3 is not zero");
#[tokio::main]
async fn main() -> anyhow::Result<()> {
init_tracing();
let backoff = Backoff::new(Duration::from_millis(50), Duration::from_secs(1))
.context("backoff bounds for the loader")?;
let loader_config = ActorConfig::default().with_supervision_strategy(
SupervisionStrategy::Restart(RestartPolicy::new(MAX_RESTARTS).with_backoff(backoff)),
);
let system = ActorSystem::new(Overseer { loader_config });
system
.terminated()
.await
.context("awaiting actor system termination")
}
fn init_tracing() {
tracing_subscriber::registry()
.with(EnvFilter::from_default_env())
.with(
tracing_subscriber::fmt::layer()
.json()
.flatten_event(true)
.with_writer(io::stderr),
)
.init();
}
struct Overseer {
loader_config: ActorConfig,
}
impl Actor for Overseer {
type Message = Nothing;
type State = ();
type Error = Infallible;
fn init(&self, context: &ActorContext<Self::Message>) -> Result<Self::State, Self::Error> {
let loader = context.spawn_with_config(Loader, self.loader_config);
context.watch(&loader);
for value in [1, 2, TOXIC, 3, TOXIC, 4] {
loader.tell(Message::Load(value));
}
loader.tell(Message::Finish);
Ok(())
}
fn receive(
&self,
_: &ActorContext<Self::Message>,
_: Incoming<Self::Message>,
_: Self::State,
) -> Result<Control<Self::State>, Self::Error> {
println!("## Loader finished");
Ok(Control::Stop)
}
}
struct Loader;
impl Actor for Loader {
type Message = Message;
type State = u64;
type Error = ToxicValue;
fn init(&self, _: &ActorContext<Self::Message>) -> Result<Self::State, Self::Error> {
println!("## Loader initialized, count starts over at 0");
Ok(0)
}
fn receive(
&self,
_: &ActorContext<Self::Message>,
incoming: Incoming<Self::Message>,
count: Self::State,
) -> Result<Control<Self::State>, Self::Error> {
let Incoming::Message(message) = incoming else {
unreachable!("the loader watches no actor, hence never gets a terminated signal")
};
match message {
Message::Load(TOXIC) => Err(ToxicValue(TOXIC)),
Message::Load(value) => {
let count = count + 1;
println!("## Loaded {value}, count is {count}");
Ok(Control::Continue(count))
}
Message::Finish => Ok(Control::Stop),
}
}
}
enum Message {
Load(u32),
Finish,
}
#[derive(Debug, Error)]
#[error("toxic value {0}")]
struct ToxicValue(u32);