use super::{Observation, ServerError, failure};
use crate::link::{LinkEnd, LinkLife};
use crate::link_carrier::choice::ChoiceLink as DialedLink;
use crate::server::connection::services::LiminalConnectionServices;
use std::sync::Arc;
impl Observation {
pub(super) fn watch_end(&self, wake: &Arc<super::Wake>) -> Result<(), ServerError> {
let wake = Arc::downgrade(wake);
self.life
.get()
.ok_or_else(|| failure("bridge lifecycle not installed"))?
.notify_with(Arc::new(move || {
if let Some(wake) = wake.upgrade() {
wake.notify();
}
}))
.map_err(failure)
}
pub(super) fn ended(&self) -> Result<bool, ServerError> {
match self.life_reason().map_err(failure)? {
None => Ok(false),
Some(reason) if reason == "shutdown" || reason == "peer_gone" => Ok(true),
Some(reason) => Err(failure(format!("link carrier ended: {reason}"))),
}
}
pub(super) fn worker_ended(&self, result: &Result<(), ServerError>) -> Result<(), ServerError> {
self.end_life(match result {
Ok(()) => LinkEnd::Shutdown,
Err(error) => LinkEnd::Refused(error.to_string()),
})
}
pub(super) fn install_life(
self: &Arc<Self>,
link: &DialedLink,
services: &Arc<LiminalConnectionServices>,
) -> Result<(), ServerError> {
let life = link
.carrier
.life()
.ok_or_else(|| failure("bridge carrier has no link lifecycle"))?;
self.life
.set(life.clone())
.map_err(|_| failure("bridge lifecycle already installed"))?;
let local = services.console().device().to_owned();
let services = Arc::clone(services);
let observation = Arc::downgrade(self);
let carrier = link.carrier.observe_carrier();
life.observe(
local,
Arc::new(move |mut change| {
if let crate::console::TopologyChange::Link {
carrier: observed,
counters,
..
} = &mut change
{
if let Some(observation) = observation.upgrade() {
counters.lifecycle = Some(observation.snapshot().lifecycle);
}
if let Ok(carrier) = carrier.lock() {
observed.clone_from(&carrier);
} else {
tracing::error!("carrier_observation_poisoned");
return;
}
}
if let Err(error) = services.console().emit(change) {
tracing::error!(%error, "bridge lifecycle topology failed");
}
}),
)
.map_err(failure)
}
pub(super) fn end_life(&self, reason: LinkEnd) -> Result<(), ServerError> {
self.life
.get()
.ok_or_else(|| failure("bridge lifecycle not installed"))?
.end(reason)
.map_err(failure)
}
pub(super) fn life_reason(&self) -> Result<Option<String>, String> {
self.life
.get()
.ok_or("bridge lifecycle not installed")?
.reason()
}
}
pub(super) fn counts(life: Option<&LinkLife>) -> crate::link::LinkLifeCounters {
life.map_or_else(crate::link::LinkLifeCounters::default, LinkLife::snapshot)
}
pub(super) fn topology(
services: &LiminalConnectionServices,
id: &str,
peer: &str,
observation: &Observation,
reason: Option<String>,
) {
let reason = match observation.life_reason() {
Ok(ended) => ended.or(reason),
Err(error) => {
tracing::error!(link = %id, %error, "link lifecycle observation failed");
return;
}
};
let counters = observation.snapshot();
let peer = observation.life.get().map_or(peer, LinkLife::peer);
let change = crate::console::TopologyChange::Link {
link_id: crate::console::records::Link::to_peer(services.console().device(), peer).id,
peer: peer.to_owned(),
carrier: None,
state: if reason.is_some() {
crate::console::records::LinkState::Down
} else {
crate::console::records::LinkState::Up
},
reason,
counters: crate::console::records::LinkCounters {
lifecycle: Some(counters.lifecycle),
crossed: Some(counters.crossed),
refused: Some(counters.refused),
..crate::console::records::LinkCounters::default()
},
};
if let Err(error) = services.console().emit(change) {
tracing::warn!(link = %id, %error, "link topology observation failed");
}
}