diff --git a/tokio/src/sync/broadcast.rs b/tokio/src/sync/broadcast.rs index 1d8f3a9ab..f88972ba6 100644 --- a/tokio/src/sync/broadcast.rs +++ b/tokio/src/sync/broadcast.rs @@ -32,15 +32,26 @@ //! This broadcast channel implementation handles this case by setting a hard //! upper bound on the number of values the channel may retain at any given //! time. This upper bound is passed to the [`channel`] function as an argument. +//! The provided capacity is rounded **up** to the next power of two; that +//! rounded size is the number of messages the ring buffer can hold, and is what +//! lag detection is based on. For example, `channel(3)` allocates a buffer of +//! length 4, so a receiver only lags once it falls more than 4 messages behind +//! the sender. //! //! If a value is sent when the channel is at capacity, the oldest value -//! currently held by the channel is released. This frees up space for the new -//! value. Any receiver that has not yet seen the released value will return -//! [`RecvError::Lagged`] the next time [`recv`] is called. +//! currently held by the channel is overwritten. This frees up space for the +//! new value. Any receiver that has not yet seen the overwritten value will +//! return [`RecvError::Lagged`] the next time [`recv`] (or +//! [`try_recv`](Receiver::try_recv)) is called. The error carries the number of +//! messages that were dropped before the receiver's cursor and are therefore +//! no longer available. //! -//! Once [`RecvError::Lagged`] is returned, the lagging receiver's position is -//! updated to the oldest value contained by the channel. The next call to -//! [`recv`] will return this value. +//! Returning [`RecvError::Lagged`] does **not** close or disconnect the +//! receiver. The lagging receiver's internal cursor is advanced to the oldest +//! value still retained by the channel. The **next** successful call to +//! [`recv`] / [`try_recv`](Receiver::try_recv) returns that oldest retained +//! value (unless further sends overwrite it again before the receiver reads +//! it). Subsequent receives then continue in send order from there. //! //! This behavior enables a receiver to detect when it has lagged so far behind //! that data has been dropped. The caller may decide how to respond to this: @@ -97,20 +108,23 @@ //! //! ``` //! use tokio::sync::broadcast; +//! use tokio::sync::broadcast::error::RecvError; //! //! # #[tokio::main(flavor = "current_thread")] //! # async fn main() { +//! // Capacity 2 → ring buffer of length 2. //! let (tx, mut rx) = broadcast::channel(2); //! //! tx.send(10).unwrap(); //! tx.send(20).unwrap(); +//! // Overwrites 10; receiver has not read it yet. //! tx.send(30).unwrap(); //! -//! // The receiver lagged behind -//! assert!(rx.recv().await.is_err()); -//! -//! // At this point, we can abort or continue with lost messages +//! // One message (10) was dropped; cursor moves to the oldest retained value (20). +//! assert!(matches!(rx.recv().await, Err(RecvError::Lagged(1)))); //! +//! // At this point, we can abort or continue with lost messages. +//! // Continuing resumes from the oldest retained message. //! assert_eq!(20, rx.recv().await.unwrap()); //! assert_eq!(30, rx.recv().await.unwrap()); //! # } @@ -278,10 +292,18 @@ pub mod error { /// be sent. Closed, - /// The receiver lagged too far behind. Attempting to receive again will - /// return the oldest message still retained by the channel. + /// The receiver lagged too far behind: one or more messages were + /// overwritten in the ring buffer before this receiver could read them. /// - /// Includes the number of skipped messages. + /// The receiver remains subscribed. Its internal cursor has been advanced + /// to the oldest message still retained by the channel; the next + /// successful [`recv`] call returns that message (unless further sends + /// overwrite it first). + /// + /// The `u64` is the number of messages that were skipped (dropped before + /// the receiver's previous cursor position). + /// + /// [`recv`]: crate::sync::broadcast::Receiver::recv Lagged(u64), } @@ -312,11 +334,18 @@ pub mod error { /// be sent. Closed, - /// The receiver lagged too far behind and has been forcibly disconnected. - /// Attempting to receive again will return the oldest message still - /// retained by the channel. + /// The receiver lagged too far behind: one or more messages were + /// overwritten in the ring buffer before this receiver could read them. /// - /// Includes the number of skipped messages. + /// The receiver remains subscribed. Its internal cursor has been advanced + /// to the oldest message still retained by the channel; the next + /// successful [`try_recv`] call returns that message (unless further sends + /// overwrite it first). + /// + /// The `u64` is the number of messages that were skipped (dropped before + /// the receiver's previous cursor position). + /// + /// [`try_recv`]: crate::sync::broadcast::Receiver::try_recv Lagged(u64), } @@ -453,7 +482,10 @@ const MAX_RECEIVERS: usize = usize::MAX >> 2; /// Create a bounded, multi-producer, multi-consumer channel where each sent /// value is broadcasted to all active receivers. /// -/// **Note:** The actual capacity may be greater than the provided `capacity`. +/// **Note:** The provided `capacity` is rounded **up** to the next power of +/// two. That rounded size is the number of messages the internal ring buffer +/// can retain, and is what [lag detection](self#lagging) uses. For example, +/// `channel(3)` behaves as if the capacity were 4. /// /// All data sent on [`Sender`] will become available on every active /// [`Receiver`] in the same order as it was sent. @@ -1130,12 +1162,16 @@ impl Receiver { /// Returns the number of messages that were sent into the channel and that /// this [`Receiver`] has yet to receive. /// - /// If the returned value from `len` is larger than the next largest power of 2 - /// of the capacity of the channel any call to [`recv`] will return an - /// `Err(RecvError::Lagged)` and any call to [`try_recv`] will return an - /// `Err(TryRecvError::Lagged)`, e.g. if the capacity of the channel is 10, - /// [`recv`] will start to return `Err(RecvError::Lagged)` once `len` returns - /// values larger than 16. + /// This count includes messages that have already been overwritten in the + /// ring buffer and are no longer readable. If `len` is **greater than** the + /// channel's effective capacity (the provided capacity rounded up to the + /// next power of two), the next call to [`recv`] returns + /// `Err(RecvError::Lagged)` and the next call to [`try_recv`] returns + /// `Err(TryRecvError::Lagged)`. For example, with `channel(10)` the buffer + /// length is 16, so lagging begins once `len` is larger than 16. + /// + /// After a successful receive (including after handling `Lagged` and then + /// reading retained messages), `len` decreases accordingly. /// /// [`Receiver`]: crate::sync::broadcast::Receiver /// [`recv`]: crate::sync::broadcast::Receiver::recv @@ -1399,11 +1435,13 @@ impl Receiver { /// dropped, indicating that no further values can be sent on the channel. /// /// If the [`Receiver`] handle falls behind, once the channel is full, newly - /// sent values will overwrite old values. At this point, a call to [`recv`] - /// will return with `Err(RecvError::Lagged)` and the [`Receiver`]'s - /// internal cursor is updated to point to the oldest value still held by - /// the channel. A subsequent call to [`recv`] will return this value - /// **unless** it has been since overwritten. + /// sent values overwrite old values in the ring buffer. The next call to + /// [`recv`] then returns `Err(RecvError::Lagged(n))`, where `n` is the + /// number of overwritten messages the receiver missed. The receiver stays + /// subscribed; its internal cursor is advanced to the oldest value still + /// held by the channel. A subsequent call to [`recv`] returns that value, + /// unless further sends overwrite it before the receiver reads it. See + /// [lagging](self#lagging) for details. /// /// # Cancel safety /// @@ -1444,6 +1482,7 @@ impl Receiver { /// /// ``` /// use tokio::sync::broadcast; + /// use tokio::sync::broadcast::error::RecvError; /// /// # #[tokio::main(flavor = "current_thread")] /// # async fn main() { @@ -1453,11 +1492,10 @@ impl Receiver { /// tx.send(20).unwrap(); /// tx.send(30).unwrap(); /// - /// // The receiver lagged behind - /// assert!(rx.recv().await.is_err()); - /// - /// // At this point, we can abort or continue with lost messages + /// // One message was overwritten before this receiver could read it. + /// assert!(matches!(rx.recv().await, Err(RecvError::Lagged(1)))); /// + /// // Resume from the oldest retained message, or abort the task instead. /// assert_eq!(20, rx.recv().await.unwrap()); /// assert_eq!(30, rx.recv().await.unwrap()); /// # } @@ -1478,12 +1516,14 @@ impl Receiver { /// dropped, indicating that no further values can be sent on the channel. /// /// If the [`Receiver`] handle falls behind, once the channel is full, newly - /// sent values will overwrite old values. At this point, a call to [`recv`] - /// will return with `Err(TryRecvError::Lagged)` and the [`Receiver`]'s - /// internal cursor is updated to point to the oldest value still held by - /// the channel. A subsequent call to [`try_recv`] will return this value - /// **unless** it has been since overwritten. If there are no values to - /// receive, `Err(TryRecvError::Empty)` is returned. + /// sent values overwrite old values in the ring buffer. The next call to + /// [`try_recv`] then returns `Err(TryRecvError::Lagged(n))`, where `n` is + /// the number of overwritten messages the receiver missed. The receiver + /// stays subscribed; its internal cursor is advanced to the oldest value + /// still held by the channel. A subsequent call to [`try_recv`] returns + /// that value, unless further sends overwrite it before the receiver reads + /// it. If there are no values to receive, `Err(TryRecvError::Empty)` is + /// returned. See [lagging](self#lagging) for details. /// /// [`recv`]: crate::sync::broadcast::Receiver::recv /// [`try_recv`]: crate::sync::broadcast::Receiver::try_recv diff --git a/tokio/tests/sync_broadcast.rs b/tokio/tests/sync_broadcast.rs index b742a6f61..2bfe235e5 100644 --- a/tokio/tests/sync_broadcast.rs +++ b/tokio/tests/sync_broadcast.rs @@ -720,3 +720,240 @@ async fn broadcast_sender_new_must_be_closed() { let mut task2 = task::spawn(tx.closed()); assert_pending!(task2.poll()); } + +// --- Lagging semantics ------------------------------------------------------- +// +// Capacity is rounded up to the next power of two. That buffer length is the +// maximum number of messages retained; exceeding it overwrites the oldest +// message and causes RecvError::Lagged / TryRecvError::Lagged on the slow +// receiver. After Lagged, the cursor points at the oldest retained message. + +/// A slow receiver that stays within the ring buffer receives every message +/// in order; lagging only begins once the buffer wraps past the receiver. +#[test] +fn slow_receiver_within_capacity_does_not_lag() { + // capacity 2 → buffer length 2 + let (tx, mut slow) = broadcast::channel(2); + let mut fast = tx.subscribe(); + + assert_ok!(tx.send(1)); + assert_ok!(tx.send(2)); + + // Fast keeps up; slow has not read yet but both messages are still retained. + assert_eq!(assert_recv!(fast), 1); + assert_eq!(assert_recv!(fast), 2); + assert_empty!(fast); + + assert_eq!(assert_recv!(slow), 1); + assert_eq!(assert_recv!(slow), 2); + assert_empty!(slow); +} + +/// When sends overwrite the oldest retained values, the slow receiver sees +/// Lagged(n) with the correct miss count, then resumes from the oldest +/// retained message in send order. +#[test] +fn capacity_overflow_reports_lagged_then_oldest_retained() { + // capacity 2 → buffer length 2; third send overwrites the first. + let (tx, mut rx) = broadcast::channel(2); + + assert_ok!(tx.send(10)); + assert_ok!(tx.send(20)); + assert_ok!(tx.send(30)); + + assert_lagged!(rx.try_recv(), 1); + // After Lagged, cursor is at oldest retained (20). + assert_eq!(assert_recv!(rx), 20); + assert_eq!(assert_recv!(rx), 30); + assert_empty!(rx); +} + +/// Lagged(n) counts every overwritten message since the receiver's previous +/// cursor, not merely "at least one". +#[test] +fn lagged_count_matches_number_of_overwritten_messages() { + // capacity 4 → buffer length 4 + let (tx, mut rx) = broadcast::channel(4); + + for i in 1..=4 { + assert_ok!(tx.send(i)); + } + // Still within capacity: no lag. + assert_eq!(assert_recv!(rx), 1); + + // Send four more without reading: overwrites 2, 3, 4, and then the slot + // that held 1 (already consumed). Receiver still owed 2,3,4 — all gone — + // so it missed 3 messages; oldest retained is 5. + for i in 5..=8 { + assert_ok!(tx.send(i)); + } + + assert_lagged!(rx.try_recv(), 3); + assert_eq!(assert_recv!(rx), 5); + assert_eq!(assert_recv!(rx), 6); + assert_eq!(assert_recv!(rx), 7); + assert_eq!(assert_recv!(rx), 8); + assert_empty!(rx); +} + +/// Non-power-of-two capacity is rounded up; lag detection uses the rounded +/// buffer length (e.g. capacity 3 → buffer 4). +#[test] +fn lag_uses_power_of_two_buffer_length() { + // capacity 3 → buffer length 4 + let (tx, mut rx) = broadcast::channel(3); + + for i in 1..=4 { + assert_ok!(tx.send(i)); + } + // Four messages fit in the rounded buffer; no lag yet. + assert_eq!(rx.len(), 4); + assert_eq!(assert_recv!(rx), 1); + + // After reading 1, next points at 2. Buffer holds 2,3,4,5 — still no lag. + assert_ok!(tx.send(5)); + assert_eq!(rx.len(), 4); + + // Overwrites 2; receiver still wants 2 → Lagged(1), resume at 3. + assert_ok!(tx.send(6)); + assert_eq!(rx.len(), 5); + assert_lagged!(rx.try_recv(), 1); + assert_eq!(assert_recv!(rx), 3); + assert_eq!(assert_recv!(rx), 4); + assert_eq!(assert_recv!(rx), 5); + assert_eq!(assert_recv!(rx), 6); + assert_empty!(rx); +} + +/// Async `recv` reports the same Lagged semantics as `try_recv`. +#[tokio::test] +async fn async_recv_lagged_then_resumes_from_oldest_retained() { + use broadcast::error::RecvError; + + let (tx, mut rx) = broadcast::channel(2); + + assert_ok!(tx.send(10)); + assert_ok!(tx.send(20)); + assert_ok!(tx.send(30)); + + assert!(matches!(rx.recv().await, Err(RecvError::Lagged(1)))); + assert_eq!(rx.recv().await.unwrap(), 20); + assert_eq!(rx.recv().await.unwrap(), 30); +} + +/// A receiver that lags, catches up, then lags again reports a fresh miss +/// count based on the new gap only. +#[test] +fn lag_catch_up_then_lag_again() { + let (tx, mut rx) = broadcast::channel(2); + + assert_ok!(tx.send(1)); + assert_ok!(tx.send(2)); + assert_ok!(tx.send(3)); + + assert_lagged!(rx.try_recv(), 1); + assert_eq!(assert_recv!(rx), 2); + assert_eq!(assert_recv!(rx), 3); + assert_empty!(rx); + + // Fully caught up; another overflow lags again from a clean cursor. + assert_ok!(tx.send(4)); + assert_ok!(tx.send(5)); + assert_ok!(tx.send(6)); + + assert_lagged!(rx.try_recv(), 1); + assert_eq!(assert_recv!(rx), 5); + assert_eq!(assert_recv!(rx), 6); + assert_empty!(rx); +} + +/// Only the slow receiver lags; a caught-up receiver is unaffected. +#[test] +fn lag_is_per_receiver() { + let (tx, mut slow) = broadcast::channel(2); + let mut fast = tx.subscribe(); + + assert_ok!(tx.send(1)); + assert_ok!(tx.send(2)); + + assert_eq!(assert_recv!(fast), 1); + assert_eq!(assert_recv!(fast), 2); + + assert_ok!(tx.send(3)); + assert_ok!(tx.send(4)); + // Fast has read through 2; buffer holds 3,4 — no lag. + assert_eq!(assert_recv!(fast), 3); + assert_eq!(assert_recv!(fast), 4); + + // Slow never read; 1 and 2 were overwritten → Lagged(2), oldest is 3. + assert_lagged!(slow.try_recv(), 2); + assert_eq!(assert_recv!(slow), 3); + assert_eq!(assert_recv!(slow), 4); + assert_empty!(slow); + assert_empty!(fast); +} + +/// If the receiver lags and more messages are sent before it reads the +/// Lagged error's "resume" position, a second Lagged reflects the additional +/// overwrites since the cursor was advanced. +#[test] +fn lag_again_before_reading_retained_messages() { + let (tx, mut rx) = broadcast::channel(2); + + assert_ok!(tx.send(1)); + assert_ok!(tx.send(2)); + assert_ok!(tx.send(3)); + + // First lag: miss 1, cursor advances to oldest retained (value 2). + assert_lagged!(rx.try_recv(), 1); + + // Before reading 2/3, send enough to overwrite them (and one more). + assert_ok!(tx.send(4)); + assert_ok!(tx.send(5)); + assert_ok!(tx.send(6)); + + // Cursor was at value 2; values 2, 3, and 4 are gone; oldest retained is 5. + assert_lagged!(rx.try_recv(), 3); + assert_eq!(assert_recv!(rx), 5); + assert_eq!(assert_recv!(rx), 6); + assert_empty!(rx); +} + +/// With capacity 1 (buffer length 1), every send after the first unread one +/// causes a lag of exactly one when the receiver finally reads. +#[test] +fn single_slot_capacity_lag_semantics() { + let (tx, mut rx) = broadcast::channel(1); + + assert_ok!(tx.send(1)); + assert_eq!(assert_recv!(rx), 1); + + assert_ok!(tx.send(2)); + assert_ok!(tx.send(3)); + + assert_lagged!(rx.try_recv(), 1); + assert_eq!(assert_recv!(rx), 3); + assert_empty!(rx); +} + +/// `len` after lagging still counts from the old cursor until Lagged is +/// observed and the cursor advances; then `len` reflects retained messages. +#[test] +fn receiver_len_after_lag_error_advances_cursor() { + let (tx, mut rx) = broadcast::channel(2); + + assert_ok!(tx.send(1)); + assert_ok!(tx.send(2)); + assert_ok!(tx.send(3)); + assert_ok!(tx.send(4)); + + // Missed 1 and 2; buffer holds 3,4. len counts from old cursor. + assert_eq!(rx.len(), 4); + assert_lagged!(rx.try_recv(), 2); + // Cursor now at oldest retained (3); two messages remain. + assert_eq!(rx.len(), 2); + assert_eq!(assert_recv!(rx), 3); + assert_eq!(rx.len(), 1); + assert_eq!(assert_recv!(rx), 4); + assert_eq!(rx.len(), 0); +}