microsoft / debugpy

An implementation of the Debug Adapter Protocol for Python
https://pypi.org/project/debugpy/
Other
1.76k stars 128 forks source link

Ctrl+C causes KeyboardInterrupt inside pydevd #171

Open jdwestwood opened 4 years ago

jdwestwood commented 4 years ago

Environment data

Expected behaviour

Set up and run task "test (debug)" (see below). Launch "Python: Remote Attach" (see below). Remote debugger attaches to the "test (debug)" task.

Actual behaviour

Remote debugger hangs while waiting for IDE to attach. Under Python 2.7.15, a VSCode error window pops up with "Invalid message: Session is not initialized yet". Integrated terminal shows traceback and additional error messages (see below). With python.pythonPath set to Python 3.7.1, the debugger hangs, but the VSCode error window does not pop up, and the integrated terminal does not show any traceback or error output.

Behavior is new under Python 2.7.15 with the 2019.10.41019 release. Suspect also new under Python 3.7.1, but not certain.

Steps to reproduce:

Create a workspace folder with:

  1. settings.json:
    {
    // Python 2.7.15
    "python.pythonPath": "C:\\Python27\\python.exe"
    }
  2. launch.json:
    {
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Remote Attach",
            "type": "python",
            "request": "attach",
            "port": 5678,
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "${workspaceFolder}"
                }
            ]
        }
    ]
    }
  3. tasks.json:
    {   "version": "2.0.0",
    "tasks": [
        {
            "label": "test (debug)",
            "type": "shell",
            "command": "${config:python.pythonPath}",
            "args": ["${workspaceFolder}\\test.py"],
            "options": {
                "env": {"PYTHONPATH": "C:\\Users\\John\\.vscode\\extensions\\ms-python.python-2019.10.41019\\pythonFiles\\lib\\python"}
            },
            "problemMatcher": []
        }
    ]
    }
  4. test.py:
    import ptvsd
    ptvsd.enable_attach(address=('localhost', 5678))
    ptvsd.wait_for_attach()
    hello = "Hello"
    print(hello)

    Output from task in integrated terminal:

    
    > Executing task: C:\Python27\python.exe 'C:\Users\John\VSCode\Python remote debugging\test.py' <

