Skip to main content

liminal_server/tap/
error.rs

1//! The tap's failures: a lock another thread poisoned, a station list its
2//! two passes disagreed on, and hops that could not be kept as measured.
3
4/// A tap or console operation found a lock poisoned and refused to continue
5/// with a guard it cannot trust.
6#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
7pub enum TapError {
8    /// The named lock was poisoned by a panic on another thread.
9    #[error("console {what} lock is poisoned")]
10    Poisoned {
11        /// Which lock.
12        what: &'static str,
13    },
14    /// The two passes that build a message's station list disagreed: the
15    /// count named `named` stations and the fill found `found`. The list is
16    /// refused, never filled in, and the message is kept as not recorded.
17    #[error("tap station list refused: counted {named} stations, found {found}")]
18    StationsDisagree {
19        /// Stations the counting pass named.
20        named: usize,
21        /// Stations the filling pass found.
22        found: usize,
23    },
24    /// A kept message's hop was measured earlier than the hop before it,
25    /// which monotonic instants taken in order never give (lane brief
26    /// ruling 3, Hermes 04:09). The message is kept with its hops as
27    /// measured, never rewritten, and the observe is counted as failed.
28    #[error("tap hop {hop} measured at {at_ms} ms, before the hop ahead of it at {before_ms} ms")]
29    HopsOutOfOrder {
30        /// Where the hop stood in the message's chain.
31        hop: usize,
32        /// Its measured milliseconds after first sight.
33        at_ms: u64,
34        /// The hop before it, measured.
35        before_ms: u64,
36    },
37    /// A kept message's hop was measured before the message was first seen
38    /// (Buttercup's V2, Hermes 04:27). `at_ms` cannot carry it, so the hop
39    /// is left out of the record, never written as 0, and the observe is
40    /// counted as failed.
41    #[error("tap hop {hop} measured {early_ms} ms before first sight; left out")]
42    HopBeforeFirstSight {
43        /// Where the hop stood in the message's chain.
44        hop: usize,
45        /// How long before first sight it was measured.
46        early_ms: u64,
47    },
48    /// A kept message's hop was measured further from first sight than
49    /// `at_ms` can carry. The hop is left out, never written as the most it
50    /// can hold, and the observe is counted as failed.
51    #[error("tap hop {hop} measured beyond u64 milliseconds; left out")]
52    HopOverflow {
53        /// Where the hop stood in the message's chain.
54        hop: usize,
55    },
56    /// More than one of the above on one observe, each reported (Buttercup's
57    /// V3, Hermes 04:27): a refused station list never hides a hop. One
58    /// entry per kind, the first hop of each.
59    #[error("tap observe failed {} ways: {}", .0.len(), display_all(.0))]
60    Several(Vec<Self>),
61}
62
63impl TapError {
64    /// How many failures this is: one, or each of [`Self::Several`].
65    #[must_use]
66    pub fn failures(&self) -> usize {
67        match self {
68            Self::Several(each) => each.len(),
69            _ => 1,
70        }
71    }
72}
73
74fn display_all(each: &[TapError]) -> String {
75    each.iter()
76        .map(ToString::to_string)
77        .collect::<Vec<_>>()
78        .join("; ")
79}
80
81/// Locks `mutex`, naming it in the error when it is poisoned.
82///
83/// # Errors
84/// Returns [`TapError::Poisoned`] when another thread panicked holding it.
85pub fn lock<'a, T>(
86    mutex: &'a std::sync::Mutex<T>,
87    what: &'static str,
88) -> Result<std::sync::MutexGuard<'a, T>, TapError> {
89    mutex.lock().map_err(|_| TapError::Poisoned { what })
90}