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}