Code Monkey home page Code Monkey logo

rust-ctrlc's Introduction

CtrlC

A simple easy to use wrapper around Ctrl-C signal.

Documentation

Example usage

In cargo.toml:

[dependencies]
ctrlc = "3.4"

then, in main.rs

use std::sync::mpsc::channel;
use ctrlc;

fn main() {
    let (tx, rx) = channel();
    
    ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))
        .expect("Error setting Ctrl-C handler");
    
    println!("Waiting for Ctrl-C...");
    rx.recv().expect("Could not receive from channel.");
    println!("Got it! Exiting..."); 
}

Try the example yourself

cargo build --examples && target/debug/examples/readme_example

Handling SIGTERM and SIGHUP

Add CtrlC to Cargo.toml using termination feature and CtrlC will handle SIGINT, SIGTERM and SIGHUP.

License

Licensed under either of

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.

Similar crates

There are alternatives that give you more control over the different signals and/or add async support.

rust-ctrlc's People

Contributors

benjins avatar bretello avatar cuviper avatar dagit avatar dancek avatar debris avatar detegr avatar drusellers avatar ecnelises avatar equal-l2 avatar flba-eb avatar frazar avatar henninglive avatar ignatenkobrain avatar ilya-bobyr avatar jikstra avatar john-sharratt avatar kornelski avatar kpcyrd avatar mrzleo avatar niyaznigmatullin avatar phip1611 avatar printfn avatar ranweiler avatar retep998 avatar rtzoeller avatar saethlin avatar taiki-e avatar tomaka avatar yoursvivek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

rust-ctrlc's Issues

Ctrl+C is awkward with Windows

With Zola for example it says:

Press Ctrl+C to stop

but if you press that, it doesnt stop. You have to press it 3 times, or hold it. Then it does this:

Encountered an error: other os error
Enable a logger to see more information.

App dies to ctrl+c when run from cargo run

I'm trying it on Windows 7, when I press ctrl+C the handler is called but then the process dies anyway...
Looks like something is wrong with returning TRUE from the os_handler
Will try to debug this more later...

Handler code does not finish

Trying to use the crate I manage to set a handler with some clean up code. When a signal is caught, the handler is indeed executed, but it's killed half way before it can finish (no panic or untreated errors are being generated in the handler function). The behavior occurs with both cargo run and through systemctl commands. Any ideas why this is happening?
The code looks like this:

fn set_termination_handler() {
    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();
    ctrlc::set_handler(move || {
        info!("Termination signal received - Cleaning up");
        clean_up();
        r.store(false, Ordering::SeqCst);
    }).expect("Error setting Termination handler");
}

Now, I dont actually have a necessity for the atomic since I'm not checking the condition in a busy loop, instead I'm blocking my future (Actix web server) with await after setting the handler.
As I said, the handler is indeed called, but it never finish the execution of its instructions. Any ideas?

error[E0015]: calls in statics are limited

Rust 1.56.1 compiles error:

