rustrade_integration/
channel.rs1use 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#[derive(Debug)]
23pub struct Channel<T> {
24 pub tx: UnboundedTx<T>,
25 pub rx: UnboundedRx<T>,
26}
27
28impl<T> Channel<T> {
29 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 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 Poll::Ready(Ok(()))
86 }
87
88 fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
89 Poll::Ready(Ok(()))
91 }
92}
93
94#[derive(Debug, Constructor)]
95pub struct UnboundedRx<T> {
96 pub rx: tokio::sync::mpsc::UnboundedReceiver<T>,
97}
98
99impl<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 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 pub fn len(&self) -> usize {
126 self.rx.len()
127 }
128
129 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)] mod 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 });
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 #[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}