geph5-client 0.2.19

Geph5 client
Documentation
use std::{
    future::Future,
    sync::Arc,
    time::{Duration, SystemTime},
};

use futures_util::future::BoxFuture;

use smol::future::FutureExt;
use smolscale::immortal::Immortal;

pub struct RefreshCell<T: Clone> {
    inner: Arc<smol::lock::Mutex<(T, SystemTime)>>,
    _task: Immortal,
    interval: Duration,
    refresh: Arc<dyn Fn() -> BoxFuture<'static, T> + Send + Sync + 'static>,
}

impl<T: Clone + Send + 'static> RefreshCell<T> {
    pub async fn create<Fut: Future<Output = T> + Send + 'static>(
        interval: Duration,
        refresh: impl Fn() -> Fut + Send + Sync + 'static,
    ) -> Self {
        let inner = Arc::new(smol::lock::Mutex::new((refresh().await, SystemTime::now())));
        let inner2 = inner.clone();
        let refresh = Arc::new(move || refresh().boxed());
        let task = {
            let refresh = refresh.clone();
            Immortal::spawn(async move {
                let mut heartbeat = smol::Timer::interval(interval);
                (&mut heartbeat).await;
                loop {
                    let refresh = refresh.clone();
                    let fresh = async {
                        let new_value = refresh().await;
                        tracing::debug!(
                            interval = debug(interval),
                            "RefreshCell refreshed properly"
                        );
                        let mut inner = inner2.lock().await;
                        *inner = (new_value, SystemTime::now());
                        true
                    };
                    let timeout = async {
                        (&mut heartbeat).await;
                        tracing::debug!(
                            interval = debug(interval),
                            "RefreshCell timed out this time"
                        );
                        false
                    };
                    let refreshed = fresh.race(timeout).await;
                    if refreshed {
                        (&mut heartbeat).await;
                    }
                }
            })
        };
        Self {
            inner,
            _task: task,
            interval,
            refresh,
        }
    }

    pub async fn get(&self) -> T {
        let mut inner = self.inner.lock().await;
        if let Ok(old) = inner.1.elapsed() {
            if old > self.interval + Duration::from_secs(30) {
                tracing::warn!(
                    interval = debug(self.interval),
                    "forcing refresh of RefreshCell due to out of date"
                );
                let val = (self.refresh)().await;
                *inner = (val.clone(), SystemTime::now());
                return val;
            }
        }
        inner.0.clone()
    }
}