use derive_deftly::Deftly;
use std::num::NonZeroUsize;
use web_time_compat::{Instant, InstantExt};
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) struct TunnelActivity {
inner: Inner,
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
enum Inner {
#[default]
NeverUsed,
Disused {
since: Instant,
},
InUse {
n_open_streams: NonZeroUsize,
},
}
#[derive(Debug, Deftly)]
#[must_use]
#[derive_deftly_adhoc]
pub(crate) struct InTunnelActivity {
_prevent_create: (),
}
impl Drop for InTunnelActivity {
fn drop(&mut self) {
panic!("Dropped an InTunnelActivity without giving it to dec_streams()")
}
}
derive_deftly::derive_deftly_adhoc! {
InTunnelActivity:
const _ : () = {
$(
assert!(! std::mem::needs_drop::<$ftype>());
)
};
}
impl InTunnelActivity {
pub(crate) fn consume_and_forget(self) {
std::mem::forget(self);
}
}
impl TunnelActivity {
pub(crate) fn never_used() -> Self {
Self::default()
}
pub(crate) fn inc_streams(&mut self) -> InTunnelActivity {
self.inner = Inner::InUse {
n_open_streams: NonZeroUsize::new(self.n_open_streams() + 1)
.expect("overflow on stream count"),
};
InTunnelActivity {
_prevent_create: (),
}
}
pub(crate) fn dec_streams(&mut self, token: InTunnelActivity) {
token.consume_and_forget();
let Inner::InUse { n_open_streams } = &mut self.inner else {
panic!("Tried to decrement 0!");
};
if let Some(new_value) = NonZeroUsize::new(n_open_streams.get() - 1) {
*n_open_streams = new_value;
} else {
self.inner = Inner::Disused {
since: Instant::get(),
};
}
}
pub(crate) fn n_open_streams(&self) -> usize {
match self.inner {
Inner::NeverUsed | Inner::Disused { .. } => 0,
Inner::InUse { n_open_streams } => n_open_streams.get(),
}
}
pub(crate) fn disused_since(&self) -> Option<Instant> {
match self.inner {
Inner::Disused { since } => Some(since),
Inner::NeverUsed | Inner::InUse { .. } => None,
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use std::time::Duration;
use super::*;
use rand::seq::SliceRandom as _;
use tor_basic_utils::test_rng::testing_rng;
#[test]
fn ordering() {
use Inner::*;
let t1 = Instant::get();
let t2 = t1 + Duration::new(60, 0);
let t3 = t2 + Duration::new(120, 0);
let sorted = vec![
NeverUsed,
NeverUsed,
Disused { since: t1 },
Disused { since: t2 },
Disused { since: t3 },
InUse {
n_open_streams: 5.try_into().unwrap(),
},
InUse {
n_open_streams: 10.try_into().unwrap(),
},
];
let mut scrambled = sorted.clone();
let mut rng = testing_rng();
for _ in 0..=8 {
scrambled.shuffle(&mut rng);
scrambled.sort();
assert_eq!(&scrambled, &sorted);
}
}
}