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

            
3
use derive_deftly::Deftly;
4
use std::{num::NonZeroUsize, time::Instant};
5

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

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

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

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

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

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

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

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

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

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

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

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

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

            
183
    use std::time::Duration;
184

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

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

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