Skip to content

Ability to remove handlers, and ability to add FnOnce handlers instead of FnMut. #130

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ harness = false
name = "issue_97"
path = "tests/main/issue_97.rs"

[[test]]
harness = false
name = "deinit"
path = "tests/main/deinit.rs"

[dev-dependencies]
signal-hook = "0.3"

Expand Down
5 changes: 5 additions & 0 deletions src/block_outcome.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BlockOutcome {
Awaited,
HandlerRemoved,
}
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub enum Error {
MultipleHandlers,
/// Unexpected system error.
System(std::io::Error),
/// Handler was removed
HandlerRemoved,
}

impl Error {
Expand All @@ -18,6 +20,7 @@ impl Error {
Error::NoSuchSignal(_) => "Signal could not be found from the system",
Error::MultipleHandlers => "Ctrl-C signal handler already registered",
Error::System(_) => "Unexpected system error",
Error::HandlerRemoved => "Handler was removed",
}
}
}
Expand Down
130 changes: 122 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,16 @@

mod error;
mod platform;
use block_outcome::BlockOutcome;
pub use platform::Signal;
mod signal;
pub use signal::*;
mod block_outcome;

pub use error::Error;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use std::thread;
use std::thread::{self, JoinHandle};

static INIT: AtomicBool = AtomicBool::new(false);
static INIT_LOCK: Mutex<()> = Mutex::new(());
Expand Down Expand Up @@ -90,7 +92,7 @@ static INIT_LOCK: Mutex<()> = Mutex::new(());
///
/// # Panics
/// Any panic in the handler will not be caught and will cause the signal handler thread to stop.
pub fn set_handler<F>(user_handler: F) -> Result<(), Error>
pub fn set_handler<F>(user_handler: F) -> Result<JoinHandle<()>, Error>
where
F: FnMut() + 'static + Send,
{
Expand All @@ -102,31 +104,31 @@ where
/// # Errors
/// Will return an error if another handler exists or if a system error occurred while setting the
/// handler.
pub fn try_set_handler<F>(user_handler: F) -> Result<(), Error>
pub fn try_set_handler<F>(user_handler: F) -> Result<JoinHandle<()>, Error>
where
F: FnMut() + 'static + Send,
{
init_and_set_handler(user_handler, false)
}

fn init_and_set_handler<F>(user_handler: F, overwrite: bool) -> Result<(), Error>
fn init_and_set_handler<F>(user_handler: F, overwrite: bool) -> Result<JoinHandle<()>, Error>
where
F: FnMut() + 'static + Send,
{
if !INIT.load(Ordering::Acquire) {
let _guard = INIT_LOCK.lock().unwrap();

if !INIT.load(Ordering::Relaxed) {
set_handler_inner(user_handler, overwrite)?;
let result = set_handler_inner(user_handler, overwrite)?;
INIT.store(true, Ordering::Release);
return Ok(());
return Ok(result);
}
}

Err(Error::MultipleHandlers)
}

fn set_handler_inner<F>(mut user_handler: F, overwrite: bool) -> Result<(), Error>
fn set_handler_inner<F>(mut user_handler: F, overwrite: bool) -> Result<JoinHandle<()>, Error>
where
F: FnMut() + 'static + Send,
{
Expand All @@ -138,11 +140,123 @@ where
.name("ctrl-c".into())
.spawn(move || loop {
unsafe {
platform::block_ctrl_c().expect("Critical system error while waiting for Ctrl-C");
match platform::block_ctrl_c() {
Ok(BlockOutcome::Awaited) => {},
Ok(BlockOutcome::HandlerRemoved) => break,
Err(err) => panic!("Critical system error while waiting for Ctrl-C: {err:?}")
};
}
user_handler();
})
.map_err(Error::System)
}


/// Same as [`ctrlc::set_handler`], but uses [`std::ops::FnOnce`] as a handler that only handles one interrupt.
///
/// Register signal handler for Ctrl-C.
///
/// Starts a new dedicated signal handling thread. Should only be called at the start of the program, or after
/// last `_once`-handler already fired (for example, via `.join()`).
///
/// # Example
/// ```no_run
///
/// # use ctrlc::*;
///
/// let fires = 0;
/// let handle = ctrlc::set_handler_once(move || fires + 1).unwrap();
///
/// // interrupt_and_wait(); // platform-dependant
///
/// // First unwrap for thread join, second one to `Option` because handler can be removed without firing.
/// let fires = handle.join().unwrap().unwrap();
/// assert_eq!(fires, 1);
///
/// assert!(ctrlc::remove_all_handlers().is_err()); // This handler should be already removed after firing once.
/// ```
pub fn set_handler_once<F, T>(user_handler: F) -> Result<JoinHandle<Option<F::Output>>, Error>
where
F: FnOnce() -> T + 'static + Send,
T: 'static + Send
{
init_and_set_handler_once(user_handler, true)
}

/// The same as [`ctrlc::try_set_handler`] but uses [`std::ops::FnOnce`] as a handler that only handles one interrupt.
/// The same as [`ctrlc::set_handler_once`] but errors if a handler already exists for the signal(s).
///
/// # Errors
/// Will return an error if another handler exists or if a system error occurred while setting the
/// handler.
pub fn try_set_handler_once<F, T>(user_handler: F) -> Result<JoinHandle<Option<F::Output>>, Error>
where
F: FnOnce() -> T + 'static + Send,
T: 'static + Send
{
init_and_set_handler_once(user_handler, false)
}


fn init_and_set_handler_once<F, T>(user_handler: F, overwrite: bool) -> Result<JoinHandle<Option<F::Output>>, Error>
where
F: FnOnce() -> T + 'static + Send,
T: 'static + Send
{
if !INIT.load(Ordering::Acquire) {
let _guard = INIT_LOCK.lock().unwrap();

if !INIT.load(Ordering::Relaxed) {
let handle = set_handler_inner_once(user_handler, overwrite)?;
INIT.store(true, Ordering::Release);
return Ok(handle);
}
}

Err(Error::MultipleHandlers)
}


fn set_handler_inner_once<F, T>(user_handler: F, overwrite: bool) -> Result<JoinHandle<Option<F::Output>>, Error>
where
F: FnOnce() -> T + 'static + Send,
T: 'static + Send,
{
unsafe {
platform::init_os_handler(overwrite)?;
}

let thread = thread::Builder::new()
.name("ctrl-c".into())
.spawn(move || {
let outcome = unsafe {
platform::block_ctrl_c().expect("Critical system error while waiting for Ctrl-C")
};
if outcome == BlockOutcome::HandlerRemoved {
return None;
}
let result = user_handler();

match remove_all_handlers() {
Ok(()) |
Err(Error::HandlerRemoved) => {},
_ => eprintln!("[ctrlc] System error after waiting for Ctrl-C"),
};
Some(result)
})
.map_err(Error::System)?;

Ok(thread)
}

/// Removes all previously added handlers
pub fn remove_all_handlers() -> Result<(), Error> {
if !INIT.load(Ordering::Acquire) {
return Err(Error::HandlerRemoved);
}
unsafe {
platform::deinit_os_handler()?;
INIT.store(false, Ordering::Relaxed);
}
Ok(())
}
Loading