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

Ctrl-C doesn't send SIGINT to python program launched with debugger #500

Closed
alexwhittemore opened this issue Dec 14, 2020 · 13 comments · Fixed by #539
Closed

Ctrl-C doesn't send SIGINT to python program launched with debugger #500

alexwhittemore opened this issue Dec 14, 2020 · 13 comments · Fixed by #539

Comments

@alexwhittemore
Copy link

Extensive searching leads me to believe this probably isn't a bug, but I still can't figure out how to get the expected behavior in Code. In a nutshell, "Ctrl-C" should raise a KeyboardInterupt in a running python program. When run in a vscode-python terminal with python3 program.py, it works as expected. When launched via debugger, it does not and the program just keeps executing.

Environment data

  • VS Code version: 1.52.0
  • Extension version (available under the Extensions sidebar): v2020.11.371526539
  • OS and version: macOS 10.15.7
  • Python version (& distribution if applicable, e.g. Anaconda): 3.7.9 (from homebrew, I think/hope)
  • Type of virtual environment used (N/A | venv | virtualenv | conda | ...): venv
  • Relevant/affected Python packages and their versions: N/A
  • Relevant/affected Python-related VS Code extensions and their versions: N/A
  • Value of the python.languageServer setting: Jedi

[NOTE: If you suspect that your issue is related to the Microsoft Python Language Server (python.languageServer: 'Microsoft'), please download our new language server Pylance from the VS Code marketplace to see if that fixes your issue]

^ Tried this, no change.

Expected behaviour

When I "Start debugging" with "Python: Current file", "ctrl-c" at terminal, SIGINT is passed to python process and raises a KeyboardException wherever it is currently executing.

Actual behaviour

The above works as expected when a program is run from the terminal with python3 program.py, but when launched with the debugger as above, SIGINT isn't passed to Python.

Steps to reproduce:

  1. Open new folder.
  2. Create file "looptest.py"
  3. Add code:
import time

number = 0
try:
    while True:
        number += 1
        print(number)
        time.sleep(.5)
except KeyboardInterrupt:
    print("Finished with ctrl-c")
  1. Use debugger to "run current file"
  2. Attempt to "ctrl-c" in terminal. Observe output such as the following (ctrl-c input prints as ^C but is otherwise ignored)
