From b0aae4f1fa4054297d57e1e65d738f0bc75ec870 Mon Sep 17 00:00:00 2001 From: Luna Yao <40349250+ZnqbuZ@users.noreply.github.com> Date: Sat, 18 Apr 2026 11:48:11 +0200 Subject: [PATCH] replace AsyncRuntime with simpler CancellableTask add docstring for AsyncRuntime task task --- easytier/Cargo.toml | 2 +- easytier/src/utils/task.rs | 179 +++++++++++-------------------------- 2 files changed, 55 insertions(+), 126 deletions(-) diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index eae1517c..61c14abb 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -62,7 +62,7 @@ futures = { version = "0.3", features = ["bilock", "unstable"] } tokio = { version = "1", features = ["full"] } tokio-stream = "0.1" -tokio-util = { version = "0.7.9", features = ["codec", "net", "io"] } +tokio-util = { version = "0.7.9", features = ["codec", "net", "io", "rt"] } async-stream = "0.3.5" async-trait = "0.1.74" diff --git a/easytier/src/utils/task.rs b/easytier/src/utils/task.rs index cf436373..3c4828b9 100644 --- a/easytier/src/utils/task.rs +++ b/easytier/src/utils/task.rs @@ -1,145 +1,74 @@ -use crate::common::scoped_task::ScopedTask; -use derivative::Derivative; -use derive_more::{Deref, DerefMut}; -use parking_lot::Mutex; use std::future::Future; -use std::mem::take; -use std::sync::Arc; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; -use tokio::sync::Notify; -use tokio::task::{AbortHandle, JoinError}; +use tokio::task::JoinError; use tokio_util::sync::CancellationToken; +use tokio_util::task::AbortOnDropHandle; -#[derive(Derivative, Debug)] -#[derivative(Default(bound = ""))] -enum AsyncRuntimeState { - #[derivative(Default)] - Idle, - Running { - id: tokio::task::Id, - task: ScopedTask, - token: CancellationToken, - }, - Stopping(AbortHandle), +#[derive(Debug)] +pub struct CancellableTask { + handle: AbortOnDropHandle, + token: CancellationToken, } -#[derive(Derivative, Debug)] -#[derivative(Default(bound = ""))] -pub struct AsyncRuntimeInner { - state: Mutex>, - idle: Notify, -} - -#[derive(Derivative, Deref, DerefMut)] -#[derivative(Debug = "transparent", Default(bound = ""), Clone(bound = ""))] -pub struct AsyncRuntime(Arc>); - -impl AsyncRuntime { - pub fn token(&self) -> Option { - if let AsyncRuntimeState::Running { token, .. } = &*self.state.lock() { - Some(token.clone()) - } else { - None - } +impl CancellableTask { + pub fn token(&self) -> &CancellationToken { + &self.token } - pub fn start(&self, token: Option, factory: F) -> anyhow::Result<()> + pub fn new( + token: Option, + factory: impl FnOnce(CancellationToken) -> F, + ) -> Self where - F: FnOnce(CancellationToken) -> Fut, - Fut: Future + Send + 'static, + F: Future + Send + 'static, { - let mut state = self.state.lock(); - if !matches!(*state, AsyncRuntimeState::Idle) { - return Err(anyhow::anyhow!("task is already running/stopping")); - } - let token = token.unwrap_or_default(); - - let task = { - let f = factory(token.clone()); - let this = (*self).clone(); - tokio::spawn(async move { - let result = f.await; - let mut state = this.state.lock(); - if let AsyncRuntimeState::Running { id, .. } = &*state - && *id == tokio::task::id() - { - take(&mut *state); - } - result - }) - }; - - *state = AsyncRuntimeState::Running { - id: task.id(), - task: task.into(), + Self { + handle: AbortOnDropHandle::new(tokio::spawn(factory(token.clone()))), token, - }; - - Ok(()) + } } - pub async fn stop(&self, timeout: Option) -> Option> { - let state = { - let mut state = self.state.lock(); - match &*state { - AsyncRuntimeState::Running { .. } => { - let AsyncRuntimeState::Running { task, token, .. } = take(&mut *state) else { - unreachable!() - }; - *state = AsyncRuntimeState::Stopping(task.abort_handle()); - Ok((task, token)) - } - AsyncRuntimeState::Stopping(_) => Err(self.idle.notified()), - AsyncRuntimeState::Idle => return None, - } - }; - - let (mut task, token) = match state { - Ok(running) => running, - Err(stopping) => { - stopping.await; - return None; - } - }; - - token.cancel(); - let result = match timeout { - Some(duration) => { - if let Ok(result) = tokio::time::timeout(duration, &mut task).await { - result - } else { - task.abort(); - tracing::warn!("task stop timeout after {:?}, aborted", duration); - task.await - } - } - None => task.await, - }; - - { - let mut state = self.state.lock(); - if matches!(*state, AsyncRuntimeState::Stopping(_)) { - *state = AsyncRuntimeState::Idle; - drop(state); - self.idle.notify_waiters(); - } - } - - Some(result) + pub fn spawn(f: impl FnOnce(CancellationToken) -> F) -> Self + where + F: Future + Send + 'static, + { + Self::new(None, f) } - pub fn abort(&self) { - let mut state = self.state.lock(); - match &*state { - AsyncRuntimeState::Running { task, .. } => { - task.abort(); - *state = AsyncRuntimeState::Idle; - drop(state); - self.idle.notify_waiters(); + pub fn child(&self, f: impl FnOnce(CancellationToken) -> F) -> Self + where + F: Future + Send + 'static, + { + Self::new(Some(self.token.clone()), f) + } + + pub fn with_token(token: CancellationToken, future: F) -> Self + where + F: Future + Send + 'static, + { + Self::new(Some(token), |_| future) + } + + pub async fn stop(mut self, timeout: Option) -> Result { + self.token.cancel(); + if let Some(timeout) = timeout { + if let Ok(result) = tokio::time::timeout(timeout, &mut self.handle).await { + return result; + } else { + self.handle.abort(); + tracing::warn!("task stop timeout after {:?}, aborted", timeout); } - AsyncRuntimeState::Stopping(handle) => handle.abort(), - _ => {} } + self.handle.await + } +} + +impl Future for CancellableTask { + type Output = as Future>::Output; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.handle).poll(cx) } }