use std::{marker::PhantomData, ptr, rc::Rc};
pub(super) struct SignalMask {
previous: libc::sigset_t,
selected: libc::sigset_t,
_same_thread: PhantomData<Rc<()>>,
}
#[allow(unsafe_code)] impl SignalMask {
pub(super) fn block(signal: libc::c_int) -> Self {
let mut selected = unsafe { std::mem::zeroed::<libc::sigset_t>() };
assert_eq!(unsafe { libc::sigemptyset(&raw mut selected) }, 0);
assert_eq!(unsafe { libc::sigaddset(&raw mut selected, signal) }, 0);
let mut previous = unsafe { std::mem::zeroed::<libc::sigset_t>() };
assert_eq!(
unsafe {
libc::pthread_sigmask(libc::SIG_BLOCK, &raw const selected, &raw mut previous)
},
0
);
Self {
previous,
selected,
_same_thread: PhantomData,
}
}
pub(super) fn unblock(&self) {
assert_eq!(
unsafe {
libc::pthread_sigmask(libc::SIG_UNBLOCK, &raw const self.selected, ptr::null_mut())
},
0
);
}
pub(super) fn was_blocked(&self, signal: libc::c_int) -> bool {
unsafe { libc::sigismember(&raw const self.previous, signal) == 1 }
}
}
#[allow(unsafe_code)] impl Drop for SignalMask {
fn drop(&mut self) {
let result = unsafe {
libc::pthread_sigmask(libc::SIG_SETMASK, &raw const self.previous, ptr::null_mut())
};
if !std::thread::panicking() {
assert_eq!(result, 0, "restore test thread's signal mask");
}
}
}
#[test]
fn nested_masks_restore_the_original_thread_state() {
let signal = libc::SIGUSR2;
let initially_blocked = current_is_blocked(signal);
{
let _outer = SignalMask::block(signal);
assert!(current_is_blocked(signal));
{
let inner = SignalMask::block(signal);
inner.unblock();
assert!(!current_is_blocked(signal));
}
assert!(current_is_blocked(signal));
}
assert_eq!(current_is_blocked(signal), initially_blocked);
}
#[allow(unsafe_code)] fn current_is_blocked(signal: libc::c_int) -> bool {
let mut current = unsafe { std::mem::zeroed::<libc::sigset_t>() };
assert_eq!(
unsafe { libc::pthread_sigmask(libc::SIG_BLOCK, ptr::null(), &raw mut current) },
0
);
unsafe { libc::sigismember(&raw const current, signal) == 1 }
}