From bb2c890685aa407c305ecadf1c185dff14c13b47 Mon Sep 17 00:00:00 2001 From: Guramrit Singh Date: Fri, 1 May 2026 12:25:11 -0700 Subject: [PATCH 1/3] Added a doc comment for TaskControl --- support/task_control/src/lib.rs | 64 +++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/support/task_control/src/lib.rs b/support/task_control/src/lib.rs index 8fc78c773c..571baea297 100644 --- a/support/task_control/src/lib.rs +++ b/support/task_control/src/lib.rs @@ -134,8 +134,68 @@ impl Future for StopTask<'_> { } } -/// A task wrapper that runs the task asynchronously and provides access to its -/// state. +/// Hosts an async task that can be started, stopped, mutated, and inspected. +/// +/// Pairs a task implementation `T: AsyncRun` with transient state `S`. +/// State is provided via [`insert`](Self::insert), the task body is run by +/// [`start`](Self::start) calling `T::run(&mut StopTask, &mut S)`, and +/// execution is paused via [`stop`](Self::stop). While stopped, `T` and `S` +/// are accessible through [`task_mut`](Self::task_mut), +/// [`state_mut`](Self::state_mut), etc., and the state can be swapped out +/// before the next run. +/// +/// Cancellation is cooperative: implementations wrap interruptible await +/// points in [`StopTask::until_stopped`], which drops the inner future and +/// returns `Err(Cancelled)` when a stop is signaled. `T::run` should +/// propagate `Err(Cancelled)` so `TaskControl` regains control. +/// +/// `TaskControl` also raises the stop signal internally to service +/// [`update_with`](Self::update_with) and [`Inspect`]/[`InspectMut`] +/// requests, after which it re-invokes `T::run` with the (possibly updated) +/// state. Because `until_stopped` discards the inner future on +/// cancellation, implementations must ensure no in-flight work is silently +/// lost. The recommended pattern is to wrap only a single "wait for next +/// request" await (e.g. a channel receive) in `until_stopped` and process +/// the resulting request outside of it. Wrapping a larger scope keeps +/// inspect/`update_with` responsive but risks dropping work; await points +/// outside `until_stopped` block servicing of those requests until they +/// complete. +/// +/// # Example +/// ```no_run +/// use task_control::AsyncRun; +/// use task_control::Cancelled; +/// use task_control::StopTask; +/// +/// struct Worker; +/// +/// struct WorkerState { +/// processed: u64, +/// } +/// +/// impl Worker { +/// async fn next_request(&mut self) -> Option { None } +/// async fn handle(&mut self, _req: u32, _state: &mut WorkerState) {} +/// } +/// +/// impl AsyncRun for Worker { +/// async fn run( +/// &mut self, +/// stop: &mut StopTask<'_>, +/// state: &mut WorkerState, +/// ) -> Result<(), Cancelled> { +/// loop { +/// // Only the receive is wrapped: a dropped recv future is +/// // harmless, but request handling below runs to completion +/// // so no in-flight work is lost across stops. +/// let Some(req) = stop.until_stopped(self.next_request()).await? else { +/// return Ok(()); +/// }; +/// self.handle(req, state).await; +/// } +/// } +/// } +/// ``` pub struct TaskControl { inner: Inner, } From 1ce2b6cc790c7c8d348feb870dc87b12c78b4710 Mon Sep 17 00:00:00 2001 From: Guramrit Singh Date: Fri, 1 May 2026 13:01:37 -0700 Subject: [PATCH 2/3] Much better explanation for TaskControl now --- support/task_control/src/lib.rs | 68 ++++++--------------------------- 1 file changed, 12 insertions(+), 56 deletions(-) diff --git a/support/task_control/src/lib.rs b/support/task_control/src/lib.rs index 571baea297..e063dc8ebf 100644 --- a/support/task_control/src/lib.rs +++ b/support/task_control/src/lib.rs @@ -134,68 +134,24 @@ impl Future for StopTask<'_> { } } -/// Hosts an async task that can be started, stopped, mutated, and inspected. +/// A task wrapper that runs a task asynchronously and provides access to its +/// state and control over its execution (start/stop). /// /// Pairs a task implementation `T: AsyncRun` with transient state `S`. -/// State is provided via [`insert`](Self::insert), the task body is run by -/// [`start`](Self::start) calling `T::run(&mut StopTask, &mut S)`, and -/// execution is paused via [`stop`](Self::stop). While stopped, `T` and `S` -/// are accessible through [`task_mut`](Self::task_mut), -/// [`state_mut`](Self::state_mut), etc., and the state can be swapped out -/// before the next run. +/// Execution is cancelled when [`stop`](Self::stop) is invoked. /// -/// Cancellation is cooperative: implementations wrap interruptible await -/// points in [`StopTask::until_stopped`], which drops the inner future and +/// Cancellation is cooperative: implementations are expected to wrap +/// futures in [`StopTask::until_stopped`], which drops the inner future and /// returns `Err(Cancelled)` when a stop is signaled. `T::run` should /// propagate `Err(Cancelled)` so `TaskControl` regains control. /// -/// `TaskControl` also raises the stop signal internally to service -/// [`update_with`](Self::update_with) and [`Inspect`]/[`InspectMut`] -/// requests, after which it re-invokes `T::run` with the (possibly updated) -/// state. Because `until_stopped` discards the inner future on -/// cancellation, implementations must ensure no in-flight work is silently -/// lost. The recommended pattern is to wrap only a single "wait for next -/// request" await (e.g. a channel receive) in `until_stopped` and process -/// the resulting request outside of it. Wrapping a larger scope keeps -/// inspect/`update_with` responsive but risks dropping work; await points -/// outside `until_stopped` block servicing of those requests until they -/// complete. -/// -/// # Example -/// ```no_run -/// use task_control::AsyncRun; -/// use task_control::Cancelled; -/// use task_control::StopTask; -/// -/// struct Worker; -/// -/// struct WorkerState { -/// processed: u64, -/// } -/// -/// impl Worker { -/// async fn next_request(&mut self) -> Option { None } -/// async fn handle(&mut self, _req: u32, _state: &mut WorkerState) {} -/// } -/// -/// impl AsyncRun for Worker { -/// async fn run( -/// &mut self, -/// stop: &mut StopTask<'_>, -/// state: &mut WorkerState, -/// ) -> Result<(), Cancelled> { -/// loop { -/// // Only the receive is wrapped: a dropped recv future is -/// // harmless, but request handling below runs to completion -/// // so no in-flight work is lost across stops. -/// let Some(req) = stop.until_stopped(self.next_request()).await? else { -/// return Ok(()); -/// }; -/// self.handle(req, state).await; -/// } -/// } -/// } -/// ``` +/// Implementations of `T: AsyncRun` are expected to be cancel-tolerant. Outside of +/// an actual [`stop`](Self::stop), `TaskControl` also raises the stop +/// signal to fulfill [`update_with`](Self::update_with) and +/// [`Inspect`]/[`InspectMut`] calls, after which `run` is re-invoked with +/// the (possibly updated) state. Because `until_stopped` discards the +/// inner future on cancellation, implementations must ensure no in-flight +/// work is silently lost. pub struct TaskControl { inner: Inner, } From 114e310727b580df075da4b382ade24467c56709 Mon Sep 17 00:00:00 2001 From: Guramrit Singh Date: Wed, 6 May 2026 14:05:00 -0700 Subject: [PATCH 3/3] Minor tweaks to the comments --- support/task_control/src/lib.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/support/task_control/src/lib.rs b/support/task_control/src/lib.rs index e063dc8ebf..6ebcbde90f 100644 --- a/support/task_control/src/lib.rs +++ b/support/task_control/src/lib.rs @@ -35,7 +35,11 @@ pub trait AsyncRun: 'static + Send { /// /// If the function instead returns `Err(Cancelled)`, this indicates that /// the task's work is not complete, and it should be restarted after - /// handling any incoming events. + /// handling any incoming events. Implementations that choose to use + /// [`StopTask::until_stopped`] must be cancel-tolerant: because + /// [`StopTask::until_stopped`] drops the inner future when a stop + /// is signaled, implementations must ensure no in-flight work is silently + /// lost across a cancellation. fn run( &mut self, stop: &mut StopTask<'_>, @@ -140,18 +144,11 @@ impl Future for StopTask<'_> { /// Pairs a task implementation `T: AsyncRun` with transient state `S`. /// Execution is cancelled when [`stop`](Self::stop) is invoked. /// -/// Cancellation is cooperative: implementations are expected to wrap -/// futures in [`StopTask::until_stopped`], which drops the inner future and -/// returns `Err(Cancelled)` when a stop is signaled. `T::run` should -/// propagate `Err(Cancelled)` so `TaskControl` regains control. -/// -/// Implementations of `T: AsyncRun` are expected to be cancel-tolerant. Outside of -/// an actual [`stop`](Self::stop), `TaskControl` also raises the stop -/// signal to fulfill [`update_with`](Self::update_with) and +/// Outside of an actual [`stop`](Self::stop), `TaskControl` also raises the +/// stop signal to fulfill [`update_with`](Self::update_with) and /// [`Inspect`]/[`InspectMut`] calls, after which `run` is re-invoked with -/// the (possibly updated) state. Because `until_stopped` discards the -/// inner future on cancellation, implementations must ensure no in-flight -/// work is silently lost. +/// the (possibly updated) state. See [`AsyncRun`] for the cancel-tolerance +/// requirements this places on implementations. pub struct TaskControl { inner: Inner, }