aven-core 0.1.12

Core library for the Aven local-first task manager
use std::sync::{Arc, LazyLock};

use anyhow::{Context, Result};
use tokio::sync::Semaphore;

const MAX_ATTACHMENT_WORKERS: usize = 2;
const MAX_PREVIEW_WORKERS: usize = 2;

static ATTACHMENT_WORKERS: LazyLock<Arc<Semaphore>> =
    LazyLock::new(|| Arc::new(Semaphore::new(MAX_ATTACHMENT_WORKERS)));
static PREVIEW_WORKERS: LazyLock<Arc<Semaphore>> =
    LazyLock::new(|| Arc::new(Semaphore::new(MAX_PREVIEW_WORKERS)));

pub async fn run<F, T>(work: F) -> Result<T>
where
    F: FnOnce() -> Result<T> + Send + 'static,
    T: Send + 'static,
{
    run_with_pool(ATTACHMENT_WORKERS.clone(), work).await
}

pub async fn run_preview<F, T>(work: F) -> Result<T>
where
    F: FnOnce() -> Result<T> + Send + 'static,
    T: Send + 'static,
{
    run_with_pool(PREVIEW_WORKERS.clone(), work).await
}

async fn run_with_pool<F, T>(workers: Arc<Semaphore>, work: F) -> Result<T>
where
    F: FnOnce() -> Result<T> + Send + 'static,
    T: Send + 'static,
{
    let permit = workers
        .acquire_owned()
        .await
        .context("image worker pool closed")?;
    tokio::task::spawn_blocking(move || {
        let _permit = permit;
        work()
    })
    .await
    .context("image worker failed")?
}

#[cfg(test)]
mod tests {
    use std::time::Duration;

    use super::MAX_PREVIEW_WORKERS;

    #[tokio::test]
    async fn preview_capacity_does_not_delay_attachment_work() {
        let mut started = Vec::new();
        let mut previews = Vec::new();
        for _ in 0..MAX_PREVIEW_WORKERS {
            let (started_tx, started_rx) = tokio::sync::oneshot::channel();
            previews.push(tokio::spawn(async move {
                super::run_preview(move || {
                    let _ = started_tx.send(());
                    std::thread::sleep(Duration::from_millis(150));
                    Ok(())
                })
                .await
            }));
            started.push(started_rx);
        }
        for started in started {
            started.await.unwrap();
        }

        tokio::time::timeout(Duration::from_millis(100), super::run(|| Ok(())))
            .await
            .expect("attachment pool should remain available")
            .unwrap();
        for preview in previews {
            preview.await.unwrap().unwrap();
        }
    }
}