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();
}
}
pub fn settle_deferred_signals(fd: i32) {
use crate::ported::signals::{queue_front, queue_rear, queueing_enabled};
use std::sync::atomic::Ordering::SeqCst;
let pending = || queue_front.load(SeqCst) != queue_rear.load(SeqCst);
while pending() && queueing_enabled.load(SeqCst) == 0 {
crate::ported::signals_h::run_queued_signals();
if !pending() {
break;
}
let mut p = libc::pollfd { fd, events: libc::POLLIN, revents: 0 };
if unsafe { libc::poll(&mut p, 1, 10) } > 0 {
break;
}
}
}
#[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"
);
}
#[test]
fn settle_runs_a_signal_deferred_behind_another_threads_table_lock() {
use crate::ported::signals::{queue_front, queue_rear, zhandler};
use std::time::Instant;
let _g = crate::test_util::global_state_lock();
let (done, finished) = mpsc::channel();
std::thread::spawn(move || {
let pending = || queue_front.load(Ordering::SeqCst) != queue_rear.load(Ordering::SeqCst);
assert!(!pending(), "the queue starts empty");
let mut pipe = [0i32; 2];
assert_eq!(unsafe { libc::pipe(pipe.as_mut_ptr()) }, 0);
let (locked, locked_rx) = mpsc::channel();
let holder = std::thread::spawn(move || {
let table = crate::ported::params::paramtab().write().expect("paramtab");
locked.send(()).unwrap();
std::thread::sleep(Duration::from_millis(100));
drop(table);
});
locked_rx.recv().unwrap();
zhandler(libc::SIGCHLD);
assert!(pending(), "the signal is deferred while another thread holds the table");
let start = Instant::now();
super::settle_deferred_signals(pipe[0]);
assert!(!pending(), "the deferred signal ran once the table was free");
assert!(start.elapsed() >= Duration::from_millis(50), "it waited for the holder");
holder.join().unwrap();
unsafe {
libc::close(pipe[0]);
libc::close(pipe[1]);
}
let _ = done.send(());
});
assert!(finished.recv_timeout(Duration::from_secs(30)).is_ok(), "settle_deferred_signals hung");
}
#[test]
fn run_queued_signals_does_not_spin_while_queueing_is_enabled() {
use crate::ported::signals::{queue_front, queue_rear, zhandler};
use crate::ported::signals_h::{queue_signals, run_queued_signals, unqueue_signals};
let _g = crate::test_util::global_state_lock();
let (done, finished) = mpsc::channel();
std::thread::spawn(move || {
let pending = || queue_front.load(Ordering::SeqCst) != queue_rear.load(Ordering::SeqCst);
assert!(!pending(), "the queue starts empty");
queue_signals();
zhandler(libc::SIGCHLD);
assert!(pending(), "the signal is queued while queueing is enabled");
run_queued_signals(); assert!(pending(), "it stays queued until the level drops");
unqueue_signals();
assert!(!pending(), "dropping the level to zero dispatches it");
let _ = done.send(());
});
assert!(
finished.recv_timeout(Duration::from_secs(30)).is_ok(),
"run_queued_signals spun while queueing_enabled was non-zero"
);
}
}