E+00005.483: [handling microsoft/vscode-python#2 request "attach" from IDE-1] Traceback (most recent call last): File "C:\Users\John.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\common\messaging.py", line 1091, in init raise self InvalidMessageError: Invalid message: Session is not initialized yet

         Stack where logged:
           File "C:\Python27\lib\threading.py", line 774, in __bootstrap     
             self.__bootstrap_inner()
           File "C:\Python27\lib\threading.py", line 801, in __bootstrap_inner
             self.run()
           File "C:\Python27\lib\threading.py", line 754, in run
             self.__target(*self.__args, **self.__kwargs)
           File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\common\messaging.py", line 1528, in _run_handlers
             handler()
           File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\common\messaging.py", line 776, in _handle
             result = handler(self)
           File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\adapter\ide.py", line 138, in handle
             raise request.isnt_valid("Session is not initialized yet")
           File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\common\messaging.py", line 577, in isnt_valid
             return args[0].error(InvalidMessageError, *args[1:], **kwargs)
           File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\common\messaging.py", line 565, in error
             exc = exc_type(reason, cause, silent)  # will log it
           File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\common\messaging.py", line 1093, in __init__
             log.exception()

E+00005.660: [handling microsoft/vscode-python#2 request "attach" from IDE-1] Handler 'handle' (file u'C:\Users\John\.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\adapter\ide.py', line 135) couldn't handle microsoft/vscode-python#2 request "attach" from IDE-1: Invalid message: Session is not initialized yet

W+00010.698: [handling microsoft/vscode-python#3 request "disconnect" from IDE-1] Session-1 timed out waiting for Server-1 to disconnect.

W+00010.713: [handling microsoft/vscode-python#3 request "disconnect" from IDE-1] Channel was closed before the response from handler 'disconnect_request' (file u'C:\Users\John\.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\adapter\components.py', line 75) to microsoft/vscode-python#3 request "disconnect" from IDE-1 could be sent


<!--
Note: If you think a GIF of what is happening would be helpful, consider tools like https://www.cockos.com/licecap/, https://github.com/phw/peek or https://www.screentogif.com/ .
-->

## Logs
Output for `Python` in the `Output` panel (pylint ouput omitted):

User belongs to experiment group 'ShowPlayIcon - start' User belongs to experiment group 'ShowExtensionSurveyPrompt - control' User belongs to experiment group 'DebugAdapterFactory - experiment' User belongs to experiment group 'PtvsdWheels37 - experiment' Starting Microsoft Python language server.

conda --version pyenv root python3.7 -c "import sys;print(sys.executable)" python3.6 -c "import sys;print(sys.executable)" python3 -c "import sys;print(sys.executable)" python -c "import sys;print(sys.executable)" python2 -c "import sys;print(sys.executable)" py -3.7 -c "import sys;print(sys.executable)" py -2 -c "import sys;print(sys.executable)" py -3.6 -c "import sys;print(sys.executable)" py -3 -c "import sys;print(sys.executable)" C:\Python27\python.exe -c "import sys;print(sys.executable)" conda info --json C:\PythonEnvs\GCF Excel\Scripts\python.exe c:\Users\John.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\interpreterInfo.py C:\PythonEnvs\GCF Excel\Scripts\python.exe c:\Users\John.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\interpreterInfo.py ~\AppData\Local\Microsoft\WindowsApps\python.exe c:\Users\John.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\interpreterInfo.py ~\AppData\Local\Microsoft\WindowsApps\python.exe c:\Users\John.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\interpreterInfo.py ~\AppData\Local\Microsoft\WindowsApps\python3.exe c:\Users\John.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\interpreterInfo.py ~\AppData\Local\Microsoft\WindowsApps\python3.exe c:\Users\John.vscode\extensions\ms-python.python-2019.10.41019\pythonFiles\interpreterInfo.py

karthiknadig commented 4 years ago

This is likely because of microsoft/vscode-python#7836. See if this workaround helps you https://github.com/microsoft/vscode-python/issues/7836#issuecomment-540350986

jdwestwood commented 4 years ago

I had already added the requirements.txt file as specified in microsoft/vscode-python#7836. Local debugging works. Remote debugging does not.

kirtangajjar commented 4 years ago

Can confirm, having same issue.

karthiknadig commented 4 years ago

@jdwestwood Can you try the following:

Option 1:

Looking at the call-stack it looks like you are using a newer version of the debugger in when you import it. But extension is still attaching with the assumption that it is the old debugger. To use the newer debugger change the debug config to this: NOTE: this is a temporary solution. you should not need it when we fully transition to the new debugger.

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Remote Attach",
            "type": "python",
            "request": "attach",
            "port": 5678,
            "debugServer": 5678,
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "${workspaceFolder}"
                }
            ]
        }
    ]
}

Option 2:

In case option 1 does not work for you. Change the python path in your task to point to old_ptvsd.

{   "version": "2.0.0",
    "tasks": [
        {
            "label": "test (debug)",
            "type": "shell",
            "command": "${config:python.pythonPath}",
            "args": ["${workspaceFolder}\\test.py"],
            "options": {
                "env": {"PYTHONPATH": "C:\\Users\\John\\.vscode\\extensions\\ms-python.python-2019.10.41019\\pythonFiles\\lib\\python\\old_ptvsd"}
            },
            "problemMatcher": []
        }
    ]
}

and use the the original debug config (i.e., without debugServer field):

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Remote Attach",
            "type": "python",
            "request": "attach",
            "port": 5678,
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "${workspaceFolder}"
                }
            ]
        }
    ]
}
kirtangajjar commented 4 years ago

