kkawakam / rustyline

Readline Implementation in Rust
https://crates.io/crates/rustyline/
MIT License
1.56k stars 178 forks source link

Signal handling / listening #804

Open eldoccc opened 2 months ago

eldoccc commented 2 months ago

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 :

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

thanks !

gwenn commented 2 months ago

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 commented 2 months ago

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 commented 1 month ago

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 commented 1 week ago

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)