brokapi_runtime/detached.rs
1//! Run a command to its end even when the caller stops waiting.
2
3use std::future::Future;
4
5/// Runs `work` as a task of its own and waits for its result. If the caller's future is dropped
6/// (a `select!` branch lost, a timeout around the call, a cancelled task), `work` is **not** stopped: it
7/// runs to its end.
8///
9/// A trading command needs this. After the cid is marked and the order is registered as in flight, the
10/// request, the answer and the update of the order must all happen. A command that stopped halfway would
11/// leave the order in flight for ever, or the bot with a placed order it never heard about.
12///
13/// Returns `None` only when the task was torn down with the runtime (it panicked or the runtime is
14/// shutting down). A caller maps that to an unknown outcome.
15///
16/// # Examples
17///
18/// ```
19/// use std::sync::Arc;
20/// use std::sync::atomic::{AtomicBool, Ordering};
21/// use std::time::Duration;
22/// use brokapi_runtime::run_detached;
23///
24/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
25/// # async fn main() {
26/// let done = Arc::new(AtomicBool::new(false));
27/// let flag = Arc::clone(&done);
28/// let work = async move {
29/// tokio::time::sleep(Duration::from_secs(5)).await;
30/// flag.store(true, Ordering::SeqCst);
31/// 7
32/// };
33///
34/// // The caller gives up after one second ...
35/// let waited = tokio::time::timeout(Duration::from_secs(1), run_detached(work)).await;
36/// assert!(waited.is_err());
37/// assert!(!done.load(Ordering::SeqCst));
38///
39/// // ... and the work still finishes.
40/// tokio::time::sleep(Duration::from_secs(10)).await;
41/// assert!(done.load(Ordering::SeqCst));
42///
43/// // Waited for to the end, it returns the value.
44/// assert_eq!(run_detached(async { 1 + 1 }).await, Some(2));
45/// # }
46/// ```
47pub async fn run_detached<T, F>(work: F) -> Option<T>
48where
49 F: Future<Output = T> + Send + 'static,
50 T: Send + 'static,
51{
52 tokio::spawn(work).await.ok()
53}