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<bool, ServerError> {
208 let mut counters = self.snapshot().remove(id).unwrap_or_default();
209 let changed = counters.last_refusal.as_ref() != Some(&reason);
210 counters.dial_refused = counters
211 .dial_refused
212 .checked_add(1)
213 .ok_or_else(|| failure("configured dial refusal counter overflow"))?;
214 counters.last_refusal = Some(reason);
215 self.remember(id.to_owned(), counters)?;
216 Ok(changed)
217 }
218 pub(crate) fn open_refused(&self, id: &str, reason: String) -> Result<bool, ServerError> {
219 let mut counters = self.snapshot().remove(id).unwrap_or_default();
220 let changed = counters.last_refusal.as_ref() != Some(&reason);
221 counters.refused = counters
222 .refused
223 .checked_add(1)
224 .ok_or_else(|| failure("configured open refusal counter overflow"))?;
225 counters.last_refusal = Some(reason);
226 self.remember(id.to_owned(), counters)?;
227 Ok(changed)
228 }
229 #[cfg(feature = "test-support")]
230 pub(crate) fn observe_answers(
231 &self,
232 ids: &[String],
233 count: u64,
234 bound: std::time::Duration,
235 ) -> Result<bool, ServerError> {
236 let reached = |observations: &BTreeMap<String, Arc<Observation>>| {
237 ids.iter().all(|id| {
238 observations.get(id).is_some_and(|observation| {
239 observation.settled.load(Ordering::Acquire) >= count
240 && observation.snapshot().error.is_none()
241 })
242 })
243 };
244 let (observations, _) = self
245 .2
246 .wait_timeout_while(
247 lock(&self.0, "configured answer observation")?,
248 bound,
249 |observations| !reached(observations),
250 )
251 .map_err(|_| failure("configured answer observation poisoned"))?;
252 let observed = reached(&observations);
253 drop(observations);
254 Ok(observed)
255 }
256 pub(super) fn install(&self, id: String, observation: &Arc<Observation>) {
257 if let Err(error) = self.register_stop(&id, observation) {
258 observation.wake.refuse(&error);
259 return;
260 }
261 #[cfg(feature = "test-support")]
262 if observation
263 .registry_signal
264 .set((Arc::downgrade(&self.0), Arc::downgrade(&self.2)))
265 .is_err()
266 {
267 observation
268 .wake
269 .refuse(&failure("bridge observation registry already installed"));
270 return;
271 }
272 let result = lock(&self.0, "health registry installation").map(|mut registry| {
273 registry.insert(id, Arc::clone(observation));
274 self.2.notify_all();
275 });
276 if let Err(error) = result {
277 self.refuse(&error);
278 }
279 if let Some(reason) = self.1.reason.get() {
280 observation.wake.refuse(&failure(reason.clone()));
281 }
282 }
283 fn register_stop(&self, id: &str, observation: &Arc<Observation>) -> Result<(), ServerError> {
284 let mut next = &self.1.stops;
286 loop {
287 if let Some(existing) = next.get() {
288 if existing.id == id {
289 *lock(&existing.wake, "registry stop replacement")? =
290 Arc::downgrade(&observation.wake);
291 return Ok(());
292 }
293 next = &existing.next;
294 continue;
295 }
296 let stop = Box::new(RegistryStop {
297 id: id.to_owned(),
298 wake: Mutex::new(Arc::downgrade(&observation.wake)),
299 next: OnceLock::new(),
300 });
301 if next.set(stop).is_ok() {
302 return Ok(());
303 }
304 }
305 }
306
307 fn refuse(&self, error: &ServerError) {
308 if self.1.reason.set(error.to_string()).is_err() {
309 return;
310 }
311 tracing::error!(%error, "link bridge registry refused poisoned state");
312 let mut next = self.1.stops.get();
313 while let Some(stop) = next {
314 match lock(&stop.wake, "registry stop refusal") {
315 Ok(wake) => {
316 if let Some(wake) = wake.upgrade() {
317 wake.refuse(error);
318 }
319 }
320 Err(error) => tracing::error!(%error, "registry stop refused poisoned state"),
321 }
322 next = stop.next.get();
323 }
324 }
325}
326
327#[derive(Debug, Default)]
328pub(super) struct WakeState {
329 ready: bool,
330 stopping: bool,
331}
332
333#[derive(Debug, Default)]
334pub(super) struct Wake {
335 pub(super) state: Mutex<WakeState>,
336 changed: Condvar,
337 reason: OnceLock<String>,
338 observation: Weak<Observation>,
339 interrupt: Option<TcpStream>,
340}
341
342impl Wake {
343 pub(super) fn notify(&self) {
344 match lock(&self.state, "wake notification") {
345 Ok(mut state) => state.ready = true,
346 Err(error) => self.refuse(&error),
347 }
348 self.changed.notify_one();
349 }
350
351 pub(super) fn stopping(&self) -> bool {
352 match lock(&self.state, "wake stop observation") {
353 Ok(state) => state.stopping,
354 Err(error) => {
355 self.refuse(&error);
356 true
357 }
358 }
359 }
360
361 pub(super) fn stop(&self) {
362 match lock(&self.state, "wake stop") {
363 Ok(mut state) => state.stopping = true,
364 Err(error) => self.refuse(&error),
365 }
366 self.changed.notify_all();
367 }
368
369 pub(super) fn wait(&self) -> bool {
370 let result = lock(&self.state, "wake wait").and_then(|state| {
371 self.changed
372 .wait_while(state, |state| !state.ready && !state.stopping)
373 .map_err(|_| failure("link_bridge_poisoned: wake wait"))
374 });
375 match result {
376 Ok(mut state) => {
377 state.ready = false;
378 !state.stopping
379 }
380 Err(error) => {
381 self.refuse(&error);
382 false
383 }
384 }
385 }
386
387 pub(super) fn check(&self) -> Result<(), ServerError> {
388 self.reason
389 .get()
390 .map_or(Ok(()), |reason| Err(failure(reason.clone())))
391 }
392
393 pub(super) fn refuse(&self, error: &ServerError) {
394 if self.reason.set(error.to_string()).is_err() {
395 return;
396 }
397 tracing::error!(%error, "link bridge stopped as a fault");
398 match self.state.lock() {
399 Ok(mut state) => state.stopping = true,
400 Err(_) => self.changed.notify_all(),
401 }
402 self.changed.notify_all();
403 if let Some(interrupt) = &self.interrupt {
404 match interrupt.shutdown(Shutdown::Both) {
405 Ok(()) => {}
406 Err(error) if error.kind() == std::io::ErrorKind::NotConnected => {
407 tracing::info!(%error, "faulted bridge carrier already closed");
408 }
409 Err(error) => tracing::error!(%error, "faulted bridge interruption failed"),
410 }
411 }
412 if let Some(observation) = self.observation.upgrade() {
413 match observation.counters.lock() {
414 Ok(mut counters) => counters.error = Some(error.to_string()),
415 Err(_) => observation.changed.notify_all(),
416 }
417 observation.changed.notify_all();
418 }
419 }
420}