chanmgr: Discard channels after sufficient disuse
Right now, tor-chanmgr doesn't actually drop channels for being too old or unused; we should fix that. (Fortunately, relays will close them for us.)
Here's the general approach:
1. Backend: tracking channel disuse
- For every
tor_proto::Channel
, we should keep a timestamp that tells us when the channel was last "used". A channel counts as being "used" whenever it has at least one circuit on it; it only becomes unused when it has no circuits.
We'll probably want to keep track of when a channel is used inside the tor_proto::channel
code, since that's the code that sees when circuits are actually attached or removed to a channel. For the purposes of this ticket, only CircEnt::Open
and CircEnt::Opening
circuits count; CircEnt::DestroySent
circuits don't count.
These timestamps can use the mechanism from tor_proto::util::ts
(or something like it) to ensure that they are as cheap as possible. (Or they might want to hold a coarsetime::Instant
if no atomic behavior is needed. But if we make that choice, we should add a new struct to wrap coarsetime::Instant
to make it easily replaceable with another cheap-timestamp mechanism.)
These timestamps should be exposed as a method from Channel, something like this:
/// If the channel is not in use, return the amount of time it has had with no circuits.
///
/// Return None if the channel is currently in use.
pub fn duration_unused_at(&self, now: Instant) -> Option<Duration> { ... }
2. Discarding disused channels
In tor_changmgr, we'll want code that drops channels that have been disused for too long. This code should probably be in several parts. First, we'll need a function with a signature something like this:
/// Expire all channels that have been unused for too long.
///
/// Return a Duration until the next time at which a channel _could_ expire.
fn expire_channels(&self, now: Instant) -> Duration { ... }
Then we'll need a background task with a loop like this:
loop {
let delay = if let Some(cm) = Weak::upgrade(&cm) {
cm.expire_channels(runtime.now())
} else {
return; // channel manager is closed.
}
runtime.sleep(delay).await; // This will sometimes be an underestimate, but it's no big deal; we just sleep some more.
}
And finally we'll need some way to configure the actual "maximum unused duration" for a channel. This should probably be a Duration associated with the channel in ChannelState::Open; for consistency, it should be chosen at random when the channel is built, from the range 180..270 seconds.
(The lower bound of 180 seconds means that if we have no unused channels, the expire_channels()
call can always safety return 180 seconds.)