use crate::ported::signals_h::{queue_signals, unqueue_signals};
#[must_use = "signals are unqueued when the guard is dropped; bind it to a variable"]
pub struct QueuedSignals(());
impl QueuedSignals {
pub fn enter() -> Self {
queue_signals();
QueuedSignals(())
}
}
impl Drop for QueuedSignals {
fn drop(&mut self) {
unqueue_signals();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::Ordering;
#[test]
fn the_guard_pairs_queue_and_unqueue_on_every_exit() {
let _g = crate::test_util::global_state_lock();
let level = || crate::ported::signals::queueing_enabled.load(Ordering::SeqCst);
let before = level();
{
let _q = QueuedSignals::enter();
assert_eq!(level(), before + 1);
let _nested = QueuedSignals::enter();
assert_eq!(level(), before + 2);
}
assert_eq!(level(), before);
fn early_return(flag: bool) -> bool {
let _q = QueuedSignals::enter();
if flag {
return true;
}
false
}
assert!(early_return(true));
assert!(!early_return(false));
assert_eq!(level(), before);
}
}
#[cfg(test)]
mod deferral_tests {
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::time::Duration;
#[test]
fn a_signal_arriving_while_the_parameter_table_is_locked_is_deferred_not_deadlocked() {
let _g = crate::test_util::global_state_lock();
let (done, finished) = mpsc::channel();
std::thread::spawn(move || {
use crate::ported::signals::{queue_front, queue_rear, zhandler};
let pending = || queue_front.load(Ordering::SeqCst) != queue_rear.load(Ordering::SeqCst);
assert!(!pending(), "the queue starts empty");
let held = crate::ported::params::paramtab().write().expect("paramtab");
zhandler(libc::SIGCHLD); assert!(pending(), "the signal is queued, not dispatched, while the table is held");
drop(held);
crate::ported::signals_h::run_queued_signals();
assert!(!pending(), "the deferred signal is dispatched once the table is free");
let _ = done.send(());
});
assert!(
finished.recv_timeout(Duration::from_secs(30)).is_ok(),
"zhandler deadlocked on the parameter table its own thread holds"
);
}
}