error[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants
  --> d:\Users\jinqing01\.cargo\registry\src\github.com-1ecc6299db9ec823\ctrlc-3.4.0\src\lib.rs:63:31
   |
63 | static INIT_LOCK: Mutex<()> = Mutex::new(());
   |                               ^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0015`.
error: could not compile `ctrlc` due to previous error

Allow passing through the 2nd ctrl+c

I'm considering adding a ctrl+c handler, but since I'm only setting a bool it might take a while until my program is actually picking it up. The application would feel broken since it can not be terminated in a reliable way even after pressing ctrl+c multiple times. I'm thinking about 2 possible solutions:

  • inside the signal handler, allow passing through the signal so if ctrl+c is pressed, I would first check the value of the boolean, if it is true I set it to false, if it is false, I let the signal pass and terminate the process
  • allow removing the signal handler. Before I start an action that should be cancelable, I register a signal handler and if ctrl+c is pressed I'm setting the boolean to false and unregister the signal handler so that the second ctrl+c isn't caught anymore (Related to #24)

Basically I'm looking for behavior similar to this (implementation doesn't need to be identical as long as the behavior is the same):

from time import sleep
try:
    try:
        print('running...')
        sleep(20)
    except KeyboardInterrupt:
        # requesting shutdown
        print('exiting...')
        sleep(5)
except KeyboardInterrupt:
    # kill application instantly
    pass

Ctrl-C while waiting for IO

ctrlc doesn't work as I would expect it to when SIGINT is sent while the main thread blocks waiting for IO. Consider this example:

use std::net::TcpListener;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

extern crate ctrlc;

fn main() {
    let sigint = Arc::new(AtomicBool::new(false));
    let sigint_clone = sigint.clone();
    ctrlc::set_handler(move || {
            sigint_clone.store(true, Ordering::SeqCst);
        })
        .expect("Error setting SIGINT handler!");

    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();

    while !sigint.load(Ordering::SeqCst) {
        // blocking IO call
        let (_, _) = listener.accept().unwrap();
    }
    println!("Gracefully exiting...");
}

If I run this code and press Ctrl-C, I get my command line back immediately (without "Gracefully exiting..." being printed) and the main thread hangs somewhere in the background, waiting to accept() a TCP connection. (NB that you can't re-run the program at this point because the address is still in use.) If I then establish a connection (using e.g. curl localhost:8080), it is accepted by the listener, the thread resumes, the message is printed and the program exits.

The same thing happens if you wait for user input using io::stdin().read_line(), only this case is of course resolved by typing anything + Enter at the same command line after the Ctrl-C. This leads me to think this is a general property of how ctrlc interacts with blocking IO.

I would argue it would be more intuitive / useful / generally preferable if ctrlc were able to terminate any pending IO, so that the shutdown can be completed. On the other hand, I understand there might be no way to achieve this in Rust. So if there's no workaround for this, I suggest it would be nice to mention this limitation in the docs.

Also, thanks for an otherwise very useful crate! :)

Prone to spurious calling of the handler

            loop {
                let _ = CVAR.wait(MUTEX.lock().unwrap());
                user_handler();
            }

Waiting on a condition variable is prone to spurious wakeups. Therefore there should likely be some sort of check to ensure it wasn't a spurious wakeup.

Incorrect os_handler() signature

The nix os_handler() needs be declared extern “C” since it is being called from libc. The current signature expects the rust calling convention, but it is being called with the C calling convention. This doesn’t crash on x64 or ARM where both C and rust pass the first arguments in registries, but will probably crash on x86 where C passes all arguments on the stack. Also, the register order is probably different, so the sig argument is probably invalid.

High CPU usage using provided example

// Detect ctrl+c 
// to exit the program
pub fn start_ctrlc()
{
    thread::spawn(move || 
    {
        let running = Arc::new(AtomicBool::new(true));
        let r = running.clone();

        ctrlc::set_handler(move || {
            r.store(false, Ordering::SeqCst);
        }).unwrap();

        while running.load(Ordering::SeqCst) {}
        exit();
    });
}

Using this my CPU usage stays at a constant 25%. Doesn't happen if I don't start that function.
If I'm supposed to do something to limit thread usage it should be documented.

Relicense under dual MIT/Apache-2.0

Why?

The MIT license requires reproducing countless copies of the same copyright
header with different names in the copyright field, for every MIT library in
use. The Apache license does not have this drawback, and has protections from
patent trolls and an explicit contribution licensing clause. However, the
Apache license is incompatible with GPLv2. This is why Rust is dual-licensed as
MIT/Apache (the "primary" license being Apache, MIT only for GPLv2 compat), and
doing so would be wise for this project. This also makes this crate suitable
for inclusion in the Rust standard distribution and other project using dual
MIT/Apache.

How?

To do this, get explicit approval from each contributor of copyrightable work
(as not all contributions qualify for copyright) and then add the following to
your README:

## License

Licensed under either of
 * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
 * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as above, without any
additional terms or conditions.

and in your license headers, use the following boilerplate (based on that used in Rust):

// Copyright (c) 2015 t developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

And don't forget to update the license metadata in your Cargo.toml!

Contributor checkoff

ctrlc::Counter

A common pattern when handling Ctrl+C is setting a flag in the handler and polling the flag somewhere else in the application. The current API does allow you to implement this, but there is significant extra overhead because the extra thread, pipes and semaphores. I think it would make sense to try and build a safe abstraction with less overhead for this use case.

I suggest implementing a new ctrlc::Counter abstraction as a struct. A ctrlc::Counter increases its value each time a specific signal is received. It will implement Send and Sync, allowing other threads poll and reset its value. Creating a new ctrlc::Counter registers a native signal handler. The lifetime of the native handler is connected to the ctrlc::Counter and will be unregistered when the ctrlc::Counter is dropped, this allows users unregister the handler if they want. We would probably also need to distinguish between SIGINT and SIGTERM, and possibly between CTRL+C and CTRL+BREAK. So, implementing a signal enum as suggested in #26 is probably a good idea.

I think using a counter is better than a simple flag, a counter can be used in all cases where you would use a flag, but the counter opens for additional use cases not covered by a simple flag.

The suggested abstraction is similar to the one provided by signalbool, a crate owned by @lilydjwg. I guess he created this after our discussion in #20.

I feel like this interface and the interface I suggested in #28 fits well with Rust’s philosophy of safe abstractions.

Multiple set_handler calls don't properly overwrite? 🤔

Hello!

Not sure if I'm holding it wrong or... 🤔

The docs for set_handler say:

Will return an error if a system error occurred while setting the handler.

and for try_set_handler say:

Will return an error if another handler exists or if a system error occurred while setting the handler.

But a modified snippet from the README like this:

use ctrlc;
use std::sync::mpsc::channel;

fn main() {
    let (tx, rx) = channel();

    ctrlc::set_handler(move || {}).expect("Error setting Ctrl-C handler");

    ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))
        .expect("Error setting Ctrl-C handler for the second time");

    println!("Waiting for Ctrl-C...");
    rx.recv().expect("Could not receive from channel.");
    println!("Got it! Exiting...");
}

panics with:

thread 'main' panicked at src/main.rs:10:10:
Error setting Ctrl-C handler for the second time: MultipleHandlers

So it seems set_handler can return MultipleHandlers? 🤔

I'm on OSX 14.4.1

Problem in win11 with powershell 7.4.1 (fine in command prompt)

Running 3.4.2

This code

fn exit() {
    eprintln!("Exiting");
    std::process::exit(0);
}

fn main() {
    ctrlc::set_handler(exit).expect("Error setting Ctrl-C handler");
    loop {
        println!("Waiting for ctrl-c");
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}

works fine in command prompt, but inside powershell 7.4.1 it is ignored. powershell 5.1 is also fine!

https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4

v3.1.5 ctrlc::set_handler() weird errors

ctrlc::set_handler(move || { })

from rust analyzer: Syntax Error: expected BANG
from cargo / rustc:

error: expected one of ! or ::, found (

 ctrlc::set_handler(move || { })
                   ^ expected one of ! or ::

in addition my editor(vsc) does not have any autocomplete at all on it, but it has the docs shown when you finish the function. this does look like a compiler issue or something else, but this is the only time i have gotten this

os: Windows 10
rustc: 1.44.1
ctrlc ver: 3.1.5

Async-signal safety, race condition, spurious wakeups

  1. StaticCondvar on *nix system is implemented using pthread condition
    variables. This is problematic because pthread_cond_signal is not safe to use
    with asynchronous signal handlers:
    (http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_cond_broadcast.html)

  2. There is inherent race condition between setting DONE variable and condition
    variable notification. Consider following execution order:

    A: if !DONE.load(Ordering::Relaxed) { 
    B: super::features::DONE.store(true, Ordering::Relaxed);
    B: super::features::CVAR.notify_all();
    A: let _ = CVAR.wait(MUTEX.lock().unwrap());
    

    In this situation thread A will not notice that DONE is true, and will have
    to wait for another notification (or spurious wakeup).

  3. In case of spurious wakeup from condition variable, user handler is called
    unconditionally. This could be potentially fixed using swap / compare_and_swap.

Need to bump Nix to pick up fix for riscv64

TL;DR: http doesn't build on riscv64; root cause appears to be ctrlc using version 0.17 of nix which doesn't have the fix that's in version 0.18.

This is just a tracking issue. I expect to follow up with a pull requests once I have verified the fix.

Make ctrlc::platform::block_ctrl_c() public?

Is it possible to make ctrlc::platform::block_ctrl_c() public? I am currently using a Condvar to emulate the behavior of this function and being able to use it directly would greatly simplify things:

use std::sync::{Arc, Condvar, Mutex};

let pair = Arc::new((Mutex::new(false), Condvar::new()));
let copy = pair.clone();

ctrlc::set_handler(move || {
    let &(ref lock, ref cvar) = &*copy;
    let mut started = lock.lock().unwrap();
    *started = true;
    cvar.notify_one();
})
.expect("unable to set signal handler");

let &(ref lock, ref cvar) = &*pair;

let mut started = lock.lock().unwrap();
while !*started {
    started = cvar.wait(started).unwrap();
}

Please support using signals

The fix for #6 switched to a spinloop, which wakes up periodically. This wastes power by waking up the CPU and preventing it from reaching deep sleep states; in addition, this delays responsiveness to Ctrl-C.

Please consider switching back to signals. This would not require a large number of changes, just a few simple ones:

  • Create a pipe (libc::pipe or something like the os_pipe crate), and mark both ends as CLOEXEC.
  • Set the signal handler and spawn the thread, as you do now.
  • In the signal handler, write one byte to the write end of the pipe. You can safely call write() from a signal handler.
  • In the thread, do a blocking read of a byte from the pipe. When that read completes, set the condition variable.

This eliminates the spinloop, and allows the thread to sleep forever waiting for a signal rather than waking up periodically.

Optionally (not required), you could add an additional function that just sets up the signal handler and the pipe, but not the thread, for users who instead want to put the read end of the pipe into an event loop.

Migrate to `windows`/`-sys` crate

winapi is a community-maintained crate for windows bindings that has received only a single update in the last year. Microsoft has officially released their own set of bindings providing both a raw low-level crate, windows-sys, and a higherish-level crate (with some helper traits and etc), windows.
As the general Rust ecosystem is migrating to the new windows crates, ctrlc should follow. It would be preferable to use the windows-sys crate over the windows crate as it should cut down on any extra compilation times. Additionally, since the scale of this project is relatively small, I don't think it would benefit from any abstractions made by the windows crate.

MultipleHandler's using nohup to run binary

Hello!

I have detected a weird result using your crate. I am programming a File monitor tool in Rust just for context.

Suddenly, my CI tests failed to give this stack trace:

thread 'main' panicked at 'Error setting Ctrl-C handler: MultipleHandlers': src/monitor.rs:165
   0: <backtrace::capture::Backtrace as core::default::Default>::default
   1: log_panics::Config::install_panic_hook::{{closure}}
   2: std::panicking::rust_panic_with_hook
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:702:17
   3: std::panicking::begin_panic_handler::{{closure}}
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:588:13
   4: std::sys_common::backtrace::__rust_end_short_backtrace
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/sys_common/backtrace.rs:138:18
   5: rust_begin_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:584:5
   6: core::panicking::panic_fmt
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:142:14
   7: core::result::unwrap_failed
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/result.rs:1785:5
   8: fim::monitor::monitor::{{closure}}
   9: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
  10: tokio::runtime::park::CachedParkThread::block_on
  11: tokio::runtime::scheduler::multi_thread::MultiThread::block_on
  12: tokio::runtime::runtime::Runtime::block_on
  13: fim::main
  14: std::sys_common::backtrace::__rust_begin_short_backtrace
  15: std::rt::lang_start::{{closure}}
  16: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:283:13
      std::panicking::try::do_call
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:492:40
      std::panicking::try
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:456:19
      std::panic::catch_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panic.rs:137:14
      std::rt::lang_start_internal::{{closure}}
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/rt.rs:148:48
      std::panicking::try::do_call
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:492:40
      std::panicking::try
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:456:19
      std::panic::catch_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panic.rs:137:14
      std::rt::lang_start_internal
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/rt.rs:148:20
  17: main
  18: __libc_start_main
  19: <unknown>

After a lot of research, I detected that the ctrlc crate is detecting multiple handlers to something like background execution. The FIM process is called with & in some of the failed tests but from my shell, the error doesn't appear at all. The only way to reproduce this issue is by using the nohup command to start up FIM. Then the error is always present.
In this link, you have the failing code and the solution applied now:
Failing code: https://github.com/Achiefs/fim/blob/90b39d5e53b3c2952fc4ad09e14f57ea3ad8764a/src/monitor.rs#L152-L164
Partial solution: https://github.com/Achiefs/fim/blob/9455ce7a6d6fee4f91a5a6a6021cfca58c7595b2/src/monitor.rs#L152-L167

Apart from that, I want to know if there is some solution, workaround or a way to manage the SIGINT signal when nohup is used. I think this is related to nohup attaching the FIM process to the init process (That probably handles SIGINT). But I am not an expert in this field.

It also fails on Github actions using & more info here: https://docs.github.com/en/actions/managing-workflow-runs/canceling-a-workflow#steps-github-takes-to-cancel-a-workflow-run

ctrl-c not working when key remapped on linux

My .xmodmap has Command and control switched as I'm on a mac but running Linux (nixos). Ctrl-c (for which I press Command-c) works for non-rust stuff but not for verco crate that uses this. I know this is pretty esoteric, but I figure it'd be a good corner case to nail down and as ctrl-c crate is so widely used it's good to make it rock solid.

.xmodmap contents:

clear control
clear mod4

keycode 105 =
keycode 206 =

keycode 133 = Control_L NoSymbol Control_L
keycode 134 = Control_R NoSymbol Control_R
keycode 37 = Super_L NoSymbol Super_L

add control = Control_L
add control = Control_R
add mod4 = Super_L

When I get a spare minute I'll try and create a failing test, but first step is to log it as an issue.

termination feature infects the entire workspace

Cargo will unify the features across crates, so if you have in your workspace two different crates that depend on ctrlc, one with termination feature enabled and other without, then the binary without it enabled will implicitly get the termination feature enabled for it as well, potentially leading to unintended behaviour.

Switch back stream to its initial flags ?

Hello !

I've encountered a situation where a rust binary (volta.sh to name it) uses ctrlc which changes the stream to non-blocking here :

if let Err(e) = fcntl::fcntl(PIPE.1, fcntl::FcntlArg::F_SETFL(fcntl::OFlag::O_NONBLOCK)) {

And then later, my python program calling the rust binary faces the exception BlockingIOError: [Errno 11] write could not complete without blocking when trying to write large amounts of text to its own stderr.
It seems strange that it is the same fd, but I've confirmed that F_GETFL indicates that the O_NONBLOCK bit of the python stream has been set with the call fcntl.fcntl(stream, fcntl.F_GETFL) & os.O_NONBLOCK

Does that make sense to call fcntl again and restore the original flags when binaries using ctrlc exit normally ?

Handling runs after the loop executes

image
As you can see, the above image show $ $ Keyboard intterupt. Use `exit` to exit
What I expect it to be is something like

$ *ctrl+c*
Keyboard intterupt. Use `exit` to exit`
$

But that didn't happened. It runs the next iteration from my loop. Basically

prompt
wait for user input
ctrl+c
prompt
SIGINT handling
wait for user input

It should be

prompt
wait for user input
ctrl+c
SIGINT handling
prompt
wait for user input

(SIGINT handling and 2nd prompt supposed to swap places)
I don't know if this is a bug or not but I can send more details if required

Make it work inside `cargo run`

Thank you for the crate.
It currently works on the university system (currently win10) with cmd and powershell, while I use my program directly.
When I start it with cargo run the handler will not execute. Can I enable that?

`Init("sem_init failed")` on macOS

I tried to run the example program from the docs on macOS 10.12.1, but the call to set_handler fails with Init("sem_init failed").

I started to try to look into why this is but got a little confused--it looks like this error is raised when sem_init returns -1, so I tried to look up the docs for sem_init to see what can lead to -1 being returned. It's possible I'm looking at the wrong docs but it seems like it can return three different potential error values (EINVAL, ENOSPC, ENOSYS, or EPERM) none of which are defined as -1, which clearly isn't true since it's returning -1 for me.

ctrlc::Channel

One problem that has been mentioned(#24, #22) is that there is currently no way to unregister a signal handler. I suggest we solve this by building a new abstraction. This new abstraction would be a struct called something like ctrlc::Channel. Creating a new one would register a native signal handler and create a pipe/channel that we write to in the native signal handler. It would provide recv() and recv_timeout() methods for blocking on and reading from the pipe/channel. Last but not least, it would provide methods for unregistering the handler and destroying itself, this would be called on drop. It would implement Send, but not Sync.

I feel like this interface and the interface I suggested in #27 fits better with Rust’s philosophy of safe abstractions and lifetimes. It would also transfer the responsibility of handling threads, closures and panics to the user. Users can avoid the extra thread if they want and it would fix the current problem with panics in the user provided closure. We would still keep the old API, but we would reimplement it on top of this abstraction.

I suggest we also implement #26, so the ctrlc::Channel would return signal type when read from.

Cleaning up spawned threads.

I has a service that in addition to processing some data also sets up a web frontend with iron in a thread.

ie

scope.spawn(move || {
            web::serve();
});

I intercept ctrl-c like to stop my data processing like so

ctrlc::set_handler(move || {
stop_data_processing();
}).expect("Error setting Ctrl-C handler");

However, this leaves the web server process still running. Without the handler all processes are cleaned up at exit. What do I need to do to clean up all spawn threads ?

Thanks

Possible memory leak?

using:

[dependencies]
ctrlc = "3.2.2"

given this code:

fn main() -> Result<(),Box<dyn std::error::Error>>{
    ctrlc::set_handler(|| println!("receive ctrl+c signal 2"))?;
    println!("Hello, world!");
    Ok(())
}

after running valgrind:

valgrind --leak-check=full -show-leak-kinds=all --track-origins=yes --verbose ./target/debug/leakctrlc

Hello, world!
==14888== 
==14888== HEAP SUMMARY:
==14888==     in use at exit: 431 bytes in 6 blocks
==14888==   total heap usage: 19 allocs, 13 frees, 3,633 bytes allocated
==14888== 
==14888== Searching for pointers to 6 not-freed blocks
==14888== Checked 2,205,592 bytes
==14888== 
==14888== 7 bytes in 1 blocks are still reachable in loss record 1 of 6
==14888==    at 0x484DCD3: realloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14888==    by 0x14FD01: alloc::raw_vec::finish_grow (alloc.rs:126)
==14888==    by 0x14FFF8: alloc::ffi::c_str::CString::_from_vec_unchecked (raw_vec.rs:419)
==14888==    by 0x114BBB: <T as alloc::ffi::c_str::CString::new::SpecNewImpl>::spec_new_impl (c_str.rs:271)
==14888==    by 0x116C9F: alloc::ffi::c_str::CString::new (c_str.rs:316)
==14888==    by 0x11800C: std::thread::Builder::spawn_unchecked_::{{closure}} (mod.rs:478)
==14888==    by 0x11269C: core::option::Option<T>::map (option.rs:929)
==14888==    by 0x11798A: std::thread::Builder::spawn_unchecked_ (mod.rs:477)
==14888==    by 0x1177C7: std::thread::Builder::spawn_unchecked (mod.rs:459)
==14888==    by 0x11835B: std::thread::Builder::spawn (mod.rs:391)
==14888==    by 0x111BB4: ctrlc::set_handler (lib.rs:106)
==14888==    by 0x112BE0: leakctrlc::main (main.rs:2)
==14888== 
==14888== 16 bytes in 1 blocks are still reachable in loss record 2 of 6
==14888==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14888==    by 0x13ABC5: std::sys::unix::thread::Thread::new (alloc.rs:89)
==14888==    by 0x117DFC: std::thread::Builder::spawn_unchecked_ (mod.rs:529)
==14888==    by 0x1177C7: std::thread::Builder::spawn_unchecked (mod.rs:459)
==14888==    by 0x11835B: std::thread::Builder::spawn (mod.rs:391)
==14888==    by 0x111BB4: ctrlc::set_handler (lib.rs:106)
==14888==    by 0x112BE0: leakctrlc::main (main.rs:2)
==14888==    by 0x115CFA: core::ops::function::FnOnce::call_once (function.rs:248)
==14888==    by 0x116F2D: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:122)
==14888==    by 0x117610: std::rt::lang_start::{{closure}} (rt.rs:145)
==14888==    by 0x131C0D: std::rt::lang_start_internal (function.rs:280)
==14888==    by 0x1175DF: std::rt::lang_start (rt.rs:144)
==14888== 
==14888== 24 bytes in 1 blocks are still reachable in loss record 3 of 6
==14888==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14888==    by 0x1128AB: alloc::alloc::alloc (alloc.rs:89)
==14888==    by 0x112936: alloc::alloc::Global::alloc_impl (alloc.rs:171)
==14888==    by 0x112B19: <alloc::alloc::Global as core::alloc::Allocator>::allocate (alloc.rs:231)
==14888==    by 0x11280C: alloc::alloc::exchange_malloc (alloc.rs:320)
==14888==    by 0x117CE2: std::thread::Builder::spawn_unchecked_ (boxed.rs:215)
==14888==    by 0x1177C7: std::thread::Builder::spawn_unchecked (mod.rs:459)
==14888==    by 0x11835B: std::thread::Builder::spawn (mod.rs:391)
==14888==    by 0x111BB4: ctrlc::set_handler (lib.rs:106)
==14888==    by 0x112BE0: leakctrlc::main (main.rs:2)
==14888==    by 0x115CFA: core::ops::function::FnOnce::call_once (function.rs:248)
==14888==    by 0x116F2D: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:122)
==14888== 
==14888== 48 bytes in 1 blocks are still reachable in loss record 4 of 6
==14888==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14888==    by 0x1324F8: std::thread::Thread::new (alloc.rs:89)
==14888==    by 0x1179A9: std::thread::Builder::spawn_unchecked_ (mod.rs:477)
==14888==    by 0x1177C7: std::thread::Builder::spawn_unchecked (mod.rs:459)
==14888==    by 0x11835B: std::thread::Builder::spawn (mod.rs:391)
==14888==    by 0x111BB4: ctrlc::set_handler (lib.rs:106)
==14888==    by 0x112BE0: leakctrlc::main (main.rs:2)
==14888==    by 0x115CFA: core::ops::function::FnOnce::call_once (function.rs:248)
==14888==    by 0x116F2D: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:122)
==14888==    by 0x117610: std::rt::lang_start::{{closure}} (rt.rs:145)
==14888==    by 0x131C0D: std::rt::lang_start_internal (function.rs:280)
==14888==    by 0x1175DF: std::rt::lang_start (rt.rs:144)
==14888== 
==14888== 48 bytes in 1 blocks are still reachable in loss record 5 of 6
==14888==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14888==    by 0x1128AB: alloc::alloc::alloc (alloc.rs:89)
==14888==    by 0x112936: alloc::alloc::Global::alloc_impl (alloc.rs:171)
==14888==    by 0x112B19: <alloc::alloc::Global as core::alloc::Allocator>::allocate (alloc.rs:231)
==14888==    by 0x11280C: alloc::alloc::exchange_malloc (alloc.rs:320)
==14888==    by 0x113563: alloc::sync::Arc<T>::new (boxed.rs:215)
==14888==    by 0x117AEC: std::thread::Builder::spawn_unchecked_ (mod.rs:483)
==14888==    by 0x1177C7: std::thread::Builder::spawn_unchecked (mod.rs:459)
==14888==    by 0x11835B: std::thread::Builder::spawn (mod.rs:391)
==14888==    by 0x111BB4: ctrlc::set_handler (lib.rs:106)
==14888==    by 0x112BE0: leakctrlc::main (main.rs:2)
==14888==    by 0x115CFA: core::ops::function::FnOnce::call_once (function.rs:248)
==14888== 
==14888== 288 bytes in 1 blocks are possibly lost in loss record 6 of 6
==14888==    at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14888==    by 0x40147D9: calloc (rtld-malloc.h:44)
==14888==    by 0x40147D9: allocate_dtv (dl-tls.c:375)
==14888==    by 0x40147D9: _dl_allocate_tls (dl-tls.c:634)
==14888==    by 0x4925834: allocate_stack (allocatestack.c:430)
==14888==    by 0x4925834: pthread_create@@GLIBC_2.34 (pthread_create.c:647)
==14888==    by 0x13ACB1: std::sys::unix::thread::Thread::new (thread.rs:87)
==14888==    by 0x117DFC: std::thread::Builder::spawn_unchecked_ (mod.rs:529)
==14888==    by 0x1177C7: std::thread::Builder::spawn_unchecked (mod.rs:459)
==14888==    by 0x11835B: std::thread::Builder::spawn (mod.rs:391)
==14888==    by 0x111BB4: ctrlc::set_handler (lib.rs:106)
==14888==    by 0x112BE0: leakctrlc::main (main.rs:2)
==14888==    by 0x115CFA: core::ops::function::FnOnce::call_once (function.rs:248)
==14888==    by 0x116F2D: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:122)
==14888==    by 0x117610: std::rt::lang_start::{{closure}} (rt.rs:145)
==14888== 
==14888== LEAK SUMMARY:
==14888==    definitely lost: 0 bytes in 0 blocks
==14888==    indirectly lost: 0 bytes in 0 blocks
==14888==      possibly lost: 288 bytes in 1 blocks
==14888==    still reachable: 143 bytes in 5 blocks
==14888==         suppressed: 0 bytes in 0 blocks
==14888== 
==14888== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Should call user_handler() in interrupt handler?

