simple-sigh 0.1.0

Simple signal handler, intended for examples.
Documentation
use std::sync::{Arc, OnceLock};

use parking_lot::{Condvar, Mutex};

use windows_sys::Win32::{
  Foundation::{BOOL, FALSE, TRUE},
  System::Console::{
    SetConsoleCtrlHandler, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_C_EVENT
  }
};

use crate::{err::Error, SigType};


static CELL: OnceLock<Arc<Shared>> = OnceLock::new();

#[derive(Default)]
pub struct ReadWrite {
  /// Set to `true` when a interrupt or termination signal has been triggered.
  term: bool,

  /// One-shot callback function.
  handler: Option<Box<dyn FnOnce(SigType) + Send + Sync>>
}

#[derive(Default)]
pub struct Shared {
  rw: Mutex<ReadWrite>,
  signal: Condvar
}

/// Used to wait for signal handler to be triggered.
pub struct KillWait(Arc<Shared>);

impl KillWait {
  /// Wait until the application signal handler has triggered.
  #[allow(clippy::significant_drop_tightening)]
  pub fn wait(self) {
    let mut g = self.0.rw.lock();
    while !g.term {
      self.0.signal.wait(&mut g);
    }
  }
}

/// Initialize signal handler.
///
/// # Notes
/// The application must call [`register()`] to actually get a callback in
/// interrupt or termination.
///
/// # Errors
/// [`Error::Internal`] indicates that the internal global cell couldn't be
/// set.
pub fn init() -> Result<KillWait, Error> {
  let kw = Shared::default();
  let kw = Arc::new(kw);

  CELL
    .set(Arc::clone(&kw))
    .map_err(|_| Error::internal("Unable to set global cell"))?;

  Ok(KillWait(kw))
}

/// Register a callback function.
///
/// # Errors
/// [`Error::Internal`] may indicate that the global cell is uinitialized
/// (probably meaning [`init()`] was not called), or it can mean that
/// [`SetControlCtrlHandler()`] failed.
pub fn register(
  handler: impl FnOnce(SigType) + Send + Sync + 'static
) -> Result<(), Error> {
  let Some(sh) = CELL.get() else {
    return Err(Error::internal("cell not initialized"));
  };

  let mut g = sh.rw.lock();
  g.handler = Some(Box::new(handler));
  drop(g);

  let rc = unsafe { SetConsoleCtrlHandler(Some(ctrlhandler), 1) };
  // Returns non-zero on success
  (rc != 0)
    .then_some(())
    .ok_or_else(|| Error::internal("SetConsoleCtrlHandler failed"))?;

  Ok(())
}

unsafe extern "system" fn ctrlhandler(ty: u32) -> BOOL {
  // u32 -> SigType
  let Some(sigtype) = ty2sig(ty) else {
    // Not a recognized event type
    return FALSE;
  };

  let Some(sh) = CELL.get() else {
    // cell not initialized
    return FALSE;
  };

  let mut g = sh.rw.lock();
  let Some(handler) = g.handler.take() else {
    // no handler
    return FALSE;
  };
  drop(g);

  // Call application callback
  handler(sigtype);

  let mut g = sh.rw.lock();
  g.term = true;
  sh.signal.notify_all();
  drop(g);

  TRUE
}

const fn ty2sig(ty: u32) -> Option<SigType> {
  match ty {
    CTRL_C_EVENT => Some(SigType::Int),
    CTRL_BREAK_EVENT | CTRL_CLOSE_EVENT => Some(SigType::Term),
    _ => None
  }
}

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