1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
use std::{convert::Infallible, time::Duration};

use async_task::Task;
use futures_lite::Future;

/// Immortal represents a task that can never stop, unless it is explicity cancelled or dropped from the outside. We can think of this as a Task<!>, except with some nice convenience methods.
pub struct Immortal(Task<Infallible>);

impl Immortal {
    /// Directly spawns an immortal future.
    pub fn spawn<F: Future<Output = Infallible> + Send + 'static>(f: F) -> Self {
        Self(crate::spawn(f))
    }

    /// Spawns an immortal that runs a piece of code repeatedly, restarting when it returns using a particular restart strategy.
    pub fn respawn<T: Send, F: Future<Output = T> + Send>(
        strategy: RespawnStrategy,
        mut inner: impl FnMut() -> F + Send + 'static,
    ) -> Self {
        let task = crate::spawn(async move {
            loop {
                inner().await;
                match strategy {
                    RespawnStrategy::Immediate => futures_lite::future::yield_now().await,
                    RespawnStrategy::FixedDelay(delay) => {
                        async_io::Timer::after(delay).await;
                    }
                    RespawnStrategy::JitterDelay(low, high) => {
                        let low = low.min(high);
                        let delay = Duration::from_millis(fastrand::u64(
                            (low.as_millis() as u64)..=(high.as_millis() as u64),
                        ));
                        async_io::Timer::after(delay).await;
                    }
                }
            }
        });
        Self(task)
    }

    /// Takes ownership of the immortal and cancels it, waiting only when it has fully stopped running.
    ///
    /// Unless you need to wait until the immortal stops before doing something else, it's easier to just drop this immortal rather than using this method.
    pub async fn cancel(self) {
        self.0.cancel().await;
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RespawnStrategy {
    Immediate,
    FixedDelay(Duration),
    JitterDelay(Duration, Duration),
}