Skip to main content

tor_guardmgr/
err.rs

1//! Declare error types for the `tor-guardmgr` crate.
2
3use futures::task::SpawnError;
4use std::sync::Arc;
5use std::time::Instant;
6use tor_basic_utils::iter::FilterCount;
7use tor_error::{Bug, ErrorKind, HasKind};
8
9/// A error caused by a failure to pick a guard.
10#[derive(Clone, Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum PickGuardError {
13    /// All members of the current sample were down or unusable.
14    #[error(
15        "No usable guards. Rejected {} as down, then {} as pending, then \
16         {} as unsuitable to purpose, then {} with filter.{}",
17        running.display_frac_rejected(),
18        pending.display_frac_rejected(),
19        suitable.display_frac_rejected(),
20        filtered.display_frac_rejected(),
21        if let Some(retry_at) = retry_at {
22            format!(" Retrying in {:?}.", humantime::format_duration(*retry_at - Instant::now()))
23        } else {
24            "".to_string()
25        },
26    )]
27    AllGuardsDown {
28        /// The next time at which any guard will be retriable.
29        retry_at: Option<Instant>,
30        /// How many guards we rejected because they had failed too recently.
31        running: FilterCount,
32        /// How many guards we rejected because we are already probing them.
33        pending: FilterCount,
34        /// How many guards we rejected as unsuitable for the intended application.
35        suitable: FilterCount,
36        /// How many guards we rejected because of the provided filter.
37        filtered: FilterCount,
38    },
39
40    /// We have no usable fallback directories.
41    #[error(
42        "No usable fallbacks. Rejected {} as not running, then {} as filtered.", 
43         running.display_frac_rejected(),
44        filtered.display_frac_rejected()
45    )]
46    AllFallbacksDown {
47        /// The next time at which any fallback directory will back available.
48        retry_at: Option<Instant>,
49        /// The number of fallbacks that were believed to be running or down, after applying
50        /// the filter.
51        running: FilterCount,
52        /// The number of fallbacks that satisfied our filter, or did not.
53        filtered: FilterCount,
54    },
55
56    /// Tried to select guards or fallbacks from an empty list.
57    #[error("Tried to pick from an empty list")]
58    NoCandidatesAvailable,
59
60    /// An internal programming error occurred.
61    #[error("Internal error")]
62    Internal(#[from] Bug),
63}
64
65impl tor_error::HasKind for PickGuardError {
66    fn kind(&self) -> tor_error::ErrorKind {
67        use PickGuardError as E;
68        use tor_error::ErrorKind as EK;
69        match self {
70            E::AllFallbacksDown { .. } | E::AllGuardsDown { .. } => EK::TorAccessFailed,
71            E::NoCandidatesAvailable => EK::NoPath,
72            E::Internal(_) => EK::Internal,
73        }
74    }
75}
76
77impl tor_error::HasRetryTime for PickGuardError {
78    fn retry_time(&self) -> tor_error::RetryTime {
79        use PickGuardError as E;
80        use tor_error::RetryTime as RT;
81        match self {
82            // Some errors contain times that we can just use.
83            E::AllGuardsDown {
84                retry_at: Some(when),
85                ..
86            } => RT::At(*when),
87            E::AllFallbacksDown {
88                retry_at: Some(when),
89                ..
90            } => RT::At(*when),
91
92            // If we don't know when the guards/fallbacks will be back up,
93            // though, then we should suggest a random delay.
94            E::AllGuardsDown { .. } | E::AllFallbacksDown { .. } => RT::AfterWaiting,
95
96            // We were asked to choose some kind of guard that doesn't exist in
97            // our current universe; that's not going to be come viable down the
98            // line.
99            E::NoCandidatesAvailable => RT::Never,
100
101            // Don't try to recover from internal errors.
102            E::Internal(_) => RT::Never,
103        }
104    }
105}
106/// An error caused while creating or updating a guard manager.
107#[derive(Clone, Debug, thiserror::Error)]
108#[non_exhaustive]
109pub enum GuardMgrError {
110    /// An error manipulating persistent state
111    #[error("Problem accessing persistent guard state")]
112    State(#[from] tor_persist::Error),
113
114    /// Configuration is not valid or available
115    #[error("Invalid configuration")]
116    InvalidConfig(#[from] GuardMgrConfigError),
117
118    /// An error that occurred while trying to spawn a daemon task.
119    #[error("Unable to spawn {spawning}")]
120    Spawn {
121        /// What we were trying to spawn.
122        spawning: &'static str,
123        /// What happened when we tried to spawn it.
124        #[source]
125        cause: Arc<SpawnError>,
126    },
127}
128
129impl HasKind for GuardMgrError {
130    #[rustfmt::skip] // to preserve table in match
131    fn kind(&self) -> ErrorKind {
132        use GuardMgrError as G;
133        match self {
134            G::State(e)               => e.kind(),
135            G::InvalidConfig(e)       => e.kind(),
136            G::Spawn{ cause, .. }     => cause.kind(),
137        }
138    }
139}
140
141impl GuardMgrError {
142    /// Construct a new `GuardMgrError` from a `SpawnError`.
143    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> GuardMgrError {
144        GuardMgrError::Spawn {
145            spawning,
146            cause: Arc::new(err),
147        }
148    }
149}
150
151/// An error encountered while configuring or reconfiguring a guard manager
152///
153/// When this occurs during initial configuration, it will be returned wrapped
154/// up in `GuardMgrError`.
155///
156/// When it occurs during reconfiguration, it is not exposed to caller:
157/// instead, it is converted into a `tor_config::ReconfigureError`.
158#[derive(Clone, Debug, thiserror::Error)]
159#[non_exhaustive]
160pub enum GuardMgrConfigError {
161    /// Specified configuration requires exclusive access to stored state, which we don't have
162    #[error(
163        "Configuration requires exclusive access to shared state, but another instance of Arti has the lock: {0}"
164    )]
165    NoLock(String),
166}
167
168impl From<GuardMgrConfigError> for tor_config::ReconfigureError {
169    fn from(g: GuardMgrConfigError) -> tor_config::ReconfigureError {
170        use GuardMgrConfigError as G;
171        use tor_config::ReconfigureError as R;
172        match g {
173            e @ G::NoLock(_) => R::UnsupportedSituation(e.to_string()),
174        }
175    }
176}
177
178impl HasKind for GuardMgrConfigError {
179    fn kind(&self) -> ErrorKind {
180        use ErrorKind as EK;
181        use GuardMgrConfigError as G;
182        match self {
183            // `InvalidConfig` and `FeatureDisabled` aren't right, because
184            // those should be detected at config build time and reported as `ConfigBuildError`.
185            // `InvalidConfigTransition` isn't right, because restarting arti won't help.
186            // Possibly at some point we will allow concurrent artis to work this way.
187            G::NoLock(_) => EK::NotImplemented,
188        }
189    }
190}