1
//! [`SinkTrySend`]
2

            
3
use std::error::Error;
4
use std::pin::Pin;
5
use std::sync::Arc;
6

            
7
use futures::Sink;
8
use futures::channel::mpsc;
9

            
10
use derive_deftly::{Deftly, define_derive_deftly};
11
use thiserror::Error;
12

            
13
//---------- principal API ----------
14

            
15
/// A [`Sink`] with a `try_send` method like [`futures::channel::mpsc::Sender`]'s.
16
pub trait SinkTrySend<T>: Sink<T> {
17
    /// Errors that is not disconnected, or full
18
    type Error: SinkTrySendError;
19

            
20
    /// Try to send a message `msg`
21
    ///
22
    /// If this returns with an error indicating that the stream is full,
23
    /// *No* arrangements will have been made for a wakeup when space becomes available.
24
    ///
25
    /// If the send fails, `item` is dropped.
26
    /// If you need it back, use [`try_send_or_return`](SinkTrySend::try_send_or_return),
27
    ///
28
    /// (When implementing the trait, implement `try_send_or_return`, *not* this method.)
29
244
    fn try_send(self: Pin<&mut Self>, item: T) -> Result<(), <Self as SinkTrySend<T>>::Error> {
30
244
        self.try_send_or_return(item)
31
244
            .map_err(|(error, _item)| error)
32
244
    }
33

            
34
    /// Try to send a message `msg`
35
    ///
36
    /// Like [`try_send`](SinkTrySend::try_send),
37
    /// but if the send fails, the item is returned.
38
    ///
39
    /// (When implementing the trait, implement this method.)
40
    fn try_send_or_return(
41
        self: Pin<&mut Self>,
42
        item: T,
43
    ) -> Result<(), (<Self as SinkTrySend<T>>::Error, T)>;
44
}
45

            
46
/// Error from [`SinkTrySend::try_send`]
47
///
48
/// See also [`ErasedSinkTrySendError`] which can often
49
/// be usefully used when an implementation of `SinkTrySendError` is needed.
50
pub trait SinkTrySendError: Error + 'static {
51
    /// The stream was full.
52
    ///
53
    /// *No* arrangements will have been made for a wakeup when space becomes available.
54
    ///
55
    /// Corresponds to [`futures::channel::mpsc::TrySendError::is_full`]
56
    fn is_full(&self) -> bool;
57

            
58
    /// The stream has disconnected
59
    ///
60
    /// Corresponds to [`futures::channel::mpsc::TrySendError::is_disconnected`]
61
    fn is_disconnected(&self) -> bool;
