liminal_server/link_bridge/
fault.rs1use super::{BridgeCounters, ServerError, failure, life, lock};
4use std::collections::BTreeMap;
5use std::net::{Shutdown, TcpStream};
6use std::sync::atomic::AtomicU64;
7#[cfg(feature = "test-support")]
8use std::sync::atomic::Ordering;
9use std::sync::{Arc, Condvar, Mutex, OnceLock, Weak};
10
11#[derive(Debug, Default)]
12pub(super) struct Observation {
13 pub(super) life: OnceLock<crate::link::LinkLife>,
14 previous_lifecycle: crate::link::LinkLifeCounters,
15 #[cfg(feature = "test-support")]
16 registry_signal: OnceLock<RegistrySignal>,
17 #[cfg(feature = "test-support")]
18 pub(super) stop_before_ack: std::sync::atomic::AtomicBool,
19 #[cfg(feature = "test-support")]
20 pub(super) prepared_crossing: Mutex<Option<super::prepared::PreparedCrossing>>,
21 pub(super) counters: Mutex<BridgeCounters>,
22 pub(super) settled: AtomicU64,
23 pub(super) changed: Condvar,
24 pub(super) wake: Arc<Wake>,
25}
26
27impl Observation {
28 #[cfg(test)]
29 pub(super) fn new(interrupt: TcpStream) -> Arc<Self> {
30 Self::with_counts(Some(interrupt), BridgeCounters::default(), 0)
31 }
32
33 pub(super) fn resume(
34 interrupt: TcpStream,
35 counters: BridgeCounters,
36 ) -> Result<Arc<Self>, ServerError> {
37 Self::retaining(Some(interrupt), counters)
38 }
39 pub(super) fn retaining(
40 interrupt: Option<TcpStream>,
41 counters: BridgeCounters,
42 ) -> Result<Arc<Self>, ServerError> {
43 let settled = counters
44 .crossed
45 .checked_add(counters.refused)
46 .and_then(|count| count.checked_add(counters.too_large))
47 .ok_or_else(|| failure("configured link answer counter overflow"))?;
48 Ok(Self::with_counts(interrupt, counters, settled))
49 }
50 fn with_counts(
51 interrupt: Option<TcpStream>,
52 counters: BridgeCounters,
53 settled: u64,
54 ) -> Arc<Self> {
55 Arc::new_cyclic(|observation| Self {
56 wake: Arc::new(Wake {
57 observation: observation.clone(),
58 interrupt,
59 ..Wake::default()
60 }),
61 previous_lifecycle: counters.lifecycle,
62 counters: Mutex::new(counters),
63 settled: AtomicU64::new(settled),
64 ..Self::default()
65 })
66 }
67 pub(super) fn notify_changes(&self) {
68 self.changed.notify_all();
69 #[cfg(feature = "test-support")]
70 if let Some((registry, changed)) = self.registry_signal.get()
71 && let (Some(registry), Some(changed)) = (registry.upgrade(), changed.upgrade())
72 {
73 match lock(®istry, "registry notification") {
74 Ok(guard) => {
75 changed.notify_all();
76 drop(guard);
77 }
78 Err(error) => self.wake.refuse(&error),
79 }
80 }
81 }
82
83 pub(super) fn snapshot(&self) -> BridgeCounters {
84 let mut counters = match lock(&self.counters, "counters snapshot") {
85 Ok(counters) => counters.clone(),
86 Err(error) => {
87 self.wake.refuse(&error);
88 BridgeCounters::default()
89 }
90 };
91 counters.error = self.wake.reason.get().cloned().or(counters.error);
92 match super::counts::lifecycle(life::counts(self.life.get()), self.previous_lifecycle) {
93 Ok(lifecycle) => counters.lifecycle = lifecycle,
94 Err(error) => {
95 self.wake.refuse(&error);
96 counters.error = Some(error.to_string());
97 }
98 }
99 counters
100 }
101
102 #[cfg(feature = "test-support")]
103 pub(super) fn observe_answers(&self, count: u64, bound: std::time::Duration) -> bool {
104 let result = lock(&self.counters, "answer observation").and_then(|counters| {
105 self.changed
106 .wait_timeout_while(counters, bound, |counters| {
107 self.settled.load(Ordering::Acquire) < count && counters.error.is_none()
108 })
109 .map_err(|_| failure("link_bridge_poisoned: answer observation"))
110 });
111 match result {
112 Ok((counters, _)) => {
113 let observed = self.settled.load(Ordering::Acquire) >= count
114 && counters.error.is_none()
115 && self.wake.check().is_ok();
116 drop(counters);
117 observed
118 }
119 Err(error) => {
120 self.wake.refuse(&error);
121 false
122 }
123 }
124 }
125}
126
127type RegistryMap = Mutex<BTreeMap<String, Arc<Observation>>>;
128#[cfg(feature = "test-support")]
129type RegistrySignal = (Weak<RegistryMap>, Weak<Condvar>);
130
131#[derive(Clone, Debug, Default)]
133pub struct BridgeRegistry(
134 pub(super) Arc<RegistryMap>,
135 Arc<RegistryFault>,
136 Arc<Condvar>,
137);
138
139#[derive(Debug, Default)]
140struct RegistryFault {
141 reason: OnceLock<String>,
142 stops: OnceLock<Box<RegistryStop>>,
143}
144
145#[derive(Debug)]
146struct RegistryStop {
147 id: String,
148 wake: Mutex<Weak<Wake>>,
149 next: OnceLock<Box<Self>>,
150}
151
152impl BridgeRegistry {
153 #[cfg(feature = "test-support")]
155 pub fn observe(
158 &self,
159 bound: std::time::Duration,
160 mut reached: impl FnMut(&BTreeMap<String, super::BridgeCounters>) -> bool,
161 ) -> Result<bool, ServerError> {
162 let (observations, _) = self
163 .2
164 .wait_timeout_while(
165 lock(&self.0, "registry observation")?,
166 bound,
167 |observations| {
168 let snapshot = observations
169 .iter()
170 .map(|(id, observation)| (id.clone(), observation.snapshot()))
171 .collect();
172 !reached(&snapshot)
173 },
174 )
175 .map_err(|_| failure("registry observation poisoned"))?;
176 let snapshot = observations
177 .iter()
178 .map(|(id, observation)| (id.clone(), observation.snapshot()))
179 .collect();
180 drop(observations);
181 Ok(reached(&snapshot))
182 }
183
184 #[must_use]
186 pub fn snapshot(&self) -> BTreeMap<String, BridgeCounters> {
187 match lock(&self.0, "health registry") {
188 Ok(observations) => observations
189 .iter()
190 .map(|(id, observation)| (id.clone(), observation.snapshot()))
191 .collect(),
192 Err(error) => {
193 self.refuse(&error);
194 BTreeMap::new()
195 }
196 }
197 }
198
199 pub(crate) fn remember(&self, id: String, counters: BridgeCounters) -> Result<(), ServerError> {
200 let observation = Observation::retaining(None, counters)?;
201 self.install(id, &observation);
202 observation.wake.check()
203 }
204 pub(crate) fn dial_refused(&self, id: &str, reason: String) -> Result<(), ServerError> {
205 let mut counters = self.snapshot().remove(id).unwrap_or_default();
206 counters.dial_refused = counters
207 .dial_refused
208 .checked_add(1)
209 .ok_or_else(|| failure("configured dial refusal counter overflow"))?;
210 counters.last_refusal = Some(reason);
211 self.remember(id.to_owned(), counters)
212 }
213 pub(crate) fn open_refused(&self, id: &str, reason: String) -> Result<bool, ServerError> {
214 let mut counters = self.snapshot().remove(id).unwrap_or_default();
215 let changed = counters.last_refusal.as_ref() != Some(&reason);
216 counters.refused = counters
217 .refused
218 .checked_add(1)
219 .ok_or_else(|| failure("configured open refusal counter overflow"))?;
220 counters.last_refusal = Some(reason);
221 self.remember(id.to_owned(), counters)?;
222 Ok(changed)
223 }
224 #[cfg(feature = "test-support")]
225 pub(crate) fn observe_answers(
226 &self,
227 ids: &[String],
228 count: u64,
229 bound: std::time::Duration,
230 ) -> Result<bool, ServerError> {
231 let reached = |observations: &BTreeMap<String, Arc<Observation>>| {
232 ids.iter().all(|id| {
233 observations.get(id).is_some_and(|observation| {
234 observation.settled.load(Ordering::Acquire) >= count
235 && observation.snapshot().error.is_none()
236 })
237 })
238 };
239 let (observations, _) = self
240 .2
241 .wait_timeout_while(
242 lock(&self.0, "configured answer observation")?,
243 bound,
244 |observations| !reached(observations),
245 )
246 .map_err(|_| failure("configured answer observation poisoned"))?;
247 let observed = reached(&observations);
248 drop(observations);
249 Ok(observed)
250 }
251 pub(super) fn install(&self, id: String, observation: &Arc<Observation>) {
252 if let Err(error) = self.register_stop(&id, observation) {
253 observation.wake.refuse(&error);
254 return;
255 }
256 #[cfg(feature = "test-support")]
257 if observation
258 .registry_signal
259 .set((Arc::downgrade(&self.0), Arc::downgrade(&self.2)))
260 .is_err()
261 {
262 observation
263 .wake
264 .refuse(&failure("bridge observation registry already installed"));
265 return;
266 }
267 let result = lock(&self.0, "health registry installation").map(|mut registry| {
268 registry.insert(id, Arc::clone(observation));
269 self.2.notify_all();
270 });
271 if let Err(error) = result {
272 self.refuse(&error);
273 }
274 if let Some(reason) = self.1.reason.get() {
275 observation.wake.refuse(&failure(reason.clone()));
276 }
277 }
278 fn register_stop(&self, id: &str, observation: &Arc<Observation>) -> Result<(), ServerError> {
279 let mut next = &self.1.stops;
281 loop {
282 if let Some(existing) = next.get() {
283 if existing.id == id {
284 *lock(&existing.wake, "registry stop replacement")? =
285 Arc::downgrade(&observation.wake);
286 return Ok(());
287 }
288 next = &existing.next;
289 continue;
290 }
291 let stop = Box::new(RegistryStop {
292 id: id.to_owned(),
293 wake: Mutex::new(Arc::downgrade(&observation.wake)),
294 next: OnceLock::new(),
295 });
296 if next.set(stop).is_ok() {
297 return Ok(());
298 }
299 }
300 }
301
302 fn refuse(&self, error: &ServerError) {
303 if self.1.reason.set(error.to_string()).is_err() {
304 return;
305 }
306 tracing::error!(%error, "link bridge registry refused poisoned state");
307 let mut next = self.1.stops.get();
308 while let Some(stop) = next {
309 match lock(&stop.wake, "registry stop refusal") {
310 Ok(wake) => {
311 if let Some(wake) = wake.upgrade() {
312 wake.refuse(error);
313 }
314 }
315 Err(error) => tracing::error!(%error, "registry stop refused poisoned state"),
316 }
317 next = stop.next.get();
318 }
319 }
320}
321
322#[derive(Debug, Default)]
323pub(super) struct WakeState {
324 ready: bool,
325 stopping: bool,
326}
327
328#[derive(Debug, Default)]
329pub(super) struct Wake {
330 pub(super) state: Mutex<WakeState>,
331 changed: Condvar,
332 reason: OnceLock<String>,
333 observation: Weak<Observation>,
334 interrupt: Option<TcpStream>,
335}
336
337impl Wake {
338 pub(super) fn notify(&self) {
339 match lock(&self.state, "wake notification") {
340 Ok(mut state) => state.ready = true,
341 Err(error) => self.refuse(&error),
342 }
343 self.changed.notify_one();
344 }
345
346 pub(super) fn stopping(&self) -> bool {
347 match lock(&self.state, "wake stop observation") {
348 Ok(state) => state.stopping,
349 Err(error) => {
350 self.refuse(&error);
351 true
352 }
353 }
354 }
355
356 pub(super) fn stop(&self) {
357 match lock(&self.state, "wake stop") {
358 Ok(mut state) => state.stopping = true,
359 Err(error) => self.refuse(&error),
360 }
361 self.changed.notify_all();
362 }
363
364 pub(super) fn wait(&self) -> bool {
365 let result = lock(&self.state, "wake wait").and_then(|state| {
366 self.changed
367 .wait_while(state, |state| !state.ready && !state.stopping)
368 .map_err(|_| failure("link_bridge_poisoned: wake wait"))
369 });
370 match result {
371 Ok(mut state) => {
372 state.ready = false;
373 !state.stopping
374 }
375 Err(error) => {
376 self.refuse(&error);
377 false
378 }
379 }
380 }
381
382 pub(super) fn check(&self) -> Result<(), ServerError> {
383 self.reason
384 .get()
385 .map_or(Ok(()), |reason| Err(failure(reason.clone())))
386 }
387
388 pub(super) fn refuse(&self, error: &ServerError) {
389 if self.reason.set(error.to_string()).is_err() {
390 return;
391 }
392 tracing::error!(%error, "link bridge stopped as a fault");
393 match self.state.lock() {
394 Ok(mut state) => state.stopping = true,
395 Err(_) => self.changed.notify_all(),
396 }
397 self.changed.notify_all();
398 if let Some(interrupt) = &self.interrupt {
399 match interrupt.shutdown(Shutdown::Both) {
400 Ok(()) => {}
401 Err(error) if error.kind() == std::io::ErrorKind::NotConnected => {
402 tracing::info!(%error, "faulted bridge carrier already closed");
403 }
404 Err(error) => tracing::error!(%error, "faulted bridge interruption failed"),
405 }
406 }
407 if let Some(observation) = self.observation.upgrade() {
408 match observation.counters.lock() {
409 Ok(mut counters) => counters.error = Some(error.to_string()),
410 Err(_) => observation.changed.notify_all(),
411 }
412 observation.changed.notify_all();
413 }
414 }
415}