Repository: zesterer/flume (Rust)

Issue:
Add a `WeakSender`?
[Weak pointers](https://doc.rust-lang.org/stable/std/sync/struct.Weak.html) can be useful when working with reference-counted values. For `mpsc` senders, this would allow to create a clone that does not prevent the channel from being closed. As [`flume::Sender`](https://docs.rs/flume/0.10.14/flume/struct.Sender.html) is [`Arc`-based](https://github.com/zesterer/flume/blob/8bb9ce02d3502134cb23cebe3d389aded9f64d15/src/lib.rs#L655), it should be possible to implement a weak sender type.

Some other channel libraries implement this operation already. For example, `tokio`'s `mpsc` senders have a [`downgrade`](https://docs.rs/tokio/1.21.2/tokio/sync/mpsc/struct.Sender.html#method.downgrade) method.




Discussion:
I have something similar in https://github.com/Restioson/xtra.

I think that the implementation might be a little more complex than just changing the Arc to a Weak. For instance, what would happen if you had 1 weak sender and 1 (strong) receiver? If it should drop the channel, then just having a Weak on the sender is not enough, since the Strong on the receiver would keep it alive. If it should not drop the channel, then there is no point having Weak senders, since they would only affect channel closing with no (strong) receivers, in which case the channel is already closed.
The channel should be closed when the last strong sender is dropped. The custom drop implementation of `Sender` already does that: https://github.com/zesterer/flume/blob/8bb9ce02d3502134cb23cebe3d389aded9f64d15/src/lib.rs#L746-L749

So I think it should be enough to change the `Arc` to `Weak` and skip the `sender_count` increase that normally happens on `clone`: https://github.com/zesterer/flume/blob/8bb9ce02d3502134cb23cebe3d389aded9f64d15/src/lib.rs#L732-L735
I think this should be possible and, indeed, relatively easy to implement. Out of curiosity, do you have an example of a case where this might be useful?
For example when merging events from different sources (e.g., using an `Event` enum) and only some of the sources should keep the channel open. A concrete use case could be a command line application that waits for `stdin` lines or a ctrl+c signal using the [`ctrlc`](https://docs.rs/ctrlc/3.2.3/ctrlc/) crate, where both types of events are sent over the same channel. We want to close the channel as soon as `stdin` is closed, so we could use a `WeakSender` for the `ctrlc` handler.



Patch:
diff --git a/src/lib.rs b/src/lib.rs
index 9747705..b513c82 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -40,7 +40,7 @@ pub use select::Selector;
 
 use std::{
     collections::VecDeque,
-    sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering}},
+    sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering}, Weak},
     time::{Duration, Instant},
     marker::PhantomData,
     thread,
@@ -724,6 +724,16 @@ impl<T> Sender<T> {
     pub fn capacity(&self) -> Option<usize> {
         self.shared.capacity()
     }
+
+    /// Creates a [`WeakSender`] that does not keep the channel open.
+    ///
+    /// The channel is closed once all `Sender`s are dropped, even if there
+    /// are still active `WeakSender`s.
+    pub fn downgrade(&self) -> WeakSender<T> {
+        WeakSender {
+            shared: Arc::downgrade(&self.shared),
+        }
+    }
 }
 
 impl<T> Clone for Sender<T> {
@@ -750,6 +760,45 @@ impl<T> Drop for Sender<T> {
     }
 }
 
+/// A sender that does not prevent the channel from being closed.
+///
+/// Weak senders do not count towards the number of active senders on the channel. As soon as
+/// all normal [`Sender`]s are dropped, the channel is closed, even if there is still a
+/// `WeakSender`.
+///
+/// To send messages, a `WeakSender` must first be upgraded to a `Sender` using the [`upgrade`]
+/// method.
+pub struct WeakSender<T> {
+    shared: Weak<Shared<T>>,
+}
+
+impl<T> WeakSender<T> {
+    /// Tries to upgrade the `WeakSender` to a [`Sender`], in order to send messages.
+    ///
+    /// Returns `None` if the channel was closed already. Note that a `Some` return value
+    /// does not guarantee that the channel is still open.
+    pub fn upgrade(&self) -> Option<Sender<T>> {
+        self.shared
+            .upgrade()
+            // check that there are still live senders
+            .filter(|shared| {
+                shared
+                    .sender_count
+                    .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |count| {
+                        if count == 0 {
+                            // all senders are closed already -> don't increase the sender count
+                            None
+                        } else {
+                            // there is still at least one active sender
+                            Some(count + 1)
+                        }
+                    })
+                    .is_ok()
+            })
+            .map(|shared| Sender { shared })
+    }
+}
+
 /// The receiving end of a channel.
 ///
 /// Note: Cloning the receiver *does not* turn this channel into a broadcast channel.


Test patch:
diff --git a/tests/basic.rs b/tests/basic.rs
index 9c0f81b..a4eb5dd 100644
--- a/tests/basic.rs
+++ b/tests/basic.rs
@@ -405,3 +405,24 @@ fn std_error_without_debug() {
         }
     }
 }
+
+#[test]
+fn weak_close() {
+    let (tx, rx) = unbounded::<()>();
+    let weak = tx.downgrade();
+    drop(tx);
+    assert!(weak.upgrade().is_none());
+    assert!(rx.is_disconnected());
+    assert!(rx.try_recv().is_err());
+}
+
+#[test]
+fn weak_upgrade() {
+    let (tx, rx) = unbounded();
+    let weak = tx.downgrade();
+    let tx2 = weak.upgrade().unwrap();
+    drop(tx);
+    assert!(!rx.is_disconnected());
+    tx2.send(()).unwrap();
+    assert!(rx.try_recv().is_ok());
+}