62
}
63

            
64
//---------- macrology - this has to come here, ideally all in one go ----------
65

            
66
#[rustfmt::skip] // rustfmt makes a complete hash of this
67
define_derive_deftly! {
68
    /// Implements various things which handle `full` and `disconnected`
69
    ///
70
    /// # Generates
71
    ///
72
    ///  * `SinkTrySendError for`ErasedSinkTrySendError`
73
    ///  * `From<E: SinkTrySendError> for`ErasedSinkTrySendError`
74
    ///  * [`handle_mpsc_error`]
75
    ///
76
    /// Use of macros avoids copypaste errors like
77
    /// `fn is_full(..) { self.is_disconnected() }`.
78
    ErasedSinkTrySendError expect items:
79

            
80
    ${defcond PREDICATE vmeta(predicate)}
81
    ${define PREDICATE { $<is_ ${snake_case $vname}> }}
82

            
83
    impl SinkTrySendError for ErasedSinkTrySendError {
84
        $(
85
            ${when PREDICATE}
86

            
87
12
            fn $PREDICATE(&self) -> bool {
88
                matches!(self, $vtype)
89
            }
90
        )
91
    }
92

            
93
    impl ErasedSinkTrySendError {
94
        /// Obtain an `ErasedSinkTrySendError` from a concrete `SinkTrySendError`
95
        //
96
        // (Can't be a `From` impl because it conflicts with the identity `From<T> for T`.)
97
24
        pub fn from<E>(e: E) -> ErasedSinkTrySendError
98
24
        where E: SinkTrySendError + Send + Sync
99
        {
100
            $(
101
                ${when PREDICATE}
102
                if e.$PREDICATE() {
103
                    $vtype
104
                } else
105
            )
106
                /* else */ {
107
                    let e = Arc::new(e);
108
                    // Avoid generating a nested ErasedSinkTrySendError.
109
                    // Is it *already* an ESTSE (necessarily, then, an `Other`?)
110
                    //
111
                    // TODO replace this with a call to `downcast_value` from arti!2460
112
                    let e2 = e.clone();
113
                    match Arc::downcast(e2) {
114
                        Ok::<Arc<ErasedSinkTrySendError>, _>(y2) => {
115
                            drop(e); // Drop the original
116
                            let inner: ErasedSinkTrySendError =
117
                                Arc::into_inner(y2).expect(
118
              "somehow we weren't the only owner, despite us just having made an Arc!"
119
                                );
120
                            return inner;
121
                        }
122
                        Err(other_e2) => {
123
                            drop(other_e2);
124
                            // We need to use e, not other_e2, because Arc::downcast
125
                            // returns dyn Any but we need dyn SinkTrySendError.
126
                            ErasedSinkTrySendError::Other(e)
127
                        },
128
                    }
129
                }
130
        }
131
    }
132

            
133
    fn handle_mpsc_error<T>(me: mpsc::TrySendError<T>) -> (ErasedSinkTrySendError, T) {
134
        let error = $(
135
            ${when PREDICATE}
136

            
137
            if me.$PREDICATE() {
138
                $vtype
139
            } else
140
        )
141
            /* else */ {
142
                $ttype::Other(Arc::new(MpscOtherSinkTrySendError {}))
143
            };
144
        (error, me.into_inner())
145
    }
146
}
147

            
148
//---------- helper - erased error ----------
149

            
150
/// Type-erased error for [`SinkTrySend::try_send`]
151
///
152
/// Provided for situations where providing a concrete error type is awkward.
153
///
154
/// `futures::channel::mpsc::Sender` wants this because when its `try_send` method fails,
155
/// it is not possible to extract both the sent item, and the error!
156
///
157
/// `tor_memquota::mq_queue::Sender` wants this because the types of the error return
158
/// from `its `try_send` would otherwise be tainted by complex generics,
159
/// including its private `Entry` type.
160
#[derive(Debug, Error, Clone, Deftly)]
161
#[derive_deftly(ErasedSinkTrySendError)]
162
#[allow(clippy::exhaustive_enums)] // Adding other variants would be a breaking change anyway
163
pub enum ErasedSinkTrySendError {
164
    /// The stream was full.
165
    ///
166
    /// *No* arrangements will have been made for a wakeup when space becomes available.
167
    ///
168
    /// Corresponds to [`SinkTrySendError::is_full`]
169
    #[error("stream full (backpressure)")]
170
    #[deftly(predicate)]
171
    Full,
172

            
173
    /// The stream has disconnected
174
    ///
175
    /// Corresponds to [`SinkTrySendError::is_disconnected`]
176
    #[error("stream disconnected")]
177
    #[deftly(predicate)]
178
    Disconnected,
179

            
180
    /// Something else went wrong
181
    #[error("failed to convey data")]
182
    Other(#[source] Arc<dyn Error + Send + Sync + 'static>),
183
}
184

            
185
//---------- impl for futures::channel::mpsc ----------
186

            
187
/// [`mpsc::Sender::try_send`] returned an uncategorisable error
188
///
189
/// Both `.full()` and `.disconnected()` returned `false`.
190
/// We could call [`mpsc::TrySendError::into_send_error`] but then we don't get the payload.
191
/// In the future, we might replace this type with a type alias for [`mpsc::SendError`].
192
///
193
/// When returned from `<mpsc::Sender::SinkTrySend::try_send`,
194
/// this is wrapped in [`ErasedSinkTrySendError::Other`].
195
#[derive(Debug, Error)]
196
#[error("mpsc::Sender::try_send returned an error which is neither .full() nor .disconnected()")]
197
#[non_exhaustive]
198
pub struct MpscOtherSinkTrySendError {}
199

            
200
impl<T> SinkTrySend<T> for mpsc::Sender<T> {
201
    // Ideally we would just use [`mpsc::SendError`].
202
    // But `mpsc::TrySendError` lacks an `into_parts` method that gives both `SendError` and `T`.
203
    type Error = ErasedSinkTrySendError;
204

            
205
36
    fn try_send_or_return(
206
36
        self: Pin<&mut Self>,
207
36
        item: T,
208
36
    ) -> Result<(), (ErasedSinkTrySendError, T)> {
209
36
        let self_: &mut Self = Pin::into_inner(self);
210
36
        mpsc::Sender::try_send(self_, item).map_err(handle_mpsc_error)
211
36
    }
212
}
213

            
214
// `UnboundedSender` doesn't have a `try_send()` method,
215
// since `UnboundedSender` won't fail to send due to lack of space.
216
// But it may fail to send if the receiver has gone away.
217
// Regardless, we can still implement `SinkTrySend` and just use `unbounded_send()` instead,
218
// which is like a less-fallible version of `try_send()`.
219
impl<T> SinkTrySend<T> for mpsc::UnboundedSender<T> {
220
    // Ideally we would just use [`mpsc::SendError`].
221
    // But `mpsc::TrySendError` lacks an `into_parts` method that gives both `SendError` and `T`.
222
    type Error = ErasedSinkTrySendError;
223

            
224
208
    fn try_send_or_return(
225
208
        self: Pin<&mut Self>,
226
208
        item: T,
227
208
    ) -> Result<(), (ErasedSinkTrySendError, T)> {
228
208
        let self_: &mut Self = Pin::into_inner(self);
229
208
        mpsc::UnboundedSender::unbounded_send(self_, item).map_err(handle_mpsc_error)
230
208
    }
231
}
232

            
233
#[cfg(test)]
234
mod test {
235
    // @@ begin test lint list maintained by maint/add_warning @@
236
    #![allow(clippy::bool_assert_comparison)]
237
    #![allow(clippy::clone_on_copy)]
238
    #![allow(clippy::dbg_macro)]
239
    #![allow(clippy::mixed_attributes_style)]
240
    #![allow(clippy::print_stderr)]
241
    #![allow(clippy::print_stdout)]
242
    #![allow(clippy::single_char_pattern)]
243
    #![allow(clippy::unwrap_used)]
244
    #![allow(clippy::unchecked_time_subtraction)]
245
    #![allow(clippy::useless_vec)]
246
    #![allow(clippy::needless_pass_by_value)]
247
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
248
    #![allow(clippy::arithmetic_side_effects)] // don't mind potential panicking ops in tests
249
    #![allow(clippy::useless_format)] // srsly
250

            
251
    use super::*;
252
    use derive_deftly::derive_deftly_adhoc;
253
    use tor_error::ErrorReport as _;
254

            
255
    #[test]
256
    fn chk_erased_sink() {
257
        #[derive(Error, Clone, Debug, Deftly)]
258
        #[error("concrete {is_full} {is_disconnected}")]
259
        #[derive_deftly_adhoc]
260
        struct Concrete {
261
            is_full: bool,
262
            is_disconnected: bool,
263
        }
264

            
265
        derive_deftly_adhoc! {
266
            Concrete:
267

            
268
            impl SinkTrySendError for Concrete { $(
269
                fn $fname(&self) -> bool { self.$fname }
270
            ) }
271
        }
272

            
273
        for is_full in [false, true] {
274
            for is_disconnected in [false, true] {
275
                let c = Concrete {
276
                    is_full,
277
                    is_disconnected,
278
                };
279
                let e = ErasedSinkTrySendError::from(c.clone());
280
                let e2 = ErasedSinkTrySendError::from(e.clone());
281

            
282
                let cs = format!("concrete {is_full} {is_disconnected}");
283

            
284
                let es = if is_full {
285
                    format!("stream full (backpressure)")
286
                } else if is_disconnected {
287
                    format!("stream disconnected")
288
                } else {
289
                    format!("failed to convey data: {cs}")
290
                };
291

            
292
                assert_eq!(c.report().to_string(), format!("error: {cs}"));
293
                assert_eq!(e.report().to_string(), format!("error: {es}"));
294
                assert_eq!(e2.report().to_string(), format!("error: {es}"));
295
            }
296
        }
297
    }
298
}