rmux-server 0.1.2

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
#[cfg(unix)]
use std::io;
#[cfg(unix)]
use std::thread;

#[cfg(unix)]
use signal_hook::consts::signal::{SIGCHLD, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2};
#[cfg(unix)]
use signal_hook::iterator::{Handle, Signals};
#[cfg(unix)]
use tokio::sync::mpsc;
#[cfg(unix)]
use tracing::debug;

#[cfg(unix)]
use crate::daemon::ShutdownHandle;

#[cfg(unix)]
const SERVER_SIGNALS: [i32; 7] = [SIGHUP, SIGCHLD, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2];

#[cfg(unix)]
pub(crate) struct SignalWatcher {
    handle: Handle,
    thread: Option<thread::JoinHandle<()>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(unix), allow(dead_code))]
pub(crate) enum ServerSignal {
    ChildChanged,
    RecreateSocket,
}

#[cfg(unix)]
impl std::fmt::Debug for SignalWatcher {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("SignalWatcher")
            .field("running", &self.thread.is_some())
            .finish_non_exhaustive()
    }
}

#[cfg(unix)]
impl SignalWatcher {
    pub(crate) fn install(
        shutdown: ShutdownHandle,
        server_signals: mpsc::UnboundedSender<ServerSignal>,
    ) -> io::Result<Self> {
        let mut signals = Signals::new(SERVER_SIGNALS)?;
        let handle = signals.handle();
        let thread = thread::Builder::new()
            .name("rmux-server-signals".to_owned())
            .spawn(move || {
                for signal in signals.forever() {
                    match signal {
                        SIGINT | SIGTERM => {
                            debug!(signal, "server received shutdown signal");
                            shutdown.request_shutdown();
                            break;
                        }
                        SIGCHLD => {
                            debug!(signal, "server received child status signal");
                            let _ = server_signals.send(ServerSignal::ChildChanged);
                        }
                        SIGHUP | SIGQUIT | SIGUSR1 | SIGUSR2 => {
                            if signal == SIGUSR1 {
                                debug!(signal, "server received socket recreation signal");
                                let _ = server_signals.send(ServerSignal::RecreateSocket);
                            } else {
                                debug!(signal, "server ignored non-terminating signal");
                            }
                        }
                        _ => {}
                    }
                }
            })?;

        Ok(Self {
            handle,
            thread: Some(thread),
        })
    }
}

#[cfg(unix)]
impl Drop for SignalWatcher {
    fn drop(&mut self) {
        self.handle.close();
        if let Some(thread) = self.thread.take() {
            let _ = thread.join();
        }
    }
}