Skip to content

Commit

Permalink
Add alarm module
Browse files Browse the repository at this point in the history
This module has two functions;
set: set an alarm, and
cancel: cancels a previously set alarm.
  • Loading branch information
Thomasdezeeuw committed Feb 2, 2018
1 parent 667ae50 commit fecf484
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

- Added `alarm`. ([#830](https://github.com/nix-rust/nix/pull/830))

### Changed

### Fixed
Expand Down
79 changes: 79 additions & 0 deletions src/unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,85 @@ pub fn pause() {
unsafe { libc::pause() };
}

pub mod alarm {
//! Alarm signal scheduling.
//!
//! Scheduling an alarm will trigger a `SIGALRM` signal when the time has
//! elapsed, which has to be caught, because the default action for the
//! signal is to terminate the program. This signal also can't be ignored
//! because the system calls like `pause` will not be interrupted, see the
//! second example below.
//!
//! # Examples
//!
//! Canceling an alarm:
//!
//! ```
//! use nix::unistd::alarm;
//!
//! // Set an alarm for 60 seconds from now.
//! alarm::set(60);
//!
//! // Cancel the above set alarm, which returns the number of seconds left
//! // of the previously set alarm.
//! assert_eq!(alarm::cancel(), Some(60));
//! ```
//!
//! Scheduling an alarm and waiting for the signal:
//!
//! ```
//! use std::time::{Duration, Instant};
//!
//! use nix::unistd::{alarm, pause};
//! use nix::sys::signal::*;
//!
//! // We need to setup an empty signal handler to catch the alarm signal,
//! // otherwise the program will be terminated once the signal is delivered.
//! extern fn signal_handler(_: nix::libc::c_int) { }
//! unsafe { sigaction(Signal::SIGALRM, &SigAction::new(SigHandler::Handler(signal_handler), SaFlags::empty(), SigSet::empty())); }
//!
//! // Set an alarm for 1 second from now.
//! alarm::set(1);
//!
//! let start = Instant::now();
//! // Pause the process until the alarm signal is received.
//! pause();
//!
//! assert!(start.elapsed() >= Duration::from_secs(1));
//! ```
//!
//! # References
//!
//! See also [alarm(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/alarm.html).

use libc;

/// Schedule an alarm signal.
///
/// This will cause the system to generate a `SIGALRM` signal for the
/// process after the specified number of seconds have elapsed.
///
/// Returns the leftover time of a previously set alarm if there was one.
pub fn set(secs: libc::c_uint) -> Option<libc::c_uint> {
assert!(secs != 0, "passing 0 to `alarm::set` is not allowed, to cancel an alarm use `alarm::cancel`");
alarm(secs)
}

/// Cancel an previously set alarm signal.
///
/// Returns the leftover time of a previously set alarm if there was one.
pub fn cancel() -> Option<libc::c_uint> {
alarm(0)
}

fn alarm(secs: libc::c_uint) -> Option<libc::c_uint> {
match unsafe { libc::alarm(secs) } {
0 => None,
secs => Some(secs),
}
}
}

/// Suspend execution for an interval of time
///
/// See also [sleep(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/sleep.html#tag_03_705_05)
Expand Down
51 changes: 50 additions & 1 deletion test/test_unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extern crate tempdir;
use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag};
use nix::unistd::*;
use nix::unistd::ForkResult::*;
use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
use nix::sys::wait::*;
use nix::sys::stat::{self, Mode, SFlag};
use std::{self, env, iter};
Expand All @@ -12,7 +13,7 @@ use std::io::Write;
use std::os::unix::prelude::*;
use tempfile::tempfile;
use tempdir::TempDir;
use libc::{_exit, off_t};
use libc::{self, _exit, off_t};

#[test]
fn test_fork_and_waitpid() {
Expand Down Expand Up @@ -400,3 +401,51 @@ fn test_pipe2() {
let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap());
assert!(f1.contains(FdFlag::FD_CLOEXEC));
}

// Used in `test_alarm`.
static mut ALARM_CALLED: bool = false;

// Used in `test_alarm`.
pub extern fn alarm_signal_handler(raw_signal: libc::c_int) {
assert_eq!(raw_signal, libc::SIGALRM, "unexpected signal: {}", raw_signal);
unsafe { ALARM_CALLED = true };
}

#[test]
fn test_alarm() {
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");

let handler = SigHandler::Handler(alarm_signal_handler);
let signal_action = SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
let old_handler = unsafe {
sigaction(Signal::SIGALRM, &signal_action)
.expect("unable to set signal handler for alarm")
};

// Set an alarm.
assert_eq!(alarm::set(60), None);

// Overwriting an alarm should return the old alarm.
assert_eq!(alarm::set(1), Some(60));

// We should be woken up after 1 second by the alarm, so we'll sleep for 2
// seconds to be sure.
sleep(2);
assert_eq!(unsafe { ALARM_CALLED }, true, "expected our alarm signal handler to be called");

// Reset the signal.
unsafe {
sigaction(Signal::SIGALRM, &old_handler)
.expect("unable to set signal handler for alarm");
}
}

#[test]
fn test_canceling_alarm() {
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");

assert_eq!(alarm::cancel(), None);

assert_eq!(alarm::set(60), None);
assert_eq!(alarm::cancel(), Some(60));
}

0 comments on commit fecf484

Please sign in to comment.