1
//! Helpers for tracking whether a tunnel or circuit is still active.
2

            
3
use derive_deftly::Deftly;
4
use std::num::NonZeroUsize;
5
use web_time_compat::{Instant, InstantExt};
6

            
7
/// An object to track whether a tunnel or circuit should still be considered active.
8
///
9
/// The "active" status of a tunnel depends on whether it is in use for streams,
10
/// and if not, how much time has passed since it was last in use for streams.
11
///
12
/// # Ordering and aggregation
13
///
14
/// We rely on the ordering for `TunnelActivity` structs.
15
/// In particular, we depend on the property that a "more active"
16
/// `TunnelActivity` is greater than a less active one.
17
///
18
/// Specifically,
19
/// - a TunnelActivity with streams is "more active" than one without streams.
20
/// - a TunnelActivity that was last used for streams recently is "more active"
21
///   than one that was last used for streams more time ago.
22
/// - a TunnelActivity that was ever in use for streams is "more active"
23
///   than one that has never been used.
24
///
25
/// For implementation convenience, we do not generally keep a TunnelActivity
26
/// for an entire tunnel.
27
/// Instead, we keep a separate TunnelActivity for each hop of each circuit.
28
/// When we need to find the TunnelActivity of the tunnel as a whole,
29
/// we look for the TunnelActivity of the "most active" hop.
30
/// This _does not_ give an accurate count of all the streams on the
31
/// tunnel, but we don't generally care about that.
32
///
33
/// > We could instead _add_ the TunnelActivity for each hop,
34
/// > but that would be a bit more implementation effort to little benefit,
35
/// > and we'd need to avoid counting conflux join points twice.
36
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
37
pub(crate) struct TunnelActivity {
38
    /// Actual activity for the associated tunnel.
39
    inner: Inner,
40
}
41

            
42
/// Inner enumeration used to implement [`TunnelActivity`].
43
///
44
/// This is a separate type to keep it private.
45
//
46
// NOTE: Don't re-order these: we rely on the specific behavior of
47
// derive(PartialOrd) in order to get the behavior we want.
48
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
49
enum Inner {
50
    /// The tunnel has never been used for streams.
51
    #[default]
52
    NeverUsed,
53
    /// The tunnel was in use for streams, but has no streams right now.
54
    Disused {
55
        /// The time at which the last stream was closed.
56
        ///
57
        /// See note on [`TunnelActivity::disused_since`].
58
        since: Instant,
59
    },
60
    /// The tunnel has open streams.
61
    InUse {
62
        /// The number of open streams on this tunnel.
63
        n_open_streams: NonZeroUsize,
64
    },
65
}
66

            
67
/// A zero-sized token type returned for each call to [`TunnelActivity::inc_streams()`].
68
///
69
/// The caller is responsible for passing this object to [`TunnelActivity::dec_streams()`]
70
/// when the stream is no longer in use.
71
/// Otherwise, this type will panic when it is dropped.
72
#[derive(Debug, Deftly)]
73
#[must_use]
74
#[derive_deftly_adhoc]
75
pub(crate) struct InTunnelActivity {
76
    /// Prevent this type from being created from other modules.
77
    _prevent_create: (),
78
}
79

            
80
impl Drop for InTunnelActivity {
81
    fn drop(&mut self) {
82
        panic!("Dropped an InTunnelActivity without giving it to dec_streams()")
83
    }
84
}
85

            
86
// Assert that no member of InTunnelActivity actually has meaningful drop semantics.
87
//
88
// (This lets us call std::mem::forget() below with confidence.)
89
derive_deftly::derive_deftly_adhoc! {
90
    InTunnelActivity:
91
    const _ : () = {
92
        $(
93
            assert!(! std::mem::needs_drop::<$ftype>());
94
        )
95
    };
96
}
97

            
98
impl InTunnelActivity {
99
    /// Consume this token safely, without triggering its drop panic.
100
    ///
101
    /// Calling this method directly will invalidate the corresponding TunnelActivity's counter.
102
    /// Instead, you should usually pass this to [`TunnelActivity::dec_streams`]
103
408
    pub(crate) fn consume_and_forget(self) {
104
408
        std::mem::forget(self);
105
408
    }
106
}
107

            
108
impl TunnelActivity {
109
    /// Construct a new TunnelActivity for a tunnel that has never been used.
110
1044
    pub(crate) fn never_used() -> Self {
111
1044
        Self::default()
112
1044
    }
113

            
114
    /// Increase the number of streams on this tunnel by one.
115
416
    pub(crate) fn inc_streams(&mut self) -> InTunnelActivity {
116
416
        self.inner = Inner::InUse {
117
416
            n_open_streams: NonZeroUsize::new(self.n_open_streams() + 1)
118
416
                .expect("overflow on stream count"),
119
416
        };
120
416
        InTunnelActivity {
121
416
            _prevent_create: (),
122
416
        }
123
416
    }
124

            
125
    /// Decrease the number of streams on this tunnel by one.
126
104
    pub(crate) fn dec_streams(&mut self, token: InTunnelActivity) {
127
104
        token.consume_and_forget();
128
104
        let Inner::InUse { n_open_streams } = &mut self.inner else {
129
            panic!("Tried to decrement 0!");
130
        };
131

            
132
104
        if let Some(new_value) = NonZeroUsize::new(n_open_streams.get() - 1) {
133
24
            *n_open_streams = new_value;
134
80
        } else {
135
80
            self.inner = Inner::Disused {
136
80
                since: Instant::get(),
137
80
            };
138
80
        }
139
104
    }
140

            
141
    /// Return the number of open streams on this tunnel.
142
    ///
143
    /// (But see note on [`TunnelActivity`] documentation)
144
416
    pub(crate) fn n_open_streams(&self) -> usize {
145
416
        match self.inner {
146
132
            Inner::NeverUsed | Inner::Disused { .. } => 0,
147
284
            Inner::InUse { n_open_streams } => n_open_streams.get(),
148
        }
149
416
    }
150

            
151
    /// Return the time at which this tunnel was last in use.
152
    ///
153
    /// Returns None if the tunnel has open streams right now,
154
    /// or if it has never had any open streams.
155
    ///
156
    /// # A note about time
157
    ///
158
    /// The returned Instant value is a direct result of an earlier call to `Instant::get()`.
159
    /// It is not affected by any runtime mocking.
160
    pub(crate) fn disused_since(&self) -> Option<Instant> {
161
        match self.inner {
162
            Inner::Disused { since } => Some(since),
163
            Inner::NeverUsed | Inner::InUse { .. } => None,
164
        }
165
    }
166
}
167

            
168
#[cfg(test)]
169
mod test {
170
    // @@ begin test lint list maintained by maint/add_warning @@
171
    #![allow(clippy::bool_assert_comparison)]
172
    #![allow(clippy::clone_on_copy)]
173
    #![allow(clippy::dbg_macro)]
174
    #![allow(clippy::mixed_attributes_style)]
175
    #![allow(clippy::print_stderr)]
176
    #![allow(clippy::print_stdout)]
177
    #![allow(clippy::single_char_pattern)]
178
    #![allow(clippy::unwrap_used)]
179
    #![allow(clippy::unchecked_time_subtraction)]
180
    #![allow(clippy::useless_vec)]
181
    #![allow(clippy::needless_pass_by_value)]
182
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
183

            
184
    use std::time::Duration;
185

            
186
    use super::*;
187
    use rand::seq::SliceRandom as _;
188
    use tor_basic_utils::test_rng::testing_rng;
189

            
190
    #[test]
191
    fn ordering() {
192
        use Inner::*;
193
        let t1 = Instant::get();
194
        let t2 = t1 + Duration::new(60, 0);
195
        let t3 = t2 + Duration::new(120, 0);
196
        let sorted = vec![
197
            NeverUsed,
198
            NeverUsed,
199
            Disused { since: t1 },
200
            Disused { since: t2 },
201
            Disused { since: t3 },
202
            InUse {
203
                n_open_streams: 5.try_into().unwrap(),
204
            },
205
            InUse {
206
                n_open_streams: 10.try_into().unwrap(),
207
            },
208
        ];
209

            
210
        let mut scrambled = sorted.clone();
211
        let mut rng = testing_rng();
212
        for _ in 0..=8 {
213
            scrambled.shuffle(&mut rng);
214
            scrambled.sort();
215
            assert_eq!(&scrambled, &sorted);
216
        }
217
    }
218
}