// Copyright 2024 tison // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! A countdown latch that allows one or more tasks to wait until a set of operations completes. //! //! Unlike a barrier, a latch's count can only decrease and cannot be reused once it reaches zero. //! This makes it ideal for scenarios where you need to wait for a specific number of events or //! operations to complete. //! //! A latch starts with an initial count and tasks can wait for this count to reach zero. //! The count can be decremented by calling [`count_down()`] or [`arrive()`]. Once the count //! reaches zero, all waiting tasks are unblocked. //! //! # Examples //! //! ``` //! # #[tokio::main] //! # async fn main() { //! use std::sync::Arc; //! //! use mea::latch::Latch; //! //! let latch = Arc::new(Latch::new(3)); //! let mut handles = Vec::new(); //! //! for i in 0..3 { //! let latch = latch.clone(); //! handles.push(tokio::spawn(async move { //! println!("Task {} starting", i); //! // Simulate some work //! latch.count_down(); // Signal completion //! })); //! } //! //! // Wait for all tasks to complete //! latch.wait().await; //! println!("All tasks completed"); //! # } //! ``` //! //! [`count_down()`]: Latch::count_down //! [`arrive()`]: Latch::arrive use std::fmt; use std::future::Future; use std::pin::Pin; use std::sync::Arc; use std::task::Context; use std::task::Poll; use crate::internal::CountdownState; #[cfg(test)] mod tests; /// A synchronization primitive that can be used to coordinate multiple tasks. /// /// See the [module level documentation](self) for more. #[derive(Debug)] pub struct Latch { state: CountdownState, } impl Latch { /// Creates a new latch initialized with the given count. /// /// # Arguments /// /// * `count` - The initial count value. Tasks will wait until this count reaches zero. /// /// # Examples /// /// ``` /// use mea::latch::Latch; /// /// let latch = Latch::new(3); // Creates a latch with count of 3 /// ``` pub fn new(count: u32) -> Self { Self { state: CountdownState::new(count), } } /// Returns the current count. /// /// This method is typically used for debugging and testing purposes. /// /// # Examples /// /// ``` /// use mea::latch::Latch; /// /// let latch = Latch::new(5); /// assert_eq!(latch.count(), 5); /// ``` pub fn count(&self) -> u32 { self.state.state() } /// Decrements the latch count by one, waking up all pending tasks if the counter reaches zero. /// /// If the current count is zero, this method has no effect. /// /// # Examples /// /// ``` /// use mea::latch::Latch; /// /// let latch = Latch::new(2); /// latch.count_down(); // Count is now 1 /// latch.count_down(); // Count is now 0, all waiting tasks are woken /// ``` pub fn count_down(&self) { if self.state.decrement(1) { self.state.wake_all(); } } /// Decrements the latch count by `n`, waking up all waiting tasks if the counter reaches zero. /// /// This method provides a way to decrement the counter by more than one at a time. /// It will not cause an overflow when decrementing the counter. /// /// # Arguments /// /// * `n` - The amount to decrement the counter by /// /// # Behavior /// /// * If `n` is zero or the counter has already reached zero, nothing happens /// * If the current count is greater than `n`, it is decremented by `n` /// * If the current count is greater than 0 but less than or equal to `n`, the count becomes /// zero and all waiting tasks are woken /// /// # Examples /// /// ``` /// use mea::latch::Latch; /// /// let latch = Latch::new(5); /// latch.arrive(3); // Count is now 2 /// latch.arrive(2); // Count is now 0, all waiting tasks are woken /// ``` pub fn arrive(&self, n: u32) { if n != 0 && self.state.decrement(n) { self.state.wake_all(); } } /// Attempts to wait for the latch count to reach zero without blocking. /// /// # Returns /// /// * `Ok(())` if the count is zero /// * `Err(count)` if the count is not zero, where `count` is the current count /// /// # Examples /// /// ``` /// use mea::latch::Latch; /// /// let latch = Latch::new(2); /// assert_eq!(latch.try_wait(), Err(2)); /// latch.count_down(); /// assert_eq!(latch.try_wait(), Err(1)); /// latch.count_down(); /// assert_eq!(latch.try_wait(), Ok(())); /// ``` pub fn try_wait(&self) -> Result<(), u32> { self.state.spin_wait(0) } /// Returns a future that will complete when the latch count reaches zero. /// /// # Examples /// /// ``` /// # #[tokio::main] /// # async fn main() { /// use std::sync::Arc; /// /// use mea::latch::Latch; /// /// let latch = Arc::new(Latch::new(1)); /// let latch2 = latch.clone(); /// /// // Spawn a task that will wait for the latch /// let handle = tokio::spawn(async move { /// latch2.wait().await; /// println!("Latch reached zero!"); /// }); /// /// // Count down the latch /// latch.count_down(); /// handle.await.unwrap(); /// # } /// ``` pub async fn wait(&self) { let fut = LatchWait { idx: None, latch: self, }; fut.await } /// Returns a future that will complete when the latch count reaches zero. /// /// The latch must be wrapped in an [`Arc`] to call this method. Thus, the returned future has /// no lifetime constraints. /// /// # Examples /// /// ``` /// # #[tokio::main] /// # async fn main() { /// use std::sync::Arc; /// /// use mea::latch::Latch; /// /// let latch = Arc::new(Latch::new(1)); /// let latch2 = latch.clone(); /// /// // Spawn a task that will wait for the latch /// let handle = tokio::spawn(async move { /// latch2.wait_owned().await; /// println!("Latch reached zero!"); /// }); /// /// // Count down the latch /// latch.count_down(); /// handle.await.unwrap(); /// # } /// ``` pub async fn wait_owned(self: Arc) { let fut = OwnedLatchWait { idx: None, latch: self, }; fut.await } } impl Latch { fn intern_poll(&self, idx: &mut Option, cx: &mut Context<'_>) -> Poll<()> { // register waker if the counter is not zero if self.state.spin_wait(16).is_err() { self.state.register_waker(idx, cx); // double check after register waker, to catch the update between two steps if self.state.spin_wait(0).is_err() { return Poll::Pending; } } Poll::Ready(()) } } /// A wait future returned by [`Latch::wait()`]. /// /// This future will complete when the latch count reaches zero. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct LatchWait<'a> { idx: Option, latch: &'a Latch, } impl fmt::Debug for LatchWait<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("LatchWait").finish_non_exhaustive() } } impl Future for LatchWait<'_> { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let Self { idx, latch } = self.get_mut(); latch.intern_poll(idx, cx) } } /// An owned wait future returned by [`Latch::wait()`]. /// /// This future will complete when the latch count reaches zero. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct OwnedLatchWait { idx: Option, latch: Arc, } impl fmt::Debug for OwnedLatchWait { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OwnedLatchWait").finish_non_exhaustive() } } impl Future for OwnedLatchWait { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let Self { idx, latch } = self.get_mut(); latch.intern_poll(idx, cx) } }