@karthiknadig I tried setting "debugServer": 5678, in my config and it worked for a local connection, but when the code is running in docker container and it's port are mapped to host, it doesn't work. The debugger isn't even able to connect to the ptvsd server.

If I open vscode in container with remote:containers extension, then I am able to get debugger working.

karthiknadig commented 4 years ago

@kirtangajjar The debugServer workaround only works with localhost. On your docker container what is the version of ptvsd that you are using? Did you install it from pip?

kirtangajjar commented 4 years ago

On your docker container what is the version of ptvsd that you are using?

v5.0.0a7

Did you install it from pip?

Yes

The debugServer workaround only works with localhost

I have bound the port of container on host and I'm trying to connect on 0.0.0.0, and still it's not working. Is it expected?

karthiknadig commented 4 years ago

@kirtangajjar Can you share the debug config you are using from the launch.json? Any particular reason you are using pre-release (alpha) version of the debugger?

jdwestwood commented 4 years ago

@karthiknadig The debugServer workaround is working for me with localhost. Thank you!

One detail perhaps worth documenting is that after launching the remote debugger using the workaround, if I then terminate the task using Ctrl-C in the terminal window, an unhandled exception occurs:

Traceback (most recent call last):
  File "C:\Users\John\VSCode_Bugs\Python remote debugging\test.py", line 9, in <module>
    ptvsd.wait_for_attach()
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\__init__.py", line 45, in wait_for_attach  
    return api.wait_for_attach()
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\server\api.py", line 35, in wait_for_attach
    pydevd._wait_for_attach(cancel=cancel_event)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\pydevd.py", line 2286, in 
_wait_for_attach
    if py_db.block_until_configuration_done(0.1):
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\pydevd.py", line 635, in block_until_configuration_done
    return self._on_configuration_done_event.wait(timeout)
  File "C:\Python27\lib\threading.py", line 614, in wait
    self.__cond.wait(timeout)
  File "C:\Python27\lib\threading.py", line 359, in wait
    _sleep(delay)
KeyboardInterrupt
The terminal process terminated with exit code: 1

The exception occurs whether or not the debugger has been attached to the process or a breakpoint has been set.

int19h commented 4 years ago

@jdwestwood This is the standard Python behavior for Ctrl+C. Since this code is running inside the process being debugged, on the main thread, we generally try to not interfere with stuff like that, to allow the app itself to handle KeyboardInterrupt however it sees fit (and, in particular, suppress exit).

That said, the stack above is while it was waiting in wait_for_attach(). What does it look like if you do it after debugger is attached?

jdwestwood commented 4 years ago

@int19h OK, that makes sense. Here is the trace after setting a breakpoint and attaching the debugger, the debugger hits the breakpoint. Terminate the task with Ctrl-C:

Traceback (most recent call last):
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\_pydevd_bundle\pydevd_frame.py", line 606, in trace_dispatch
    self.do_wait_suspend(thread, frame, event, arg)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\_pydevd_bundle\pydevd_frame.py", line 97, in do_wait_suspend
    self._args[0].do_wait_suspend(*args, **kwargs)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\pydevd.py", line 1725, in do_wait_suspend
    keep_suspended = self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\pydevd.py", line 1756, in _do_wait_suspend
    time.sleep(0.01)
