grace 0.1.0

Intercept shutdown requests in a portable way
Documentation
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;

use signal_stack::SignalHandlerGuard;

use super::ShutdownType;

static mut NOTIFY_SEM: UnsafeCell<MaybeUninit<libc::sem_t>> =
    UnsafeCell::new(MaybeUninit::uninit());
static mut STOP_SEM: UnsafeCell<MaybeUninit<libc::sem_t>> = UnsafeCell::new(MaybeUninit::uninit());
static INT_COUNT: AtomicUsize = AtomicUsize::new(0);
static TERM_COUNT: AtomicUsize = AtomicUsize::new(0);
static STOPPING: AtomicBool = AtomicBool::new(false);

fn load_and_reset(counter: &AtomicUsize) -> usize {
    let res = counter.load(Ordering::Relaxed);
    counter.fetch_sub(res, Ordering::Relaxed);
    res
}

fn sem_ptr(sem: &UnsafeCell<MaybeUninit<libc::sem_t>>) -> *mut libc::sem_t {
    sem.get() as *mut libc::sem_t
}

fn background_thread() {
    unsafe {
        while !STOPPING.load(Ordering::Relaxed) {
            libc::sem_wait(sem_ptr(&NOTIFY_SEM));
            let int_count = load_and_reset(&INT_COUNT);
            let term_count = load_and_reset(&TERM_COUNT);
            for _ in 0..int_count {
                super::handle(ShutdownType::Interrupt);
            }
            for _ in 0..term_count {
                super::handle(ShutdownType::Terminate);
            }
        }
        STOPPING.store(false, Ordering::Relaxed);
        libc::sem_post(sem_ptr(&STOP_SEM));
    }
}

fn signal_handler(signum: libc::c_int) -> bool {
    match signum {
        libc::SIGINT => &INT_COUNT,
        libc::SIGTERM => &TERM_COUNT,
        _ => unreachable!(),
    }
    .fetch_add(1, Ordering::Relaxed);
    unsafe {
        libc::sem_post(sem_ptr(&NOTIFY_SEM));
    }
    true
}

pub unsafe fn enter_outer() {
    libc::sem_init(sem_ptr(&NOTIFY_SEM), 0, 0);
    libc::sem_init(sem_ptr(&STOP_SEM), 0, 0);
    thread::spawn(background_thread);
}

pub type InternalGuard = SignalHandlerGuard<'static>;

pub unsafe fn enter(type_: ShutdownType) -> InternalGuard {
    SignalHandlerGuard::new_unsafe(
        match type_ {
            ShutdownType::Interrupt => &[libc::SIGINT],
            ShutdownType::Terminate => &[libc::SIGTERM],
        },
        Arc::new(signal_handler),
    )
}
pub unsafe fn leave(_guard: InternalGuard) {}

pub unsafe fn leave_outer() {
    STOPPING.store(true, Ordering::Relaxed);
    libc::sem_post(sem_ptr(&NOTIFY_SEM));
    libc::sem_wait(sem_ptr(&STOP_SEM));
    libc::sem_destroy(sem_ptr(&NOTIFY_SEM));
    libc::sem_destroy(sem_ptr(&STOP_SEM));
}