Skip to main content

alibi_core/
background_tasks.rs

1//! Application observation of already running background work.
2use crate::AuthResult;
3use std::{future::Future, pin::Pin};
4
5/// Completion of an already started owned task.
6/// Dropping this observation does not cancel its underlying work. Applications
7/// may retain and await it to keep their executor alive until work completes.
8pub type BackgroundTaskCompletion = Pin<Box<dyn Future<Output = AuthResult<()>> + Send + 'static>>;
9
10/// Application integration for deferred operations, such as platform waitUntil.
11///
12/// The callback receives a completion rather than a task that it must start.
13/// A synchronous callback error does not cancel work already started.
14pub trait BackgroundTaskHandler: Send + Sync {
15    ///
16    /// # Errors
17    ///
18    /// Returns an error if the application rejects observation of the background completion.
19    fn handle(&self, completion: BackgroundTaskCompletion) -> AuthResult<()>;
20}
21
22/// Start owned work immediately and retain the initiating request/tracing context.
23/// The returned observation may be dropped without cancelling pending work.
24///
25/// # Errors
26/// Returns an error when pending work cannot be scheduled on a Tokio executor.
27pub async fn start_background_task(
28    operation: impl Future<Output = AuthResult<()>> + Send + 'static,
29) -> AuthResult<BackgroundTaskCompletion> {
30    use tracing::{Instrument, instrument::WithSubscriber};
31    let request_context = crate::hooks::current_request_hook_context();
32    let work = async move {
33        match request_context {
34            Some(context) => {
35                crate::hooks::with_request_hook_context_value(context, operation).await
36            }
37            None => operation.await,
38        }
39    }
40    .instrument(tracing::Span::current())
41    .with_current_subscriber();
42    let mut work: BackgroundTaskCompletion = Box::pin(work);
43    match std::future::poll_fn(|context| std::task::Poll::Ready(work.as_mut().poll(context))).await
44    {
45        std::task::Poll::Ready(result) => Ok(Box::pin(async move { result })),
46        std::task::Poll::Pending => {
47            let executor = tokio::runtime::Handle::try_current().map_err(|error| {
48                crate::AuthError::internal(format!(
49                    "Background task requires a Tokio executor: {error}"
50                ))
51            })?;
52            let running = executor.spawn(work);
53            Ok(Box::pin(async move {
54                running.await.map_err(|error| {
55                    crate::AuthError::internal(format!("Background task failed: {error}"))
56                })?
57            }))
58        }
59    }
60}