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

support ctrl+z stop process #65

Open
jaki opened this issue Sep 29, 2020 · 9 comments
Open

support ctrl+z stop process #65

jaki opened this issue Sep 29, 2020 · 9 comments
Labels
bug Something isn't working

Comments

@jaki
Copy link

jaki commented Sep 29, 2020

In interactive dua i, Ctrl+Z doesn't work. It does nothing. If I stop the process using kill -STOP <pid> on a separate terminal, it does stop. If I fg it on that terminal, it starts again, but the graphical interface is not there. Whatever I type seems to have no affect, and the text I type just shows. It even doesn't recognize Ctrl+C (in most cases). So I kill -KILL <pid>, and all the keys I inputted are spit out on that terminal.

Ideally, I would like Ctrl+Z to stop the process like many other programs.

@Byron Byron added the bug Something isn't working label Sep 29, 2020
@Byron
Copy link
Owner

Byron commented Sep 29, 2020

Thanks for letting me know!

I have had a preliminary look and here are my findings.

  • dua uses tui, which exhibits the behaviour described here both for the termion and crossterm backends. All the following findings have been run with cargo run --example termion_demo -- --tick-rate 200.
  • I understand why the application doesn't respond to Ctrl+Z, which is due to the terminal being in raw mode, thus key presses don't show and are entirely handled by the application. Otherwise interrupts are generated by the owning shell which would see Ctrl+Z before the application does.
  • After stopping the process with kill -STOP, the terminal remains in raw mode. I am able to continue it with kill -CONTor fg in the shell and it does display, even though that will leave the user with control over the terminal to some extend. As such, the application will not respond to any input, even though input will not display in the terminal either as it is still in raw mode. However, now Ctrl+C or Ctrl+Z now work as they are effectively intercepted and translated into signals by the owning shell. I did not find a way to restore input to the application.

Knowing how obtaining input works, I do wonder what else has to happen to make this work. I have a feeling it's something the application has to be aware of, and possibly rebind standard input somehow.

When trying the above with dua, continuing it would not display anything. That is expected, as it only redraws when it receives user input, which we know can't be obtained anymore. However, now one can kill it with Ctrl+C, leaving the terminal in a dirty state as destructors didn't run.

Next I will continue checking what happens when doing this in termion and crossterm respectively.

@Byron
Copy link
Owner

Byron commented Sep 29, 2020

I think I know what happens. After interruption, the input thread crashes and for some reason doesn't bring down the program with it (which it really should at least). Thus it leaves the application unresponsive.

That should easily be fixable, let me see….

@Byron
Copy link
Owner

Byron commented Sep 29, 2020

Even though it's trivial to ignore interrupts, it appears that's not actually happening in case of dua. Previously the input thread would gracefully shutdown, and leave the main loop input loop with a closed channel which should unblock and stop naturally.

Further diving revealed that applications do indeed have to implement support for that themselves

As of now, I am none the wiser though on how to fix that, and the question remains why connectivity to the terminal is lost entirely.

Ok, here it comes: The terminal is loosing it's RAW mode, which is why now one can actually see all input. Thus pressing enter dispatches all input to the application, which will happily draw then as well.

Thus the solution should be to set a handler for SIGCONT and set the terminal back into raw mode.

@Byron
Copy link
Owner

Byron commented Sep 29, 2020

Any thoughts on this would be appreciated - it puzzles me that this is not done automatically. It appears like tig deals with this specifically thanks to using curses as backend.
Putting this to the test, if I run cargo run --example curses_demo --no-default-features --features="curses" -- --tick-rate 200 in the tui-rs repository, stopping it via separate signal actually leaves the terminal in a decent state, and fg brings back the application in raw mode. Thus curses must have been installing enough signal handlers to make that happen.

As dua uses tui, it would be possible to compile it against curses, a backend supported by tui.
It seems like ideally, this kind of functionality would be provided by the termion and crossterm backends respectively, ideally by a callback to separately install certain signal handlers to save and restore terminal attributes at the right time.

I will sleep on that and see if support for that can be implemented in termion or crossterm respectively to match curses capabilities.

@Byron
Copy link
Owner

Byron commented Sep 29, 2020

This article for the first time in my life explains how all this is actually working. Very interesting, even after only having read the first 10% or so.
From what I could gather, the shell as session leader should be the one to restore the previous terminal attributes (which includes raw mode) shortly before it puts an application into the foreground. This, however, doesn't seem to happen, and curses handling this specifically (at least so it seems) might be a good example for this being some sort of special case nonetheless.

@jaki
Copy link
Author

jaki commented Sep 30, 2020

Thanks for all the effort you put into this! That article was helpful for me, too. I read up to

But since the editor is now a background job, the TTY device will not allow it.

then tested it out on vim and got the same result. If I kill -STOP <pid> on vim rather than using Ctrl+Z, I get in a bad state after fg. A minor difference is that resizing the screen (which should send SIGWINCH) causes it to be redrawn, but the text input seems to have no affect to the editor.

Then, I think that kill -STOP <pid> is not something to worry about and, instead, it's about handling Ctrl+Z + fg.

@Byron
Copy link
Owner

Byron commented Oct 1, 2020

I could just reproduce this behaviour with the tui curses backend, too. Curses and Vim probably implement something like this:

  • before changing the terminal after application startup, store the current terminal settings as previous terminal settings.
  • register a signal handler for SIGTSTP
  • upon receiving the signal, store the current terminal settings, set the previous terminal settings, register a signal handler for CONT (probably deregister SIGTSTP) and send your own process the STOP signal.

Now the user should be able to interact with a pristine terminal.

Once CONT is received, the application restores the previously current terminal settings and uninstalls the CONT signal handler and potentially reinstalls the SIGTSTP handler to get ready for the next background request.

If the application wanted to, it could always install a CONT handler to be able to always restore the correct terminal settings, even in case of STOP having been received earlier.

Now is the question how to handle it. My first intuition is to put that into crosstermion at first as PoC and see if that can trickle down into the actual backends, namely crossterm or termion, or possibly it's very own crate as only one cross-platform implementation should ever be needed.

The sig crate could probably be used to send signals to a process using libc, which I suspect lacks windows support. There seem to be plenty of issues it might be more feasible to implement this as no-op on windows at first.

@Byron
Copy link
Owner

Byron commented Oct 1, 2020

Additionally, I have created an issue over at termion, pointing to the corresponding crossterm issue which probably has the higher chances of yielding an outcome.

@pickfire
Copy link

pickfire commented Jun 3, 2021

I want to implement this for helix editor as well https://github.com/helix-editor/helix but seemed like we need to use low-level library for this directly using signal-hook.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants