Skip to content

7900: sync: add closed method to broadcast::WeakSender#58

Closed
martin-augment wants to merge 1 commit intomasterfrom
pr-7900-2026-02-10-10-12-29
Closed

7900: sync: add closed method to broadcast::WeakSender#58
martin-augment wants to merge 1 commit intomasterfrom
pr-7900-2026-02-10-10-12-29

Conversation

@martin-augment
Copy link
Owner

7900: To review by AI

@gemini-code-assist
Copy link

Summary of Changes

Hello @martin-augment, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the tokio::sync::broadcast channel by providing a mechanism to asynchronously await the closure of a channel even when only holding a weak reference. This improves the usability and robustness of broadcast channels by allowing components holding WeakSender instances to react to the channel's lifecycle events, ensuring proper resource management and coordination.

Highlights

  • New WeakSender::closed method: Introduced an async fn closed() method for broadcast::WeakSender that completes when all Receivers subscribed to the channel have been dropped. This mirrors the existing functionality on Sender.
  • Refactored closed logic: The core logic for determining if a broadcast channel is closed has been extracted from Sender::closed into a new private Shared::closed method. Both Sender::closed and the new WeakSender::closed now delegate to this shared implementation.
  • New test cases: Added a dedicated test case broadcast_weak_sender_closed to verify the behavior of the new WeakSender::closed method. Existing tests for Sender::closed were also updated to include checks for the WeakSender counterpart.
Changelog
  • tokio/src/sync/broadcast.rs
    • Refactored the closed method logic from Sender<T> into a new private Shared<T>::closed method.
    • Updated Sender<T>::closed to call the new Shared<T>::closed method.
    • Added a new public async fn closed() method to WeakSender<T>, which also calls Shared<T>::closed.
    • Included comprehensive documentation and examples for the new WeakSender<T>::closed method.
  • tokio/tests/sync_broadcast.rs
    • Added a new test broadcast_weak_sender_closed to validate the functionality of WeakSender::closed.
    • Modified broadcast_sender_closed_with_extra_subscribe to concurrently test WeakSender::closed alongside Sender::closed.
Activity
  • The pull request was marked for review by AI.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

Walkthrough

This pull request introduces a unified async close-notification mechanism in the broadcast channel synchronization primitive. The changes add new closed() async methods to Sender, WeakSender, and a private Shared::closed() helper. These methods enable senders to asynchronously wait until all receivers drop before signaling closure. Accompanying test additions cover the new weak sender closure behavior and extend existing tests to validate the lifecycle of both strong and weak sender references during channel closure.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr-7900-2026-02-10-10-12-29

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Feb 10, 2026

Code Review

Overview

This PR adds a closed() method to broadcast::WeakSender that mirrors the existing Sender::closed() functionality. The implementation is clean and follows good Tokio patterns.

✅ Strengths

1. Good Code Reuse

  • Excellent refactoring by extracting the closed() logic into Shared::closed() at tokio/src/sync/broadcast.rs:1045
  • Both Sender::closed() and WeakSender::closed() now delegate to the same implementation, eliminating code duplication
  • This is the right design pattern for shared functionality

2. API Consistency

  • WeakSender::closed() now provides the same capability as Sender::closed()
  • This matches the pattern where WeakSender has strong_count() and weak_count() methods that parallel those on Sender
  • Makes the API more intuitive and complete

3. Comprehensive Test Coverage

  • Added new broadcast_weak_sender_closed test at tokio/tests/sync_broadcast.rs:677
  • Enhanced existing broadcast_sender_closed_with_extra_subscribe test to verify both Sender and WeakSender behavior in parallel
  • Tests cover the key scenarios: waiting for receivers to drop, re-subscribing, and immediate completion when already closed

4. Good Documentation

  • The doc comment for WeakSender::closed() includes a clear, runnable example
  • Documentation correctly describes the behavior

🔍 Minor Observations

1. Documentation Formatting (line 1109)
The doc comment says:

/// A future which completes when the number of [Receiver]s subscribed to this `Sender` reaches

This references Sender but should probably say broadcast channel or remove the word "Sender" since this is on WeakSender. The original Sender::closed() has the same wording, so this is consistent, but slightly imprecise. Not a blocker.

2. Test Organization
The test additions are well-structured. The new broadcast_weak_sender_closed test is appropriately simple, and the enhancements to broadcast_sender_closed_with_extra_subscribe verify that both sender types behave identically.

🎯 Final Assessment

Verdict: Approve

This is a solid, well-implemented feature addition that:

  • ✅ Improves API completeness and consistency
  • ✅ Reduces code duplication through good refactoring
  • ✅ Has comprehensive test coverage
  • ✅ Follows Tokio conventions
  • ✅ No security concerns
  • ✅ No performance concerns (same implementation as before, just better organized)
  • ✅ No bugs identified

The refactoring to extract `Shared::closed()) is particularly nice - it makes the code more maintainable and follows the DRY principle.

Recommendation: Merge after addressing the minor documentation wording if desired (or leave as-is for consistency with Sender::closed()).


🤖 Review generated by Claude Code

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tokio/src/sync/broadcast.rs`:
- Around line 1045-1059: The current async fn closed(&self) in the broadcast
implementation incorrectly returns based on tail.closed (which is set both when
all senders drop and when all receivers drop); change the check to inspect the
receiver count instead so WeakSender::closed() only completes when no receivers
remain: inside closed() acquire the tail lock (the same tail = self.tail.lock()
scope), read tail.rx_cnt (or the equivalent receiver count field) and return
only when rx_cnt == 0; keep the notify_last_rx_drop.notified()/await logic and
ensure the lock is dropped before awaiting, and leave other code paths like
close_channel() and Receiver::drop untouched.
🧹 Nitpick comments (1)
tokio/src/sync/broadcast.rs (1)