Looking at the source code, the user handler is called from a separate thread, not from the OS interrupt handler.

As a consequence, setting a flag in the user handler and reading it from elsewhere is inherently racy.

For instance, this cannot be written without race conditions:

static INTERRUPTED_BY_USER: AtomicBool = ATOMIC_BOOL_INIT;

fn main() {
    ctrlc::set_handler(move || {
        println!("Ctrl+C detected");
        INTERRUPTED_BY_USER.store(true, Ordering::Release));
    }
    // …
    if let Err(err) = some_io_call() {
        if err.kind() == io::ErrorKind::Interrupted {
            // problem: at this point, INTERRUPTED_BY_USER may not be set by the ctrlc handler yet
            if INTERRUPTED_BY_USER.load(Ordering::Acquire) {
                println!("User requested interruption");
                quit();
            } else {
                println!("Interrupted by some other signal, retry...");
                retry();
            }
        }   
    }
}

Do you see any problems with calling the user handler from the OS interrupt handler?

EDIT: probably related to #30 by the way.

Weird behavior with examples

I made a new project (cargo new --bin ctrlc_test), added ctrlc to my dependencies, and then copied the contents of simple.rs from this repo into my main.rs. Then I did a cargo build and ran the executable directly. It sits waiting for me to hit ctrl-c, but after I do that it prints "Hello world!" to the terminal until I kill it. This is on linux and using stable 1.3.0.

