Lines
0 %
Functions
Branches
100 %
//! Declare error types for the `tor-guardmgr` crate.
use futures::task::SpawnError;
use std::sync::Arc;
use std::time::Instant;
use tor_basic_utils::iter::FilterCount;
use tor_error::{Bug, ErrorKind, HasKind};
/// A error caused by a failure to pick a guard.
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PickGuardError {
/// All members of the current sample were down or unusable.
#[error(
"No usable guards. Rejected {} as down, then {} as pending, then \
{} as unsuitable to purpose, then {} with filter.",
running.display_frac_rejected(),
pending.display_frac_rejected(),
suitable.display_frac_rejected(),
filtered.display_frac_rejected(),
)]
AllGuardsDown {
/// The next time at which any guard will be retriable.
retry_at: Option<Instant>,
/// How many guards we rejected because they had failed too recently.
running: FilterCount,
/// How many guards we rejected because we are already probing them.
pending: FilterCount,
/// How many guards we rejected as unsuitable for the intended application.
suitable: FilterCount,
/// How many guards we rejected because of the provided filter.
filtered: FilterCount,
},
/// We have no usable fallback directories.
"No usable fallbacks. Rejected {} as not running, then {} as filtered.",
filtered.display_frac_rejected()
AllFallbacksDown {
/// The next time at which any fallback directory will back available.
/// The number of fallbacks that were believed to be running or down, after applying
/// the filter.
/// The number of fallbacks that satisfied our filter, or did not.
/// Tried to select guards or fallbacks from an empty list.
#[error("Tried to pick from an empty list")]
NoCandidatesAvailable,
/// An internal programming error occurred.
#[error("Internal error")]
Internal(#[from] Bug),
}
impl tor_error::HasKind for PickGuardError {
fn kind(&self) -> tor_error::ErrorKind {
use tor_error::ErrorKind as EK;
use PickGuardError as E;
match self {
E::AllFallbacksDown { .. } | E::AllGuardsDown { .. } => EK::TorAccessFailed,
E::NoCandidatesAvailable => EK::NoPath,
E::Internal(_) => EK::Internal,
impl tor_error::HasRetryTime for PickGuardError {
fn retry_time(&self) -> tor_error::RetryTime {
use tor_error::RetryTime as RT;
// Some errors contain times that we can just use.
E::AllGuardsDown {
retry_at: Some(when),
..
} => RT::At(*when),
E::AllFallbacksDown {
// If we don't know when the guards/fallbacks will be back up,
// though, then we should suggest a random delay.
E::AllGuardsDown { .. } | E::AllFallbacksDown { .. } => RT::AfterWaiting,
// We were asked to choose some kind of guard that doesn't exist in
// our current universe; that's not going to be come viable down the
// line.
E::NoCandidatesAvailable => RT::Never,
// Don't try to recover from internal errors.
E::Internal(_) => RT::Never,
/// An error caused while creating or updating a guard manager.
pub enum GuardMgrError {
/// An error manipulating persistent state
#[error("Problem accessing persistent guard state")]
State(#[from] tor_persist::Error),
/// Configuration is not valid or available
#[error("Invalid configuration")]
InvalidConfig(#[from] GuardMgrConfigError),
/// An error that occurred while trying to spawn a daemon task.
#[error("Unable to spawn {spawning}")]
Spawn {
/// What we were trying to spawn.
spawning: &'static str,
/// What happened when we tried to spawn it.
#[source]
cause: Arc<SpawnError>,
impl HasKind for GuardMgrError {
#[rustfmt::skip] // to preserve table in match
fn kind(&self) -> ErrorKind {
use GuardMgrError as G;
G::State(e) => e.kind(),
G::InvalidConfig(e) => e.kind(),
G::Spawn{ cause, .. } => cause.kind(),
impl GuardMgrError {
/// Construct a new `GuardMgrError` from a `SpawnError`.
pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> GuardMgrError {
GuardMgrError::Spawn {
spawning,
cause: Arc::new(err),
/// An error encountered while configuring or reconfiguring a guard manager
///
/// When this occurs during initial configuration, it will be returned wrapped
/// up in `GuardMgrError`.
/// When it occurs during reconfiguration, it is not exposed to caller:
/// instead, it is converted into a `tor_config::ReconfigureError`.
pub enum GuardMgrConfigError {
/// Specified configuration requires exclusive access to stored state, which we don't have
#[error("Configuration requires exclusive access to shared state, but another instance of Arti has the lock: {0}")]
NoLock(String),
impl From<GuardMgrConfigError> for tor_config::ReconfigureError {
fn from(g: GuardMgrConfigError) -> tor_config::ReconfigureError {
use tor_config::ReconfigureError as R;
use GuardMgrConfigError as G;
match g {
e @ G::NoLock(_) => R::UnsupportedSituation(e.to_string()),
impl HasKind for GuardMgrConfigError {
use ErrorKind as EK;
// `InvalidConfig` and `FeatureDisabled` aren't right, because
// those should be detected at config build time and reported as `ConfigBuildError`.
// `InvalidConfigTransition` isn't right, because restarting arti won't help.
// Possibly at some point we will allow concurrent artis to work this way.
G::NoLock(_) => EK::NotImplemented,