liminal-server 0.25.0

Standalone server for the liminal messaging bus
Documentation
//! A bridge observes its dialed carrier's existing lifecycle, never a second open.

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");
    }
}