simple-sigh 0.1.0

Simple signal handler, intended for examples.
Documentation
use std::thread;

use parking_lot::Mutex;

use nix::sys::signal::{SigSet, SigmaskHow, Signal};

use crate::{Error, SigType};

type Callback = dyn FnOnce(SigType) + Send + Sync;

static HANDLER: Mutex<Option<Box<Callback>>> = Mutex::new(None);


/// Used to wait for signal handler to be triggered.
pub struct KillWait {
  jh: thread::JoinHandle<()>
}

impl KillWait {
  /// Wait for a signal to terminate the signal handler.
  ///
  /// # Panics
  /// If the signal handler returns an error this function will panic.
  pub fn wait(self) {
    self.jh.join().unwrap();
  }
}


/// Register signal handler for `SIGINT` and `SIGTERM`.
///
/// This function must be called before any other signal management and any
/// threads are launched.
///
/// Note that this doesn't actually set a callback.  That must be done using
/// [`register()`].
///
/// # Errors
/// [`Error::Internal`] indicates that the internal signal monitoring thread
/// failed to launch.
///
/// # Panics
/// If the signal mask can not be set this function will panic.
pub fn init() -> Result<KillWait, Error> {
  //
  // Block signals-of-interest on main thread.
  //
  let mut ss = SigSet::empty();
  ss.add(Signal::SIGINT);
  ss.add(Signal::SIGTERM);

  let mut oldset = SigSet::empty();
  nix::sys::signal::pthread_sigmask(
    SigmaskHow::SIG_SETMASK,
    Some(&ss),
    Some(&mut oldset)
  )
  .unwrap();

  let jh = thread::Builder::new()
    .name("sigmon".into())
    .spawn(move || {
      // Note: Don't need to unblock signals in this thread, because sigwait()
      // does it implicitly.
      let mask = unsafe {
        let mut mask: libc::sigset_t = std::mem::zeroed();
        libc::sigemptyset(&mut mask);
        libc::sigaddset(&mut mask, libc::SIGINT);
        libc::sigaddset(&mut mask, libc::SIGTERM);
        mask
      };

      loop {
        let mut sig: libc::c_int = 0;
        let ret = unsafe { libc::sigwait(&mask, &mut sig) };
        if ret == 0 {
          let signal = Signal::try_from(sig).unwrap();

          let Some(handler) = HANDLER.lock().take() else {
            // No handler registered.  For now, just continue (wait for a
            // handler to be registered).
            continue;
          };

          match signal {
            Signal::SIGINT => {
              handler(SigType::Int);
              break;
            }
            Signal::SIGTERM => {
              handler(SigType::Term);
              break;
            }
            _ => {}
          }
        }
      }
    })
    .map_err(|e| {
      let msg = format!("Unable to spawn signal monitoring thread; {e}");
      Error::Internal(msg)
    })?;


  Ok(KillWait { jh })
}

/// Register a callback function to be called on `SIGINT` or `SIGTERM`.
///
/// # Errors
/// Won't actually fail, but needs to return a `Result` for parity with
/// implementation on other platforms.
pub fn register(
  handler: impl FnOnce(SigType) + 'static + Send + Sync
) -> Result<(), Error> {
  let mut g = HANDLER.lock();
  *g = Some(Box::new(handler));
  drop(g);
  Ok(())
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :