From fecf4840db5d6f958e50e6171141d4842a7602c3 Mon Sep 17 00:00:00 2001 From: Thomas de Zeeuw Date: Sun, 7 Jan 2018 22:09:58 +0100 Subject: [PATCH] Add alarm module This module has two functions; set: set an alarm, and cancel: cancels a previously set alarm. --- CHANGELOG.md | 2 ++ src/unistd.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++ test/test_unistd.rs | 51 ++++++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbe6110ab7..4be97c0429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/unistd.rs b/src/unistd.rs index 0f47a7fc35..6b600d0c6d 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -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 { + 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 { + alarm(0) + } + + fn alarm(secs: libc::c_uint) -> Option { + 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) diff --git a/test/test_unistd.rs b/test/test_unistd.rs index e15fec17d7..d78b6ba8df 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -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}; @@ -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() { @@ -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)); +}