use std::future::{Future, poll_fn};
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
use std::task::Poll;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
use tokio_util::task::{TaskTracker, task_tracker::TaskTrackerToken};
use super::{RunContext, RunIdentity};
use crate::{Error, Result};
#[derive(Debug)]
pub(crate) struct TaskScope {
identity: Option<RunIdentity>,
closed: AtomicBool,
tracker: OnceLock<TaskTracker>,
owner: Option<Handle>,
parent: Option<Arc<TaskScope>>,
}
impl TaskScope {
pub(super) fn new(identity: Option<RunIdentity>) -> Self {
Self {
identity,
closed: AtomicBool::new(false),
tracker: OnceLock::new(),
owner: Handle::try_current().ok(),
parent: None,
}
}
#[cfg(feature = "fleet")]
pub(super) fn execution(parent: &Arc<Self>) -> Self {
let parent = parent.parent.as_ref().unwrap_or(parent).clone();
Self {
identity: parent.identity.clone(),
closed: AtomicBool::new(false),
tracker: OnceLock::new(),
owner: parent.owner.clone(),
parent: Some(parent),
}
}
pub(super) fn owner(&self) -> Result<&Handle> {
self.owner.as_ref().ok_or_else(|| {
Error::msg("tracked task scope has no Tokio execution owner")
})
}
pub(super) fn identity(&self) -> Option<&RunIdentity> {
self.identity.as_ref()
}
fn token(&self) -> Result<TaskTrackerToken> {
if self.closed.load(Ordering::Acquire) {
return Err(Error::msg("tracked task scope is closed"));
}
let token = self.tracker.get_or_init(TaskTracker::new).token();
self.closed
.compare_exchange(false, false, Ordering::AcqRel, Ordering::Acquire)
.map_err(|_| Error::msg("tracked task scope is closed"))?;
Ok(token)
}
pub(super) fn register(&self) -> Result<Registration> {
Ok(Registration {
_local: self.token()?,
_parent: self
.parent
.as_ref()
.map(|parent| parent.token())
.transpose()?,
})
}
pub(super) fn spawn<F>(
&self,
future: F,
context: RunContext,
) -> JoinHandle<Result<()>>
where
F: Future<Output = Result<()>> + Send + 'static,
{
let tracked = self.tracker.get().unwrap().track_future(future);
match &self.parent {
Some(parent) => self.execute(
parent.tracker.get().unwrap().track_future(tracked),
context,
),
None => self.execute(tracked, context),
}
}
fn execute<F>(
&self,
tracked: F,
context: RunContext,
) -> JoinHandle<Result<()>>
where
F: Future<Output = Result<()>> + Send + 'static,
{
let owner = self.owner.as_ref().unwrap();
match context.execution_executor().cloned() {
Some(executor) => {
let execution =
executor.execute_child(Box::pin(tracked), context);
let completion =
self.tracker.get().unwrap().track_future(execution);
match &self.parent {
Some(parent) => owner.spawn(
parent.tracker.get().unwrap().track_future(completion),
),
None => owner.spawn(completion),
}
}
None => owner.spawn(tracked),
}
}
pub(crate) fn close(&self) {
self.closed.swap(true, Ordering::AcqRel);
if let Some(tracker) = self.tracker.get() {
tracker.close();
}
}
pub(crate) async fn wait(&self) {
self.close();
if let Some(tracker) = self.tracker.get() {
tracker.wait().await;
}
}
}
pub(super) struct Registration {
_local: TaskTrackerToken,
_parent: Option<TaskTrackerToken>,
}
struct Owner(RunContext);
impl Owner {
fn close(&self) {
self.0.tasks.close();
self.0.cancellation.cancel();
}
}
impl Drop for Owner {
fn drop(&mut self) {
self.close();
}
}
pub(crate) async fn run_owned<F>(
context: RunContext,
future: F,
) -> Result<()>
where
F: Future<Output = Result<()>>,
{
let owner = Owner(context);
let outcome = {
let future = future;
tokio::pin!(future);
poll_fn(|cx| {
match catch_unwind(AssertUnwindSafe(|| future.as_mut().poll(cx))) {
Ok(Poll::Pending) => Poll::Pending,
Ok(Poll::Ready(result)) => Poll::Ready(Ok(result)),
Err(panic) => Poll::Ready(Err(panic)),
}
})
.await
};
owner.close();
owner.0.tasks.wait().await;
match outcome {
Ok(result) => result,
Err(panic) => resume_unwind(panic),
}
}
#[cfg(test)]
#[path = "../../tests/service/tracking.rs"]
mod tests;