Skip to content

Add suspend to sys::signal::SigSet #1997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
([#1912](https://github.com/nix-rust/nix/pull/1912))
- Added `mq_timedreceive` to `::nix::mqueue`.
([#1966])(https://github.com/nix-rust/nix/pull/1966)
- Added `libc::sigsuspend` wrapper `suspend` to `nix::sys::signal::SigSet`.

### Changed

Expand Down
12 changes: 12 additions & 0 deletions src/sys/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,18 @@ impl SigSet {
})
}

/// Replaces the signal mask of the calling thread with this signal mask,
/// suspends execution until delivery of a signal whose action is either
/// execution of a signal-catching handler or termination of the process.
/// In the first case it returns after the handler returned and the signal
/// mask is restored to the set that existed prior to the call.
/// In the second case, suspend does not return.
#[cfg(not(target_os = "redox"))] // Redox does not yet support sigsuspend
#[cfg_attr(docsrs, doc(cfg(all())))]
pub fn suspend(&self) {
unsafe { libc::sigsuspend(&self.sigset as *const libc::sigset_t) };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the man page it is possible for this to return with EINTR or EFAULT on Linux. EINTR is the normal case for this. But should we handle EFAULT? It can't happen in this abstraction (and would need unsafe in general in Rust):

EFAULT mask points to memory which is not a valid part of the process address space.

But it also feels weird to ignore errors. And I don't have the access to other *nix systems to check if there are other fault conditions in those cases.

}

/// Converts a `libc::sigset_t` object to a [`SigSet`] without checking whether the
/// `libc::sigset_t` is already initialized.
///
Expand Down
78 changes: 77 additions & 1 deletion test/sys/test_signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use nix::errno::Errno;
use nix::sys::signal::*;
use nix::unistd::*;
use std::convert::TryFrom;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};

#[test]
fn test_kill_none() {
Expand Down Expand Up @@ -143,3 +143,79 @@ fn test_signal() {
// Restore default signal handler
unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap();
}

lazy_static! {
static ref SIGUSR1CNT: AtomicU8 = AtomicU8::new(0);
static ref SIGUSR2CNT: AtomicU8 = AtomicU8::new(0);
}

extern "C" fn test_suspend_handler(signal: libc::c_int) {
let signal = Signal::try_from(signal).unwrap();
let rel = Ordering::Relaxed;
match signal {
Signal::SIGUSR1 =>
SIGUSR1CNT.store(SIGUSR1CNT.load(rel) + 1, rel),
Signal::SIGUSR2 =>
SIGUSR2CNT.store(SIGUSR1CNT.load(rel) + 1, rel),
_ => panic!("This handler got an unexpected signal."),
}
}

/// Assert that unblocked sighandlers are executed,
/// and blocked ones are not.
#[test]
#[cfg(feature = "signal")]
fn test_suspend() {
use std::sync::{Arc, Barrier};
use std::time::Duration;
use std::thread;

let barrier = Arc::new(Barrier::new(2));
let b = Arc::clone(&barrier);
let thandle = thread::spawn(move || {
// First block SIGUSR{1,2}
let mut mask = SigSet::empty();
mask.add(SIGUSR1);
mask.add(SIGUSR2);
mask.thread_set_mask().expect("Cannot block signal.");

// Set up new handler
let act = SigAction::new(
SigHandler::Handler(test_suspend_handler),
SaFlags::empty(),
SigSet::empty(),
);
unsafe { sigaction(SIGUSR1, &act) }
.expect("Could not set handler.");
unsafe { sigaction(SIGUSR2, &act) }
.expect("Could not set handler.");

// tell im ready
b.wait();

// Only SIGUSR1 will be blocked.
let mut susp_mask = SigSet::empty();
susp_mask.add(SIGUSR1);

susp_mask.suspend();

// wait before quitting
// b.wait();
});

// wait for the handlers being set up.
barrier.wait();

// wait a little
thread::sleep(Duration::from_millis(10));

// TODO
//kill( which pid?, SIGUSR1);
//kill( which pid?, SIGUSR2);

thandle.join().expect("The thread should have quit.");


assert_eq!(SIGUSR1CNT.load(Ordering::Relaxed), 0);
assert_eq!(SIGUSR2CNT.load(Ordering::Relaxed), 1);
}