liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
//! SRV-042 R1: a connection accepted while the incarnation start runs is held
//! here, off the accept thread, and run by the start thread once the start has
//! published, so no accept worker (TCP or WebSocket) and no listener stop ever
//! waits on the start. A held connection is admitted or refused by the start's
//! outcome through the ordinary spawn, in arrival order; the supervisor's
//! shutdown drops what is held, closing each socket, and runs nothing after.
//!
//! Nothing is bounded here beyond the start's own duration: every held
//! connection still takes its admission reservation when it runs, so one over
//! `max_connections` is refused then, exactly as it would have been at accept.

use std::sync::Mutex;

use super::ConnectionSupervisor;

/// One accepted connection waiting for the incarnation start.
pub(super) type HeldAdmission = Box<dyn FnOnce(&ConnectionSupervisor) + Send>;

#[derive(Default)]
enum Phase {
    /// No start is running: an admission runs on the caller's thread.
    #[default]
    Started,
    /// The start runs: admissions are held for it.
    Starting(Vec<HeldAdmission>),
    /// The supervisor shut down: an admission is dropped, its socket closed.
    Stopped,
}

/// The admissions held while the incarnation start runs.
#[derive(Default)]
pub(super) struct HeldAdmissions {
    phase: Mutex<Phase>,
    /// Notified after each hold, so a test waits for its clients to be held
    /// before it stops the listener (SRV-046 R1).
    #[cfg(test)]
    arrived: std::sync::Condvar,
}

impl std::fmt::Debug for HeldAdmissions {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let phase = match self.phase.lock().as_deref() {
            Ok(Phase::Started) => "started".to_owned(),
            Ok(Phase::Starting(held)) => format!("starting, {} held", held.len()),
            Ok(Phase::Stopped) => "stopped".to_owned(),
            Err(_) => "poisoned".to_owned(),
        };
        let mut debug = formatter.debug_struct("HeldAdmissions");
        debug.field("phase", &phase);
        #[cfg(test)]
        debug.field("arrived", &self.arrived);
        debug.finish()
    }
}

impl HeldAdmissions {
    /// Holds every admission from now until [`Self::started`]; called before
    /// the start thread begins, so none slips past it.
    pub(super) fn starting(&self) {
        if let Ok(mut phase) = self.phase.lock() {
            if matches!(*phase, Phase::Started) {
                *phase = Phase::Starting(Vec::new());
            }
        } else {
            tracing::error!(
                "the held admission lock was poisoned; admissions wait on the start on their own threads"
            );
        }
    }

    /// Holds `admit` while the start runs, drops it once the supervisor has
    /// stopped, and answers it back to run now otherwise.
    pub(super) fn hold(&self, admit: HeldAdmission) -> Option<HeldAdmission> {
        let Ok(mut phase) = self.phase.lock() else {
            tracing::error!("the held admission lock was poisoned; the admission runs at once");
            return Some(admit);
        };
        match &mut *phase {
            Phase::Started => Some(admit),
            Phase::Starting(held) => {
                held.push(admit);
                #[cfg(test)]
                self.arrived.notify_all();
                None
            }
            // Dropped when this returns, after the lock: its socket closes.
            Phase::Stopped => None,
        }
    }

    /// The start has published: answers what was held, in arrival order, and
    /// lets later admissions run at once. Answers nothing once stopped.
    pub(super) fn started(&self) -> Vec<HeldAdmission> {
        let Ok(mut phase) = self.phase.lock() else {
            tracing::error!("the held admission lock was poisoned; nothing held is admitted");
            return Vec::new();
        };
        match std::mem::take(&mut *phase) {
            Phase::Starting(held) => held,
            Phase::Stopped => {
                *phase = Phase::Stopped;
                Vec::new()
            }
            Phase::Started => Vec::new(),
        }
    }

    /// Whether `count` admissions are held while the start runs, waiting for
    /// each hold's notify until `within` passes (SRV-046 R1).
    #[cfg(test)]
    pub(super) fn wait_held(&self, count: usize, within: std::time::Duration) -> bool {
        let held = |phase: &Phase| matches!(phase, Phase::Starting(held) if held.len() >= count);
        self.phase.lock().is_ok_and(|phase| {
            self.arrived
                .wait_timeout_while(phase, within, |phase| !held(phase))
                .is_ok_and(|(phase, _)| held(&phase))
        })
    }

    /// The supervisor is shutting down: drops what is held, closing each
    /// socket, and every admission after it.
    pub(super) fn stop(&self) {
        let Ok(mut phase) = self.phase.lock() else {
            tracing::error!(
                "the held admission lock was poisoned; held sockets close with the supervisor"
            );
            return;
        };
        let held = std::mem::replace(&mut *phase, Phase::Stopped);
        drop(phase);
        drop(held);
    }
}