diff --git a/crates/tor-circmgr/src/err.rs b/crates/tor-circmgr/src/err.rs index 9db56b589..41498e20e 100644 --- a/crates/tor-circmgr/src/err.rs +++ b/crates/tor-circmgr/src/err.rs @@ -146,7 +146,7 @@ impl HasKind for Error { E::Protocol(e) => e.kind(), E::State(e) => e.kind(), E::GuardMgr(e) => e.kind(), - E::Guard(_) => EK::NoPath, + E::Guard(e) => e.kind(), E::ExpiredConsensus => EK::DirectoryExpired, E::Spawn { cause, .. } => cause.kind(), } diff --git a/crates/tor-circmgr/src/mgr.rs b/crates/tor-circmgr/src/mgr.rs index 2da2cde2a..b46b81afc 100644 --- a/crates/tor-circmgr/src/mgr.rs +++ b/crates/tor-circmgr/src/mgr.rs @@ -780,11 +780,27 @@ impl AbstractCircMgr { } } Err(e) => { - // We couldn't pick the action! This is unusual; wait - // a little while before we try again. + // We couldn't pick the action! info!("Couldn't pick action for circuit attempt {}: {}", n, &e); + let small_delay = Duration::from_millis(50); + let wait_for_action = match &e { + Error::Guard(tor_guardmgr::PickGuardError::AllGuardsDown { + retry_at: Some(instant), + }) => { + // If we failed because all guards are down, that's fine: we just wait until + // the next guard is retriable. + instant.saturating_duration_since(self.runtime.now()) + small_delay + } + _ => { + // Any other errors are pretty unusual; wait a little while, then try again. + small_delay + } + }; retry_err.push(e); - let wait_for_action = Duration::from_millis(50); + if remaining < wait_for_action { + // We can't wait long enough. Call this failed now. + break; + } self.runtime .sleep(std::cmp::min(remaining, wait_for_action)) .await; @@ -869,12 +885,24 @@ impl AbstractCircMgr { // Okay, we need to launch circuits here. let parallelism = std::cmp::max(1, self.builder.launch_parallelism(usage)); let mut plans = Vec::new(); + let mut errs = Vec::new(); for _ in 0..parallelism { - let (pending, plan) = self.plan_by_usage(dir, usage)?; - list.add_pending_circ(pending); - plans.push(plan); + match self.plan_by_usage(dir, usage) { + Ok((pending, plan)) => { + list.add_pending_circ(pending); + plans.push(plan); + } + Err(e) => errs.push(e), + } + } + if !plans.is_empty() { + Ok(Action::Build(plans)) + } else if let Some(last_err) = errs.pop() { + Err(last_err) + } else { + // we didn't even try to plan anything! + Err(internal!("no plans were built, but no errors were found").into()) } - Ok(Action::Build(plans)) } /// Execute an action returned by pick-action, and return the