Skip to main content

rustrade_integration/
channel.rs

1use crate::Unrecoverable;
2use derive_more::{Constructor, Display};
3use futures::{Sink, Stream};
4use serde::{Deserialize, Serialize};
5use std::{
6    fmt::Debug,
7    pin::Pin,
8    task::{Context, Poll},
9};
10use tracing::warn;
11
12pub trait Tx
13where
14    Self: Debug + Clone + Send,
15{
16    type Item;
17    type Error: Unrecoverable + Debug;
18    fn send<Item: Into<Self::Item>>(&self, item: Item) -> Result<(), Self::Error>;
19}
20
21/// Convenience type that holds the [`UnboundedTx`] and [`UnboundedRx`].
22#[derive(Debug)]
23pub struct Channel<T> {
24    pub tx: UnboundedTx<T>,
25    pub rx: UnboundedRx<T>,
26}
27
28impl<T> Channel<T> {
29    /// Construct a new unbounded [`Channel`].
30    pub fn new() -> Self {
31        let (tx, rx) = mpsc_unbounded();
32        Self { tx, rx }
33    }
34}
35
36impl<T> Default for Channel<T> {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42#[derive(Debug, Clone)]
43pub struct UnboundedTx<T> {
44    pub tx: tokio::sync::mpsc::UnboundedSender<T>,
45}
46
47impl<T> UnboundedTx<T> {
48    pub fn new(tx: tokio::sync::mpsc::UnboundedSender<T>) -> Self {
49        Self { tx }
50    }
51}
52
53impl<T> Tx for UnboundedTx<T>
54where
55    T: Debug + Clone + Send,
56{
57    type Item = T;
58    type Error = tokio::sync::mpsc::error::SendError<T>;
59
60    fn send<Item: Into<Self::Item>>(&self, item: Item) -> Result<(), Self::Error> {
61        self.tx.send(item.into())
62    }
63}
64
65impl<T> Unrecoverable for tokio::sync::mpsc::error::SendError<T> {
66    fn is_unrecoverable(&self) -> bool {
67        true
68    }
69}
70
71impl<T> Sink<T> for UnboundedTx<T> {
72    type Error = tokio::sync::mpsc::error::SendError<T>;
73
74    fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
75        // UnboundedTx is always ready
76        Poll::Ready(Ok(()))
77    }
78
79    fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
80        self.tx.send(item)
81    }
82
83    fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
84        // UnboundedTx does not buffer, so no flushing is required
85        Poll::Ready(Ok(()))
86    }
87
88    fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
89        // UnboundedTx requires no closing logic
90        Poll::Ready(Ok(()))
91    }
92}
93
94#[derive(Debug, Constructor)]
95pub struct UnboundedRx<T> {
96    pub rx: tokio::sync::mpsc::UnboundedReceiver<T>,
97}
98
99/// Blocking iteration: `next` returns a waiting message at once, and otherwise blocks the thread
100/// until one arrives, or returns `None` once every sender is dropped and the channel is drained.
101///
102/// # Panics
103/// `next` panics if it has to wait while called from an asynchronous context, as
104/// [`UnboundedReceiver::blocking_recv`](tokio::sync::mpsc::UnboundedReceiver::blocking_recv)
105/// does. Iterate on a thread of its own, such as one from `spawn_blocking`, or use the
106/// [`Stream`] impl instead.
107impl<T> Iterator for UnboundedRx<T> {
108    type Item = T;
109
110    fn next(&mut self) -> Option<Self::Item> {
111        match self.rx.try_recv() {
112            Ok(event) => Some(event),
113            // Parks the thread rather than polling `try_recv` again, which would spin a core
114            // for as long as the channel stays empty.
115            Err(tokio::sync::mpsc::error::TryRecvError::Empty) => self.rx.blocking_recv(),
116            Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => None,
117        }
118    }
119}
120
121impl<T> UnboundedRx<T> {
122    /// Number of messages waiting in the channel.
123    ///
124    /// Approximate while senders run: a snapshot that may be stale by the time it is read.
125    pub fn len(&self) -> usize {
126        self.rx.len()
127    }
128
129    /// Whether no message waits in the channel. Approximate, like [`len`](Self::len).
130    pub fn is_empty(&self) -> bool {
131        self.rx.is_empty()
132    }
133
134    pub fn into_stream(self) -> tokio_stream::wrappers::UnboundedReceiverStream<T> {
135        tokio_stream::wrappers::UnboundedReceiverStream::new(self.rx)
136    }
137}
138
139impl<T> Stream for UnboundedRx<T> {
140    type Item = T;
141
142    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
143        self.rx.poll_recv(cx)
144    }
145}
146
147#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)]
148pub struct ChannelTxDroppable<ChannelTx> {
149    pub state: ChannelState<ChannelTx>,
150}
151
152impl<ChannelTx> ChannelTxDroppable<ChannelTx> {
153    pub fn new(tx: ChannelTx) -> Self {
154        Self {
155            state: ChannelState::Active(tx),
156        }
157    }
158
159    pub fn new_disabled() -> Self {
160        Self {
161            state: ChannelState::Disabled,
162        }
163    }
164
165    pub fn disable(&mut self) {
166        self.state = ChannelState::Disabled
167    }
168}
169
170#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, Display)]
171pub enum ChannelState<Tx> {
172    Active(Tx),
173    Disabled,
174}
175
176impl<ChannelTx> ChannelTxDroppable<ChannelTx>
177where
178    ChannelTx: Tx,
179{
180    pub fn send(&mut self, item: ChannelTx::Item) {
181        let ChannelState::Active(tx) = &self.state else {
182            return;
183        };
184
185        if tx.send(item).is_err() {
186            let name = std::any::type_name::<ChannelTx::Item>();
187            warn!(
188                name,
189                "ChannelTxDroppable receiver dropped - items will no longer be sent"
190            );
191            self.state = ChannelState::Disabled
192        }
193    }
194}
195pub fn mpsc_unbounded<T>() -> (UnboundedTx<T>, UnboundedRx<T>) {
196    let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
197    (UnboundedTx::new(tx), UnboundedRx::new(rx))
198}
199
200#[cfg(test)]
201#[allow(clippy::unwrap_used)] // Test code: panicking on a failed send is the assertion
202mod tests {
203    use super::*;
204
205    #[test]
206    fn len_counts_messages_waiting_in_the_channel() {
207        let (tx, mut rx) = mpsc_unbounded::<u8>();
208        assert!(rx.is_empty());
209
210        tx.send(1).unwrap();
211        tx.send(2).unwrap();
212        assert_eq!(rx.len(), 2);
213
214        assert_eq!(Iterator::next(&mut rx), Some(1));
215        assert_eq!(rx.len(), 1);
216        assert!(!rx.is_empty());
217    }
218
219    #[test]
220    fn next_waits_for_a_message_on_an_empty_channel() {
221        let (tx, mut rx) = mpsc_unbounded::<u8>();
222
223        let sender = std::thread::spawn(move || {
224            std::thread::sleep(std::time::Duration::from_millis(50));
225            tx.send(7).unwrap();
226            // `tx` drops here, disconnecting the channel once 7 is taken.
227        });
228
229        assert_eq!(Iterator::next(&mut rx), Some(7));
230        assert_eq!(Iterator::next(&mut rx), None);
231        sender.join().unwrap();
232    }
233
234    // A spinning `next` would hang here instead of panicking.
235    #[tokio::test]
236    #[should_panic(expected = "Cannot block the current thread")]
237    async fn next_panics_if_it_must_wait_inside_an_async_context() {
238        let (_tx, mut rx) = mpsc_unbounded::<u8>();
239        let _ = Iterator::next(&mut rx);
240    }
241
242    #[test]
243    fn next_drains_waiting_messages_after_the_senders_are_dropped() {
244        let (tx, mut rx) = mpsc_unbounded::<u8>();
245        tx.send(1).unwrap();
246        drop(tx);
247
248        assert_eq!(Iterator::next(&mut rx), Some(1));
249        assert_eq!(Iterator::next(&mut rx), None);
250    }
251}