Skip to content
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

Signal handling / listening #804

Open
eldoccc opened this issue Sep 10, 2024 · 5 comments
Open

Signal handling / listening #804

eldoccc opened this issue Sep 10, 2024 · 5 comments

Comments

@eldoccc
Copy link

eldoccc commented Sep 10, 2024

Hi, I was wondering if the rustyline team was interested for help concerning signal handling ?
Currently, rustlyline only listens to SIGWINCH and it would be nice to have at least SIGINT, (that would solve #780).

I was thinking in doing it in similar way to linefeed where you specify a list of signals you'd want to listen (my main personnal need would be for SIGINT).

If so, would you be kind to indicate which approach would fit your philosophy better :

  • create a Signal enum that represents every signals unix & windows
  • have two different `listen_to_signal(sig)' functions, one for unix, one for windows

ATM, I've done quick modifications just to listen to another signal of your choice here.

thanks !

@gwenn
Copy link
Collaborator

gwenn commented Sep 10, 2024

Did you try:
https://docs.rs/rustyline/latest/rustyline/config/struct.Config.html#method.enable_signals
or this:
https://man7.org/linux/man-pages/man3/termios.3.html

c_iflag flag constants:

  IGNBRK Ignore BREAK condition on input.

  BRKINT If IGNBRK is set, a BREAK is ignored.  If it is not set
         but BRKINT is set, then a BREAK causes the input and
         output queues to be flushed, and if the terminal is the
         controlling terminal of a foreground process group, it
         will cause a SIGINT to be sent to this foreground process
         group.  When neither IGNBRK nor BRKINT are set, a BREAK
         reads as a null byte ('\0')

?
https://github.com/search?q=repo%3Akkawakam%2Frustyline%20BRKINT&type=code

@eldoccc
Copy link
Author

eldoccc commented Sep 11, 2024

isn't this for recognizing signal input on stdin ? I might've explained myself poorly.

Currently, rustyline never yields, and remains ''stuck'' on waiting for input, what I'd like rustyline to do, is to listen for signals coming from another thread / task, something as simple as nix::sys::signal::kill(nix::unistd::getpid(), nix::sys::signal::SIGINT).

Yes, calling a SIGINT already closes the process, but it's brutal and it'd be better if rustyline could exit its loop properly.

with that in mind, this idea could be expanded to other signals.

@gwenn
Copy link
Collaborator

gwenn commented Oct 6, 2024

Ok, ISIG:

use std::thread;
use std::time::Duration;

use rustyline::{Config, DefaultEditor, Result};

fn main() -> Result<()> {
    env_logger::init();
    let config = Config::builder().enable_signals(true).build();
    let mut rl = DefaultEditor::with_config(config)?;

    thread::spawn(|| {
        thread::sleep(Duration::from_secs(5));
        unsafe { libc::raise(libc::SIGINT) }
    });

    loop {
        let result = rl.readline("> ");
        match result {
            Ok(line) => {
                println!("Line: {line}");
            }
            Err(err) => {
                println!("Err: {err:?}");
                break Err(err);
            }
        }
    }
}
rustyline % RUST_LOG=rustyline=debug cargo run --example signal

doesn't work.
https://stackoverflow.com/a/75704077/21836177

Finally, it may be worth mentioning that this only disables the special characters used to generate the corresponding signals, the signals can still be sent using the kill function.

So enable_signals seems useless (at least in your case).

And while trying to rewrite this code in Rust:
https://github.com/daanx/isocline/blob/c9310ae58941559d761fe5d2dd2713d245f18da6/src/tty.c#L581-L626
It appears that with signal_hook, if you use the same pipe for different signals, you cannot distinguish which signal is received:

use std::io::{Error, Read};
use std::os::unix::net::UnixStream;

use signal_hook::consts::{SIGUSR1, SIGUSR2};
use signal_hook::low_level::{pipe, raise};

fn main() -> Result<(), Error> {
    let (mut read, write) = UnixStream::pair()?;
    pipe::register(SIGUSR1, write.try_clone()?)?;
    pipe::register(SIGUSR2, write)?;
    let signal = SIGUSR2;
    raise(signal).unwrap();
    let mut buff = [0];
    read.read_exact(&mut buff)?;
    println!("buff: {buff:?} / {signal}");
    Ok(())
}

https://docs.rs/signal-hook/latest/signal_hook/low_level/pipe/index.html

the signal handler writes one byte of garbage data to the write end

@gwenn
Copy link
Collaborator

gwenn commented Nov 2, 2024

Current implementation of ExternalPrinter in rustyline supports only sending messages / text.
But replxx supports also sending keys.
Would you be ok emulating a Ctrl-C instead of a SIGINT ?
(or you could still listen on SIGINT on your side and send a Ctrl-C to rustyline)

@eldoccc
Copy link
Author

eldoccc commented Nov 19, 2024

I mean yeah, if it feels more in tune with rustyline philosophy.

Should the dev explicitly use an emulate method from the public api or should sending a "Ctrl-C" &str be recognized as actual Ctrl-C?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants