sashahart / vex

Run a command in the named virtualenv.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ctrl-C interrupts vex and shuts down IPython

robbles opened this issue · comments

I noticed this issue when running vex <venv> ipython.

Normally I can use ctrl-c to cancel the current line of input, and doing this on a blank line just starts a new one, without affecting the interpreter.

However, when running inside vex, the interrupt shuts down IPython instantly, as if I'd pressed ctrl-d. My guess is that the parent process is not handling the SIGINT signal, and its shutdown results in the child (ipython) shutting down as well, even though it normally handles interrupts gracefully.

Can this be fixed? It really makes interactive sessions in IPython/vex unusable for me, since I have a habit of cancelling half-constructed lines of code by interrupting them.

Thanks!

When I run 'vex foo bash' I can use Ctrl-C exactly as I normally would, and it does not bring down the bash shell.
Same with 'vex foo zsh'.
Same with 'vex foo fish'.
Same with 'vex foo tcsh'.
Same with 'vex foo mksh'.

vex is process-agnostic. Though both happen to be written in python, the interaction between vex and ipython is out-of-process; to vex, ipython is just any other process. It's ipython which is doing something different from every other shell, vex is doing the same thing in all of these cases. The fact that you see vex in the traceback is a consequence of ipython being run by vex, not a consequence of this being caused by vex.

Did you ever try Ctrl-U? It's one of the readline default bindings, so it applies pretty widely.

I know that it's not caused by vex. That's interesting that bash, etc. don't seem to have this problem though. I agree that ipython is likely doing something that it shouldn't as a well-behaved shell process.

The thing that makes me suspect it's something that CAN be handled in vex somehow is that ipython doesn't normally do this when run normally. If you hit Ctrl-C, it just prints "KeyboardInterrupt" and starts a new line.

Also, since this probably wasn't clear from my first message - I think vex is a fantastic tool, thanks for making it! This is really just a minor annoyance that I believe might be fixable without a patch to ipython.

Sure, no problem. I just never know what people understand about errors they are looking at.

This may overlap with what you just said but the python interactive interpreter itself does the same thing as ipython (normally: Ctrl-C gives KeyboardInterrupt, but under vex python interpreter is interrupted by Ctrl-C). I sort of forgive it more easily because it's crude in many other ways, though. But probably same root cause.

I really don't want to try to fix a problem with ipython, and in the process interfere with people's ability to abort processes they ran under vex using Ctrl-C. signal handling has or at least deserves a reputation for being subtle and hard to test. I want to keep it simple if possible.

Did you try Ctrl-U though? It might be cleaner than overloading Ctrl-C to mean 'erase line', for many purposes and not just vex's.

I noticed that if I add the following code to the main entry point for vex, it seems to fix the problem:

import signal
def handle_int(num, frame):
    print 'RECEIVED INTERRUPT IN VEX'
signal.signal(signal.SIGINT, handle_int)

This makes me think that the child process is passing along the interrupt signal to the parent process. I assume that bash, zsh, etc. don't have this problem because they both handle the interrupt and prevent it from being passed up the chain, whereas ipython only handles the interrupt.

I don't know enough about signal handling to say whether this is something you can disable when starting the child process, or if you would need to make a signal handler.

Just saw your reply. yeah, Ctrl-U is definitely helpful, thanks for the tip.

There is another possible reason to install a signal handler or something, which is to get rid of the traceback. Cosmetic but also I can see that generating confused tickets. However, I'm going to take some time to check the implications of this for things other than ipython.

Sounds good, thanks for looking into it.

From what I can see, it shouldn't hurt the ability to interrupt processes that don't handle that internally. vex foo sleep 100 still exits when I interrupt it.

Sure, your SIGINT is going to the entire foreground process group, ideally/reasonably the subprocess sees it, and then vex's call to .wait() returns and vex goes down in an orderly fashion. So it seems okay to casual inspection.

However, if you send a SIGINT to a vex process itself, it is obviously ignoring the signal and does not go down any more, because that's what we said to do. And if the child has ignored SIGINT, it won't go down. But we only expect SIGTERM from outside the foreground process group, right? Maybe. It's the child process's job to decide whether to go down from SIGINT, so if it says no, we shouldn't go down, right? So why doesn't every program with subprocesses ignore SIGINT, why isn't the default to ignore it? And since it's not the default, what else is making the assumption that it should be? Because I'm not just running a particular process that I can test and say done, I'm allowing people to run anything they want inside vex.

The problem is that programs vary in what they do with signals, and there are interactions, and then let's add in asynchronous/re-entrant problems, and then let's add in cross-platform which is guaranteed to be a headache. Testing becomes a heroic enterprise. And now I can't use this as a library easily because who knows what other handlers are installed, either I clobber someone else's or they clobber mine and either way something isn't behaving correctly.

So I sympathize if you don't want to rewire your fingers to Ctrl-U, but I'm still going to let this cook for a little while.

a draft of this is on branch ignore_SIGINT, 7677c1e