It seems like maybe once the handler is triggered it just runs forever?

eats CPU during readme example

Found that readme example (possible while loop?) eats CPU intensively >30%.
Windows - 10

Are where any workaround? Because looks likes its the only library for windows.

update nix to 0.23

Nix 0.22.0 suffer from a security issue, and sadly, updating to 0.22.2 is not an option when depending on bitflag (see nix-rust/nix#1548).
Would it be possible to update ctrlc to use nix 0.23 so that project depending on ctrlc and on bitflag don't need to suppress the RUSTSEC warning? As far as I can tell, this is as simple as changing the version in Cargo.toml, there does not appear to be breaking changes in what ctrlc uses from nix.

Add `revert_to_default()` or similar

I see that in 3.1.2 an example was added to demonstrate how killing the process from a CTRL-C handler can be done, however this masks an important distinction, which is that the process that launched our process can tell the difference between exit(0) and termination due to raise(SIGINT), through WTERMSIG() and friends.

Rust itself provides a small snippet to show how to reset the SIGINT signal handler to SIG_DFL, which would then allow a raise(SIGINT) to terminate the rust process in the same way as if ctrlc was not used.

This is particularly useful for "wrapper" executables such as juliaup where it attempts to as transparently as possible launch a child process, and exit in the same way the child process exits. In this scenario, if the child process dies due to a SIGINT, we want the wrapper process to also look like it died due to a SIGINT.

Distinguish between different signals

This is in response to concerns raised by #21.

Currently we only allow the user to register a single handler that gets called for both SIGINT and SIGTERM. There is similar situation on Windows, Windows actually distinguishes between a CTRL-C event and CTRL-BREAK event, but we call the same handler for both. Our API should allow the user distinguish between the different signals and allow them to take different actions on different signals.

There are two different approaches we could take when implementing this, we can allow users to register different handlers per signal or we can pass the signal type as parameter to the handler. I am in favour of last option. Regardless of what option we choose, we need to come with a cross platform Signal Enum. Should we just throw Windows signal types and Unix signal types together in one Enum or should we try to map signal types from one platform to types on the other platform?

Another thing we should consider is that Windows supports events/signals similar to SIGTERM. A handler registered by SetConsoleCtrlHandler(), in addition to being called on CTRL+C and CTRL+BREAK, will also be called for the following events CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT and CTRL_SHUTDOWN_EVENT. These events allow the user to preform clean up before termination, but unlike for CTRL-C events, the process will terminate when the handler routine is done executing regardless of what the return value of handler routine was. If we wanted to, we could try to use this to implement cross platform SIGTERM support, but we would have to work around the termination issue in some way. Edit: These events are probably closer in semantics to SIGHUP then SIGTERM.

There are also other Unix signals which might be worth exposing, like SIGCHLD, SIGCONT, SIGHUP, SIGTSTP, SIGUSR1 and SIGUSR2.

Use in conjunction with console input

Should it be possible to use this library in a package that also reads input from the console? I wrote the following test program that I expected to wait for me to press control c, then prompt me for input, and then echo that input back to me:

extern crate ctrlc;

use std::sync::mpsc;
use std::io;

fn main() {
    println!("Waiting for Ctrl-C...");
    let (tx, rx) = mpsc::channel();
    ctrlc::set_handler(move || tx.send(()).unwrap());
    rx.recv().unwrap();

    println!("Waiting for input...");
    let mut line = String::new();
    println!("{:?}", io::stdin().read_line(&mut line));
    println!("Line: {}", line);
}

On macOS 10.12.1 and rust-ctrlc v2.* (due to #14), the call to read_line fails:

Err(Error { repr: Os { code: 5, message: "Input/output error" } })

On Windows 10, after pressing Ctrl-C, it prints "Waiting for input..." and then immediately exits without printing anything else/any errors. If I add a panic to the last line just to make sure it's not something funky with the standard output, it never makes it there.

Ignore Ctrl-C

It would be nice if we could expose some way to ignore Ctrl-C signals without having to register an empty signal handler. Ignoring Ctrl-C is supported by the native API on both on Unix and Windows, so this shouldn’t be too hard.

There are some possible concerns related to child process inheritance. On Windows, the ignore ctrl-c status is inherited by child processes, which might be a problem. On Unix, the whole signal dispositions is inherited by child processes created via fork(2) on, but not by child processes created via execve(2). Since fork(2) is almost never used in rust, we should be fine here.

Use signal-hook-registry for listening to signals

It is unsound to use this crate and signal-hook in the same dependency graph, since they will both try to acquire the signal handler and one will overwrite the other. This issue can be fixed by changing the signal handling backend to rely on the signal-hook-registry crate.

Simpler example without busy loop but channels

use std::sync::mpsc::channel;
use ctrlc;

fn main() {
    let (interrupt_sender, interrupt_receiver) = channel();
    
    ctrlc::set_handler(move || {
        interrupt_sender.send(()).expect("Could not send signal on channel.")
    }).expect("Error setting Ctrl-C handler");
    
    println!("Waiting for Ctrl-C...");
    interrupt_receiver.recv().expect("Could not receive from channel.");
    println!("Got it! Exiting...");
}

I'm not a rust/threading expert but this is a solution I came accross which is probably helpful for others too? Not sure what are the downsides of this vs the provided example. But to me it feels cleaner/less code and more performant as it doesn't cause a busy loop.

Use sigaction without SA_RESTART instead?

Currently it uses the signal function which translates to sigaction with the SA_RESTART flag. Most blocking syscalls aren't interrupted by it, e.g. reading from a file descriptor will continue to wait for data after the signal. Setting a running flag won't work unless that read returns and the process can check the running flag.

I encountered this when I was reading inotify events. Ctrl-C didn't stop it until the next event arrived. I'm on Arch Linux x86_64 if it matters.

The change won't be backwards compatible, so perhaps add another feature to opt in?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.