Skip to main content

boson_runtime/worker/
manual.rs

1//! Manual single-step worker for tests (no background task).
2
3use std::sync::Arc;
4
5use boson_core::ExecutionContextFactory;
6use boson_core::QueueBackend;
7use tokio::sync::Mutex;
8
9use super::claim::claim_next_job;
10use super::config::WorkerSettings;
11use super::loop_::WorkerEngine;
12use crate::registry::TaskRegistry;
13
14/// Manual single-step worker for tests (no background task).
15///
16/// Use [`BosonBuilder::build_manual`](crate::BosonBuilder::build_manual) to obtain one alongside
17/// [`Boson`](crate::Boson). Call [`try_run_next`](Self::try_run_next) to claim and execute at most
18/// one queued job — useful in unit tests and the [`task_macro` example](https://github.com/unified-field-dev/boson/blob/main/boson/examples/task_macro.rs).
19///
20/// # Example
21///
22/// ```rust,no_run
23/// use std::sync::Arc;
24///
25/// use boson_backend_mem::MemQueueBackend;
26/// use boson_core::{ExecutionContext, JsonExecutionContextFactory};
27/// use boson_macros::task;
28/// use boson_runtime::{configure, Boson, ManualWorker};
29///
30/// #[task(name = "ping")]
31/// async fn ping(_ctx: Box<dyn ExecutionContext>) -> boson_core::Result<()> {
32///     Ok(())
33/// }
34///
35/// # async fn run() -> boson_core::Result<()> {
36/// let (boson, manual) = Boson::builder()
37///     .queue_backend(Arc::new(MemQueueBackend::new()))
38///     .execution_context_factory(JsonExecutionContextFactory)
39///     .auto_registry()
40///     .build_manual()?;
41/// configure(boson);
42///
43/// Ping::send_with(serde_json::json!({"System": {}}), PingParams {}).await?;
44/// assert!(manual.try_run_next().await); // runs the handler once
45/// # Ok(())
46/// # }
47/// ```
48pub struct ManualWorker {
49    inner: Arc<WorkerEngine>,
50    lock: Mutex<()>,
51}
52
53impl ManualWorker {
54    /// Create a worker that can be driven step-by-step in tests.
55    pub fn new(
56        backend: Arc<dyn QueueBackend>,
57        registry: Arc<TaskRegistry>,
58        identity: Arc<dyn ExecutionContextFactory>,
59        worker: WorkerSettings,
60    ) -> Self {
61        Self {
62            inner: Arc::new(WorkerEngine {
63                backend,
64                registry,
65                identity,
66                worker,
67            }),
68            lock: Mutex::new(()),
69        }
70    }
71
72    /// Process at most one job across all pools.
73    pub async fn try_run_next(&self) -> bool {
74        let _guard = self.lock.lock().await;
75        let discovered = self
76            .inner
77            .backend
78            .distinct_pools_queued()
79            .await
80            .unwrap_or_default();
81        let pools = self.inner.worker.pools_to_poll(discovered);
82        for pool in pools {
83            if let Ok(Some((job, lease_id))) = claim_next_job(
84                &self.inner.backend,
85                &pool,
86                &self.inner.worker.worker_id,
87                self.inner.worker.lease_ttl_secs,
88            )
89            .await
90            {
91                self.inner.drive_run(job, lease_id).await;
92                return true;
93            }
94        }
95        false
96    }
97}