KeyboardInterrupt
Traceback (most recent call last):
  File "C:\Users\John\VSCode_Bugs\Python remote debugging\test.py", line 4, in <module>
    hello = "Hello"
  File "C:\Users\John\VSCode_Bugs\Python remote debugging\test.py", line 4, in <module>
    hello = "Hello"
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\_pydevd_bundle\pydevd_trace_dispatch_regular.py", line 202, in trace_dispatch
    return thread_trace_func(frame, event, arg)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\_pydevd_bundle\pydevd_trace_dispatch_regular.py", line 280, in trace_dispatch_and_unhandled_exceptions
    self._frame_trace_dispatch = frame_trace_dispatch(frame, event, arg)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\_pydevd_bundle\pydevd_trace_dispatch_regular.py", line 486, in __call__
    ).trace_dispatch(frame, event, arg)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\_pydevd_bundle\pydevd_frame.py", line 606, in trace_dispatch
    self.do_wait_suspend(thread, frame, event, arg)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\_pydevd_bundle\pydevd_frame.py", line 97, in do_wait_suspend
    self._args[0].do_wait_suspend(*args, **kwargs)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\pydevd.py", line 1725, in do_wait_suspend
    keep_suspended = self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker)
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\_vendored\pydevd\pydevd.py", line 1756, in _do_wait_suspend
    time.sleep(0.01)
KeyboardInterrupt
Traceback (most recent call last):
  File "C:\Python27\lib\runpy.py", line 174, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "C:\Python27\lib\runpy.py", line 72, in _run_code
    exec code in run_globals
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\adapter\__main__.py", line 119, in <module>
    main(_parse_argv(sys.argv))
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\adapter\__main__.py", line 41, in main
    session.wait_for_completion()
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\adapter\session.py", line 85, in wait_for_completion
    self.ide.channel.wait()
  File "C:\Users\John\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\ptvsd\adapter/../../ptvsd\common\messaging.py", line 1239, in wait
    parser_thread.join()
  File "C:\Python27\lib\threading.py", line 940, in join
    self.__block.wait()
  File "C:\Python27\lib\threading.py", line 340, in wait
    waiter.acquire()
KeyboardInterrupt
The terminal process terminated with exit code: 1
int19h commented 4 years ago

Thank you! So there are three things receiving the interrupt signal here.

The one in the middle is where it is paused on the breakpoint in your code. I'm not sure what we can do about that one... what would your expectation be here? Consider that if you did something like input() in your script at the same line as the breakpoint, and then pressed Ctrl+C on that, you'd receive the same exception.

The last stack trace is for the debug adapter, which is a separate process, but in this case it gets spawned by the process being debugged - and so it's attached to the same console. Thus, it receives the signal, and exits. For this one, I think we need to suppress it, because the adapter shouldn't exit until the debug session is over. This is similar to microsoft/ptvsd#1896.

The first stack trace I'm not sure about. It looks like a background thread, but those shouldn't be receiving KeyboardInterrupt - it's supposed to go to the main thread. This one needs more investigation.

jdwestwood commented 4 years ago

Yes, I think the second one is OK. And also glad to learn the last one is already identified in microsoft/ptvsd#1896.

kerbrose commented 4 years ago

Option 1:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Remote Attach",
            "type": "python",
            "request": "attach",
            "port": 5678,
            "debugServer": 5678,    # I just added this line
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "${workspaceFolder}"
                }
            ]
        }
    ]
}

option microsoft/ptvsd#1 worked for me in remote debugging of a docker image

fabioz commented 4 years ago

@int19h I'm not sure if something needs to be done on pydevd here... Ctrl+c seems to be raising an exception and as a result from that the debugger left the breakpoint and raised the exception to the caller (this handling seems to be correct for me). Did you have a different expectation here?

int19h commented 4 years ago

@fabioz It's about the first of the three stacks. Note how there's no user frames in there - it's from some background helper thread. The second stack is the one where it's propagated to user code, and that one is correct. The first one, I don't know about, but it's suspicious - suppose user code would have caught KeyboardInterrupt on the main thread in this case; will the debugger still work correctly without the other thread?

int19h commented 4 years ago

Per https://docs.python.org/3/library/_thread.html:

Threads interact strangely with interrupts: the KeyboardInterrupt exception will be received by an arbitrary thread. (When the signal module is available, interrupts always go to the main thread.)

However, it's not clear whether we can do anything about this. What we really need here is the ability to remove our threads from consideration for SIGINT, without affecting any user threads.