Theia: Python VS extension: debug fails when Debug console is already opened

Created on 12 Feb 2020  路  11Comments  路  Source: eclipse-theia/theia

Description

I found an issue when debugging in Theia v0.15.0 with Python VS extension. After version 2019.9.34911 there is an error when starting a debug session with the "Python Debug Console" already open.

Reproduction Steps

Python VS extension : 2020.1.58038

OS and Theia version: Ubuntu Disco and Theia v0.15.0. Installation with this Dockerfile. Using the following package.json

Diagnostics:
In the Python Debug console:

~/workspace# /usr/bin/python /tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/launcher /root/workspace/example.py
Traceback (most recent call last):
File "/usr/lib/python3.7/runpy.py", line 193, in _run_module_as_main
 "__main__", mod_spec)
File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
 exec(code, run_globals)
File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/launcher/__main__.py", line 80, in <module>
 main()
File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/launcher/__main__.py", line 42, in main
 launcher.connect(launcher_port)
File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/launcher/../../ptvsd/launcher/__init__.py", line 27, in connect
 sock.connect(("127.0.0.1", launcher_port))
ConnectionRefusedError: [Errno 111] Connection refused

In the backend:

root ERROR [hosted-plugin: 111] E+00010.052: /handling #2 request "launch" from IDE[1]/
          Handler 'IDE._start_message_handler.<locals>.handle' (file '/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/adapter/components.py', line 160)
          couldn't handle #2 request "launch" from IDE[1]:
          Traceback (most recent call last):
            File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/common/messaging.py", line 766, in _handle
              result = handler(self)
            File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/adapter/components.py", line 95, in lock_and_handle
              return f(self, message)
            File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/adapter/ide.py", line 176, in handle
              f(self, request)
            File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/adapter/ide.py", line 278, in launch_request
              self.session, request, sudo, args, console, console_title
            File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/adapter/launchers.py", line 115, in spawn_debuggee
              spawn_launcher()
            File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/adapter/launchers.py", line 98, in spawn_launcher
              "env": env,
            File "/usr/lib/python3.7/contextlib.py", line 119, in __exit__
              next(self.gen)
            File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/adapter/sessions.py", line 163, in _accept_connection_from
              sock, (other_host, other_port) = listener.accept()
            File "/usr/lib/python3.7/socket.py", line 212, in accept
              fd, addr = self._accept()
          socket.timeout: timed out
          Stack where logged:
            File "/usr/lib/python3.7/threading.py", line 885, in _bootstrap
              self._bootstrap_inner()
            File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
              self.run()
            File "/usr/lib/python3.7/threading.py", line 865, in run
              self._target(*self._args, **self._kwargs)
            File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/common/messaging.py", line 1523, in _run_handlers
              handler()
            File "/tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/adapter/../../ptvsd/common/messaging.py", line 812, in _handle
              self.describe(),
bug help wanted python vscode

Most helpful comment

@a-kanaan hm, not sure how to elaborate more, but here it is in other words:

When a debug adapter sends a runInTerminal request, Theia spawns a shell the first time, and then reuses said shell for next runInTerminal requests. This is what VS Code does as well.

In this case, it seems like some use-cases require changing the environment variables between different runInTerminal calls. Issue is: the shell process being re-used, we cannot reset environment variables easily. We usually set the environment when spawning a process, but like I said we don't respawn processes.

The solution is to craft shell commands that will actually contain several commands: some to modify the environment directly inside the running shell, followed by the actual command that runInTerminal wanted to run with the new environment variables.

Doing all of this requires a somewhat robust command-to-shell convertion mechanism, with escaping and all that. I am currently working on this PR, and am down to the last issues with it. My biggest pain by far is to accommodate for Windows with cmd.exe + the weird node-pty mechanism. But I'm getting there.

Hope this helps.

All 11 comments

I ran into the same issue and did some investigation. It is basically caused by the fact that an already existing terminal does not get the port for the debug session. In doCreateTerminal a new terminal gets all the options as opposed to an already existing terminal. In this case a Python Debug Console does not know anything about the updated port and the connection gets refused.

From the Python Debug console we could see that the launcher is started like this:

/usr/bin/python /tmp/vscode-unpacked/ms-python.python-latest.vsix/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/launcher

What we cannot see here is that the environment variable _PTVSD_LAUNCHER_PORT_ is used by the launcher to know which port to connect to. A quick and quite specific workaround is to add the environment variable _PTVSD_LAUNCHER_PORT_ to the following line in debug-session.tsx. Just replace:

terminal.sendText(args.join(' ') + '\n');

by:

terminal.sendText('PTVSD_LAUNCHER_PORT=' + env['PTVSD_LAUNCHER_PORT'] + ' ' + args.join(' ') + '\n');

