//! Abstract implementation of a channel manager use crate::mgr::map::OpenEntry; use crate::{Error, Result}; use async_trait::async_trait; use futures::channel::oneshot; use futures::future::{FutureExt, Shared}; use rand::Rng; use std::hash::Hash; use std::time::Duration; use tor_error::internal; mod map; /// Trait to describe as much of a /// [`Channel`](tor_proto::channel::Channel) as `AbstractChanMgr` /// needs to use. pub(crate) trait AbstractChannel: Clone { /// Identity type for the other side of the channel. type Ident: Hash + Eq + Clone; /// Return this channel's identity. fn ident(&self) -> &Self::Ident; /// Return true if this channel is usable. /// /// A channel might be unusable because it is closed, because it has /// hit a bug, or for some other reason. We don't return unusable /// channels back to the user. fn is_usable(&self) -> bool; /// Return the amount of time a channel has not been in use. /// Return None if the channel is currently in use. fn duration_unused(&self) -> Option; } /// Trait to describe how channels are created. #[async_trait] pub(crate) trait ChannelFactory { /// The type of channel that this factory can build. type Channel: AbstractChannel; /// Type that explains how to build a channel. type BuildSpec; /// Construct a new channel to the destination described at `target`. /// /// This function must take care of all timeouts, error detection, /// and so on. /// /// It should not retry; that is handled at a higher level. async fn build_channel(&self, target: &Self::BuildSpec) -> Result; } /// A type- and network-agnostic implementation for /// [`ChanMgr`](crate::ChanMgr). /// /// This type does the work of keeping track of open channels and /// pending channel requests, launching requests as needed, waiting /// for pending requests, and so forth. /// /// The actual job of launching connections is deferred to a ChannelFactory /// type. pub(crate) struct AbstractChanMgr { /// A 'connector' object that we use to create channels. connector: CF, /// A map from ed25519 identity to channel, or to pending channel status. channels: map::ChannelMap, } /// Type alias for a future that we wait on to see when a pending /// channel is done or failed. type Pending = Shared>>; /// Type alias for the sender we notify when we complete a channel (or /// fail to complete it). type Sending = oneshot::Sender>; impl AbstractChanMgr { /// Make a new empty channel manager. pub(crate) fn new(connector: CF) -> Self { AbstractChanMgr { connector, channels: map::ChannelMap::new(), } } /// Remove every unusable entry from this channel manager. #[cfg(test)] pub(crate) fn remove_unusable_entries(&self) -> Result<()> { self.channels.remove_unusable() } /// Helper: return the objects used to inform pending tasks /// about a newly open or failed channel. fn setup_launch(&self) -> (map::ChannelState, Sending) { let (snd, rcv) = oneshot::channel(); let shared = rcv.shared(); (map::ChannelState::Building(shared), snd) } /// Get a channel whose identity is `ident`. /// /// If a usable channel exists with that identity, return it. /// /// If no such channel exists already, and none is in progress, /// launch a new request using `target`, which must match `ident`. /// /// If no such channel exists already, but we have one that's in /// progress, wait for it to succeed or fail. pub(crate) async fn get_or_launch( &self, ident: <::Channel as AbstractChannel>::Ident, target: CF::BuildSpec, ) -> Result { use map::ChannelState::*; /// Possible actions that we'll decide to take based on the /// channel's initial state. enum Action { /// We found no channel. We're going to launch a new one, /// then tell everybody about it. Launch(Sending), /// We found an in-progress attempt at making a channel. /// We're going to wait for it to finish. Wait(Pending), /// We found a usable channel. We're going to return it. Return(Result), } /// How many times do we try? const N_ATTEMPTS: usize = 2; // TODO(nickm): It would be neat to use tor_retry instead. let mut last_err = None; for _ in 0..N_ATTEMPTS { // First, see what state we're in, and what we should do // about it. let action = self .channels .change_state(&ident, |oldstate| match oldstate { Some(Open(ref ent)) => { if ent.channel.is_usable() { // Good channel. Return it. let action = Action::Return(Ok(ent.channel.clone())); (oldstate, action) } else { // Unusable channel. Move to the Building // state and launch a new channel. let (newstate, send) = self.setup_launch(); let action = Action::Launch(send); (Some(newstate), action) } } Some(Building(ref pending)) => { let action = Action::Wait(pending.clone()); (oldstate, action) } Some(Poisoned(_)) => { // We should never be able to see this state; this // is a bug. ( None, Action::Return(Err(Error::Internal(internal!( "Found a poisoned entry" )))), ) } None => { // No channel. Move to the Building // state and launch a new channel. let (newstate, send) = self.setup_launch(); let action = Action::Launch(send); (Some(newstate), action) } })?; // Now we act based on the channel. match action { // Easy case: we have an error or a channel to return. Action::Return(v) => { return v; } // There's an in-progress channel. Wait for it. Action::Wait(pend) => match pend.await { Ok(Ok(chan)) => return Ok(chan), Ok(Err(e)) => { last_err = Some(e); } Err(_) => { last_err = Some(Error::Internal(internal!("channel build task disappeared"))); } }, // We need to launch a channel. Action::Launch(send) => match self.connector.build_channel(&target).await { Ok(chan) => { // The channel got built: remember it, tell the // others, and return it. self.channels.replace( ident.clone(), Open(OpenEntry { channel: chan.clone(), max_unused_duration: Duration::from_secs( rand::thread_rng().gen_range(180..270), ), }), )?; // It's okay if all the receivers went away: // that means that nobody was waiting for this channel. let _ignore_err = send.send(Ok(chan.clone())); return Ok(chan); } Err(e) => { // The channel failed. Make it non-pending, tell the // others, and set the error. self.channels.remove(&ident)?; // (As above) let _ignore_err = send.send(Err(e.clone())); last_err = Some(e); } }, } } Err(last_err.unwrap_or_else(|| Error::Internal(internal!("no error was set!?")))) } /// Expire any channels that have been unused longer than /// their maximum unused duration assigned during creation. /// /// Return a duration from now until next channel expires. /// /// If all channels are in use or there are no open channels, /// return 180 seconds which is the minimum value of /// max_unused_duration. pub(crate) fn expire_channels(&self) -> Duration { self.channels.expire_channels() } /// Test only: return the current open usable channel with a given /// `ident`, if any. #[cfg(test)] pub(crate) fn get_nowait( &self, ident: &<::Channel as AbstractChannel>::Ident, ) -> Option { use map::ChannelState::*; match self.channels.get(ident) { Ok(Some(Open(ref ent))) if ent.channel.is_usable() => Some(ent.channel.clone()), _ => None, } } } #[cfg(test)] mod test { #![allow(clippy::unwrap_used)] use super::*; use crate::Error; use futures::join; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tor_error::bad_api_usage; use tor_rtcompat::{task::yield_now, test_with_one_runtime, Runtime}; struct FakeChannelFactory { runtime: RT, } #[derive(Clone, Debug)] struct FakeChannel { ident: u32, mood: char, closing: Arc, detect_reuse: Arc, } impl PartialEq for FakeChannel { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.detect_reuse, &other.detect_reuse) } } impl AbstractChannel for FakeChannel { type Ident = u32; fn ident(&self) -> &u32 { &self.ident } fn is_usable(&self) -> bool { !self.closing.load(Ordering::SeqCst) } fn duration_unused(&self) -> Option { None } } impl FakeChannel { fn start_closing(&self) { self.closing.store(true, Ordering::SeqCst); } } impl FakeChannelFactory { fn new(runtime: RT) -> Self { FakeChannelFactory { runtime } } } #[async_trait] impl ChannelFactory for FakeChannelFactory { type Channel = FakeChannel; type BuildSpec = (u32, char); async fn build_channel(&self, target: &Self::BuildSpec) -> Result { yield_now().await; let (ident, mood) = *target; match mood { // "X" means never connect. '❌' | '🔥' => return Err(Error::UnusableTarget(bad_api_usage!("emoji"))), // "zzz" means wait for 15 seconds then succeed. '💤' => { self.runtime.sleep(Duration::new(15, 0)).await; } _ => {} } Ok(FakeChannel { ident, mood, closing: Arc::new(AtomicBool::new(false)), detect_reuse: Default::default(), }) } } #[test] fn connect_one_ok() { test_with_one_runtime!(|runtime| async { let cf = FakeChannelFactory::new(runtime); let mgr = AbstractChanMgr::new(cf); let target = (413, '!'); let chan1 = mgr.get_or_launch(413, target).await.unwrap(); let chan2 = mgr.get_or_launch(413, target).await.unwrap(); assert_eq!(chan1, chan2); let chan3 = mgr.get_nowait(&413).unwrap(); assert_eq!(chan1, chan3); }); } #[test] fn connect_one_fail() { test_with_one_runtime!(|runtime| async { let cf = FakeChannelFactory::new(runtime); let mgr = AbstractChanMgr::new(cf); // This is set up to always fail. let target = (999, '❌'); let res1 = mgr.get_or_launch(999, target).await; assert!(matches!(res1, Err(Error::UnusableTarget(_)))); let chan3 = mgr.get_nowait(&999); assert!(chan3.is_none()); }); } #[test] fn test_concurrent() { test_with_one_runtime!(|runtime| async { let cf = FakeChannelFactory::new(runtime); let mgr = AbstractChanMgr::new(cf); // TODO(nickm): figure out how to make these actually run // concurrently. Right now it seems that they don't actually // interact. let (ch3a, ch3b, ch44a, ch44b, ch86a, ch86b) = join!( mgr.get_or_launch(3, (3, 'a')), mgr.get_or_launch(3, (3, 'b')), mgr.get_or_launch(44, (44, 'a')), mgr.get_or_launch(44, (44, 'b')), mgr.get_or_launch(86, (86, '❌')), mgr.get_or_launch(86, (86, '🔥')), ); let ch3a = ch3a.unwrap(); let ch3b = ch3b.unwrap(); let ch44a = ch44a.unwrap(); let ch44b = ch44b.unwrap(); let err_a = ch86a.unwrap_err(); let err_b = ch86b.unwrap_err(); assert_eq!(ch3a, ch3b); assert_eq!(ch44a, ch44b); assert_ne!(ch44a, ch3a); assert!(matches!(err_a, Error::UnusableTarget(_))); assert!(matches!(err_b, Error::UnusableTarget(_))); }); } #[test] fn unusable_entries() { test_with_one_runtime!(|runtime| async { let cf = FakeChannelFactory::new(runtime); let mgr = AbstractChanMgr::new(cf); let (ch3, ch4, ch5) = join!( mgr.get_or_launch(3, (3, 'a')), mgr.get_or_launch(4, (4, 'a')), mgr.get_or_launch(5, (5, 'a')), ); let ch3 = ch3.unwrap(); let _ch4 = ch4.unwrap(); let ch5 = ch5.unwrap(); ch3.start_closing(); ch5.start_closing(); let ch3_new = mgr.get_or_launch(3, (3, 'b')).await.unwrap(); assert_ne!(ch3, ch3_new); assert_eq!(ch3_new.mood, 'b'); mgr.remove_unusable_entries().unwrap(); assert!(mgr.get_nowait(&3).is_some()); assert!(mgr.get_nowait(&4).is_some()); assert!(mgr.get_nowait(&5).is_none()); }); } }