1109-1137: Doc and delegation look good, but note the cooperative() wrapping is missing.

The watch::Sender::closed() wraps its loop in cooperative(…).await and calls crate::trace::async_trace_leaf().await for budget/tracing support. This method (and Sender::closed above) omit both. While unlikely to cause real issues (the loop shouldn't spin rapidly), it's an inconsistency with the rest of the crate.

Comment on lines +1045 to +1059
async fn closed(&self) {
loop {
let notified = self.notify_last_rx_drop.notified();

{
// Ensure the lock drops if the channel isn't closed
let tail = self.tail.lock();
if tail.closed {
return;
}
}

notified.await;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: tail.closed conflates "all senders dropped" with "all receivers dropped" — breaks WeakSender::closed().

tail.closed is set to true by both close_channel() (when all strong Senders drop) and Receiver::drop (when rx_cnt reaches 0). This means Shared::closed() will return immediately whenever all strong senders have been dropped, even if receivers are still alive.

This is correct for Sender::closed() (you always hold a strong ref, so close_channel can't fire), but incorrect for WeakSender::closed() — the whole point is that the strong senders may already be gone. The test broadcast_weak_sender_closed drops tx first (Line 681), which sets tail.closed = true via close_channel, then expects weak.closed() to remain pending while receivers exist. It will instead resolve immediately.

The watch::Sender::closed() correctly checks receiver_count() > 0 rather than a "closed" flag. The same approach should be used here:

🐛 Proposed fix: check rx_cnt instead of tail.closed
     async fn closed(&self) {
         loop {
             let notified = self.notify_last_rx_drop.notified();
 
             {
-                // Ensure the lock drops if the channel isn't closed
                 let tail = self.tail.lock();
-                if tail.closed {
+                if tail.rx_cnt == 0 {
                     return;
                 }
             }
 
             notified.await;
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async fn closed(&self) {
loop {
let notified = self.notify_last_rx_drop.notified();
{
// Ensure the lock drops if the channel isn't closed
let tail = self.tail.lock();
if tail.closed {
return;
}
}
notified.await;
}
}
async fn closed(&self) {
loop {
let notified = self.notify_last_rx_drop.notified();
{
let tail = self.tail.lock();
if tail.rx_cnt == 0 {
return;
}
}
notified.await;
}
}
🤖 Prompt for AI Agents
In `@tokio/src/sync/broadcast.rs` around lines 1045 - 1059, The current async fn
closed(&self) in the broadcast implementation incorrectly returns based on
tail.closed (which is set both when all senders drop and when all receivers
drop); change the check to inspect the receiver count instead so
WeakSender::closed() only completes when no receivers remain: inside closed()
acquire the tail lock (the same tail = self.tail.lock() scope), read tail.rx_cnt
(or the equivalent receiver count field) and return only when rx_cnt == 0; keep
the notify_last_rx_drop.notified()/await logic and ensure the lock is dropped
before awaiting, and leave other code paths like close_channel() and
Receiver::drop untouched.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a closed() method on WeakSender and refactors the existing Sender::closed() to share implementation. However, a critical vulnerability has been identified: the Shared::closed implementation incorrectly relies on the tail.closed flag, causing WeakSender::closed() to complete prematurely when senders are dropped, even if receivers are still active. This violates documented behavior and could lead to race conditions. A specific fix has been suggested, and it's recommended to add a test case to cover the scenario where senders are dropped but receivers still exist to prevent regressions.

Comment on lines +1052 to +1053
if tail.closed {
return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The closed() method in Shared::closed completes prematurely if all Sender handles are dropped, even when Receiver handles are still active. This is a critical vulnerability because tail.closed is incorrectly used to indicate both sender and receiver drops, violating the documented behavior of WeakSender::closed() and potentially causing race conditions. The closed() future should only complete when all Receivers are dropped. The correct check should be against tail.rx_cnt == 0.

Suggested change
if tail.closed {
return;
if tail.rx_cnt == 0 {
return;
}

@augmentcode
Copy link

augmentcode bot commented Feb 10, 2026

🤖 Augment PR Summary

Summary: Adds a closed() API to broadcast::WeakSender and refactors existing logic to share the “wait for last receiver” implementation.

Changes:

  • Refactored Sender::closed() to delegate to a new internal Shared<T>::closed() helper
  • Added WeakSender::closed() with rustdoc and an async example
  • Extended tokio/tests/sync_broadcast.rs to cover WeakSender::closed() and interactions with re-subscribing receivers

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

{
// Ensure the lock drops if the channel isn't closed
let tail = self.tail.lock();
if tail.closed {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shared::closed checks tail.closed, which is also set when the last Sender is dropped (close_channel), so WeakSender::closed() can complete even while rx_cnt > 0. This seems to conflict with the docs/tests describing completion when the receiver count reaches zero; can you confirm the intended semantics?

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

}
}

/// A future which completes when the number of [Receiver]s subscribed to this `Sender` reaches
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rustdoc for WeakSender::closed says “subscribed to this Sender”, but this method is on WeakSender. This could be misleading in generated docs about what handle/channel the receiver count is tied to.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@martin-augment
Copy link
Owner Author

Superseded by #60

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants