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    /// # Errors
16    ///
17    /// Returns an error if the application rejects observation of the background completion.
18    fn handle(&self, completion: BackgroundTaskCompletion) -> AuthResult<()>;
19}
20
21/// Start owned work immediately and retain the initiating request/tracing context.
22/// The returned observation may be dropped without cancelling pending work.
23///
24/// # Errors
25/// Returns an error when pending work cannot be scheduled on a Tokio executor.
26pub async fn start_background_task(
27    operation: impl Future<Output = AuthResult<()>> + Send + 'static,
28) -> AuthResult<BackgroundTaskCompletion> {
29    use tracing::{Instrument, instrument::WithSubscriber};
30    let request_context = crate::hooks::current_request_hook_context();
31    let work = crate::hooks::with_optional_request_hook_context(request_context, operation)
32        .instrument(tracing::Span::current())
33        .with_current_subscriber();
34    let mut work: BackgroundTaskCompletion = Box::pin(work);
35    match std::future::poll_fn(|context| std::task::Poll::Ready(work.as_mut().poll(context))).await
36    {
37        std::task::Poll::Ready(result) => Ok(Box::pin(async move { result })),
38        std::task::Poll::Pending => {
39            let executor = tokio::runtime::Handle::try_current().map_err(|error| {
40                crate::AuthError::internal(format!(
41                    "Background task requires a Tokio executor: {error}"
42                ))
43            })?;
44            let running = executor.spawn(work);
45            Ok(Box::pin(async move {
46                running.await.map_err(|error| {
47                    crate::AuthError::internal(format!("Background task failed: {error}"))
48                })?
49            }))
50        }
51    }
52}