1
2
3
^C4
5
^C6
^C7
^C8
  1. Stop debugger.
  2. In terminal, run `python3 looptest.py'
  3. Attempt "ctrl-c"
  4. Observe output below. ctrl-c is caught as a KeyboardException
-> % python3 looptest.py                                                                                                           12
3
4
^CFinished with ctrl-c
@karrtikr karrtikr transferred this issue from microsoft/vscode-python Dec 14, 2020
@fabioz
Copy link
Collaborator

fabioz commented Dec 17, 2020

Just as a note, it does seem to work properly for me on Windows (so, it's probably a Mac-only issue).

@alexwhittemore
Copy link
Author

Just as a note, it does seem to work properly for me on Windows (so, it's probably a Mac-only issue).

Thank you - definitely useful and interesting info. To be clear, just generic ctrl-c copies and pastes outside of a terminal, SIGINTs inside of a terminal, and also SIGINTs in the debugger, yeah? I've chased down a million different key remaps or other key combinations or shortcuts or commands for trying to send a SIGINT to the running process, but none achieve the expected KeyboardInterrupt in my running program, so I can't say whether I'm doing it wrong or whether the debugger catches and kills them all.

@fabioz
Copy link
Collaborator

fabioz commented Dec 17, 2020

One thing to check: if instead of the internalTerminal you use "console": "externalTerminal" in your launch.json, does Ctrl+C work in the external shell?

@alexwhittemore
Copy link
Author

externalTerminal

Mmm Interesting test: No. Ctrl-C does not work in the external terminal. And symmetrically, if I "Run python file in terminal" instead of using the debugger, which does launch python in an integrated terminal, ctrl-c does work as expected. So this sounds like specifically a debugger problem (good call @karrtikr )

@int19h
Copy link
Contributor

int19h commented Jan 5, 2021

It might have something to do with the fact that we're using a separate process group to spawn the debuggee in, so that it can be cleanly terminated with its entire subprocess tree:

if sys.platform != "win32":
def preexec_fn():
try:
# Start the debuggee in a new process group, so that the launcher can
# kill the entire process tree later.
os.setpgrp()
# Make the new process group the foreground group in its session, so
# that it can interact with the terminal. The debuggee will receive
# SIGTTOU when tcsetpgrp() is called, and must ignore it.
old_handler = signal.signal(signal.SIGTTOU, signal.SIG_IGN)
try:
tty = os.open("/dev/tty", os.O_RDWR)
try:
os.tcsetpgrp(tty, os.getpgrp())
finally:
os.close(tty)
finally:
signal.signal(signal.SIGTTOU, old_handler)
except Exception:
# Not an error - /dev/tty doesn't work when there's no terminal.
log.swallow_exception(
"Failed to set up process group", level="info"
)

That said, it should still be receiving signals, since it's the foreground process in the session. This might be something Mac (or BSD) specific.

(On Windows, we use job objects instead, which don't affect terminal interaction.)

@alexwhittemore
Copy link
Author

@int19h Is there a different shortcut/signal I could try sending? like, is there a shortcut to send SIGUSR1 or something? It looks like python has a special case for SIGINT=KeyboardInterrupt, so I'm not sure how valid a test it would be anyway.

@int19h
Copy link
Contributor

int19h commented Jan 5, 2021

There's Ctrl+D for SIGQUIT. It won't translate to a Python exception, but you could use the signal module to register a handler and see if it's getting called.

@alexwhittemore
Copy link
Author

I'll try to put together a test

@osown
Copy link
Contributor

osown commented Jan 28, 2021

I did some digging and noted that the default SIGINT handler is not set when running python programs inside vscode

import signal
print(signal.getsignal(signal.SIGINT))

which is printing "Handlers.SIG_IGN" whereas it should print "<built-in function default_int_handler>"

It's not a proper solution but setting the default handler inside the python script fixes this problem:

import signal
signal.signal(signal.SIGINT, signal.default_int_handler)

@osown
Copy link
Contributor

osown commented Jan 28, 2021

Ok think I've found the real reason:

# Disable exceptions on Ctrl+C - we want to allow the debuggee process to handle
# these, or not, as it sees fit. If the debuggee exits on Ctrl+C, the launcher
# will also exit, so it doesn't need to observe the signal directly.
signal.signal(signal.SIGINT, signal.SIG_IGN)

Removing this line fixes the problem

@alexwhittemore
Copy link
Author

Hey cool! That does look VERY intentional though. I don't fully understand the logic behind the decision as it's written there. What it DOES look like is that it assumes Ctrl-C "always means exit" rather than allowing for the possibility that a process under debug might want to do its own handling.

@int19h
Copy link
Contributor

int19h commented Feb 3, 2021

It's actually the other way around. The problem is that, because the launcher is the process that spawns the actual debuggee, it receives SIGINT first on Ctrl+C. Which causes it to exit, which in turns kills the debuggee - even if the debuggee actually wanted to handle that signal. So this was an attempt to get the launcher out of the picture, such that the debuggee can do its own thing.

I'm not sure why it's not working properly. Note that the launcher is a separate process, and so whatever signal handling tweaks it makes, they do not affect the debuggee - that was the whole point. But I don't think the suggested fix is correct on all platforms. This requires further investigation.

@int19h
Copy link
Contributor

int19h commented Feb 3, 2021

I checked the suggested fix, and it does indeed cause an issue on Windows - the same one that we originally added it for: microsoft/ptvsd#1896

However, the change does work on Linux. On the other hand, the unchanged code works on Windows (i.e. debuggee does receive KeyboardInterrupt).

So, I think this needs to be handled in a platform-specific way. @osown, can you please update the PR to add a check for sys.platform == "win32", instead of just removing the line?

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

Successfully merging a pull request may close this issue.

4 participants