abridged/src/supervisor.rs

97 lines
2.5 KiB
Rust
Raw Normal View History

2023-05-24 04:23:36 +00:00
use std::{fmt, error::Error, time::Duration, any::Any, panic::AssertUnwindSafe, collections::HashMap};
2023-05-23 02:49:37 +00:00
use async_trait::async_trait;
2023-05-24 04:23:36 +00:00
use futures::{stream::FuturesUnordered, StreamExt, FutureExt};
use log::{warn, error};
use serde::Deserialize;
2023-05-23 02:49:37 +00:00
2023-05-24 04:23:36 +00:00
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Id(&'static str);
impl Id {
pub fn new(s: String) -> Self {
Self(Box::leak(s.into_boxed_str()))
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2023-05-24 04:23:36 +00:00
f.write_str(self.0)
}
2023-05-24 04:23:36 +00:00
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
pub struct Link(String);
impl <S> From<S> for Link
where S: Into<String> {
fn from(value: S) -> Self {
Self(value.into())
}
2023-05-24 04:23:36 +00:00
}
#[derive(Clone, Debug)]
pub struct Message {
pub origin: (Id, String),
pub link: Link,
pub author: String,
pub content: String,
}
pub type Sender = tokio::sync::broadcast::Sender<Message>;
pub type Receiver = tokio::sync::broadcast::Receiver<Message>;
2023-05-23 02:49:37 +00:00
pub type TaskResult = Result<(), Box<dyn Error>>;
#[async_trait]
pub trait Task {
async fn start(&self, origin: Id, tx: Sender, rc: Receiver) -> TaskResult;
fn restart_timeout(&self) -> Option<Duration> {
Some(Duration::from_secs(15))
}
}
enum ExitStatus {
Success,
Error(Box<dyn Error>),
Panic(Box<dyn Any + 'static>),
}
2023-05-24 04:23:36 +00:00
async fn start_task(id: Id, task: &dyn Task, tx: Sender, timeout: Duration) -> (Id, ExitStatus) {
if !timeout.is_zero() {
warn!("task '{id}': waiting {timeout:?} to start");
tokio::time::sleep(timeout).await;
2023-05-24 04:23:36 +00:00
}
warn!("task '{id}': starting");
2023-05-23 02:49:37 +00:00
let rx = tx.subscribe();
2023-05-24 04:23:36 +00:00
let future = AssertUnwindSafe(task.start(id, tx, rx)).catch_unwind();
2023-05-23 02:49:37 +00:00
let result = match future.await {
Ok(Ok(_)) => ExitStatus::Success,
Ok(Err(e)) => ExitStatus::Error(e),
Err(e) => ExitStatus::Panic(e),
};
(id, result)
}
2023-05-24 04:23:36 +00:00
pub async fn run_tasks(tasks: HashMap<String, Box<dyn Task>>) {
2023-05-23 02:49:37 +00:00
let mut futures = FuturesUnordered::new();
let (tx, _) = tokio::sync::broadcast::channel(64);
2023-05-24 04:23:36 +00:00
for (id, task) in &tasks {
let id = Id::new(id.clone());
2023-05-23 02:49:37 +00:00
futures.push(start_task(id, task.as_ref(), tx.clone(), Duration::ZERO));
}
while let Some((id, result)) = futures.next().await {
2023-05-24 04:23:36 +00:00
let task = &tasks[id.0];
let dur = task.restart_timeout();
2023-05-23 02:49:37 +00:00
match &result {
2023-05-24 04:23:36 +00:00
ExitStatus::Success => warn!("task '{id}' exited without error"),
ExitStatus::Error(e) => warn!("task '{id}': exited with error: {e}"),
ExitStatus::Panic(_) => error!("task '{id}': panicked"),
2023-05-23 02:49:37 +00:00
}
2023-05-24 04:23:36 +00:00
if let Some(dur) = dur {
2023-05-23 02:49:37 +00:00
futures.push(start_task(id, task.as_ref(), tx.clone(), dur));
}
}
}