This workaround just makes it work for the Python VS extension, but because _debug-session.tsx_ is a general location, a general solution should be used. Therefore, I have a few questions regarding how to best solve this issue:

  • Should the behaviour stay the same (i.e. reuse existing terminal if one exists and create a new terminal otherwise)?
  • Should it be possible to change a terminal's (process) environment if the terminal already exists? (I digged a bit through related interfaces / implementations and what I saw does not look like updating options or environment variables is supported, so it would probably need a change at several locations)
  • Should just the environment variables be updated or just relevant options or all options just like when a new terminal is created?

I thought that env variables are inherited by processes. If not then it is a bug. cc @marechal-p

@tolusha @tsmaeder Is it correct that PTVSD_LAUNCHER_PORT should be inherited by the plugin host process then by the debug adapter process and then by the program process?

@akosyakov env variables are inherited by sub-processes. The issue is that we reuse shells (like VS Code) to run commands sent by debug adapters via runInTerminal requests. This means that a single shell process is spawned, with some env vars, and then never gets updated. We just keep sending commands to it.

The following piece of code will fix this issue, since it encodes the new env into the command that is sent to the everlasting shell: https://github.com/eclipse-theia/theia/pull/6836/files#diff-6121f93c6c9a828c7d6ba245fe1354feR88-R98

@akosyakov env variables are inherited by sub-processes. The issue is that we reuse shells (like VS Code) to run commands sent by debug adapters via runInTerminal requests. This means that a single shell process is spawned, with some env vars, and then never gets updated. We just keep sending commands to it.

The following piece of code will fix this issue, since it encodes the new env into the command that is sent to the everlasting shell: https://github.com/eclipse-theia/theia/pull/6836/files#diff-6121f93c6c9a828c7d6ba245fe1354feR88-R98

can you elaborate more please what the steps are
thank you

@a-kanaan hm, not sure how to elaborate more, but here it is in other words:

When a debug adapter sends a runInTerminal request, Theia spawns a shell the first time, and then reuses said shell for next runInTerminal requests. This is what VS Code does as well.

In this case, it seems like some use-cases require changing the environment variables between different runInTerminal calls. Issue is: the shell process being re-used, we cannot reset environment variables easily. We usually set the environment when spawning a process, but like I said we don't respawn processes.

The solution is to craft shell commands that will actually contain several commands: some to modify the environment directly inside the running shell, followed by the actual command that runInTerminal wanted to run with the new environment variables.

Doing all of this requires a somewhat robust command-to-shell convertion mechanism, with escaping and all that. I am currently working on this PR, and am down to the last issues with it. My biggest pain by far is to accommodate for Windows with cmd.exe + the weird node-pty mechanism. But I'm getting there.

Hope this helps.

This is still not working out of the box.
Create an empty workspace, add a simple python file (print("hello world!) will do), try to debug using the default configuration "Python: Current file" which will create a launch.json [1] for you.
It won't work and exit with [2].

Setting the "console" paramter in the launch.json to "integratedConsole" makes it work, of course you have to enable the "Debug Console" ("View" --> "Debug Console") then to see anything.

[1]:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Current File",
      "type": "python",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal"
    }
  ]
}

[2]:

Traceback (most recent call last):
  File "/home/gitpod/.pyenv/versions/3.7.7/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/home/gitpod/.pyenv/versions/3.7.7/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/tmp/vscode-extensions/[email protected]/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/launcher/__main__.py", line 74, in <module>
    main()
  File "/tmp/vscode-extensions/[email protected]/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/launcher/__main__.py", line 36, in main
    adapter.connect(session_id, launcher_port)
  File "/tmp/vscode-extensions/[email protected]/extension/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/launcher/../../ptvsd/launcher/adapter.py", line 27, in connect
    sock.connect(("127.0.0.1", launcher_port))
ConnectionRefusedError: [Errno 111] Connection refused

It seems that the pull request shown above has not been merged yet.

@denisvasilik the PR was merged.

@ptrxyz What is your platform? And did you try building and running the example applications from the master branch?

While trying to reproduce, I just found an issue with Windows: it seems like shells on Windows expect \r to "submit" commands while we currently send \n.

@marechal-p and @ptrxyz You're right sorry for that. I used the tag v1.0.0 where the pull request has not been integrated.

@marechal-p Would it be fine to assign this issue to you?

@denisvasilik the PR was merged.

@ptrxyz What is your platform? And did you try building and running the example applications from the master branch?

While trying to reproduce, I just found an issue with Windows: it seems like shells on Windows expect \r to "submit" commands while we currently send \n.

I was running Theia on Gitpod. So probably it was Linux ... no idea about the version though.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kittaakos picture kittaakos  路  3Comments

pwFoo picture pwFoo  路  3Comments

vince-fugnitto picture vince-fugnitto  路  3Comments

dhananjayharel picture dhananjayharel  路  3Comments

akosyakov picture akosyakov  路  3Comments