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 gevent with frame eval mode #189

Closed
dalmago opened this issue Jul 30, 2019 · 12 comments
Closed

Support gevent with frame eval mode #189

dalmago opened this issue Jul 30, 2019 · 12 comments
Labels
enhancement New feature or request

Comments

@dalmago
Copy link

dalmago commented Jul 30, 2019

Environment data

  • PTVSD version: 4.3.0
  • OS and version: Windows 7 Pro
  • Python version: 3.6.1
  • Using VS Code

Actual behavior

The code unexpectedly exits when I set the GEVENT_SUPPORT environment variable to True. Getting the following error:

Unhandled exception in thread started by SystemError: ..\Objects\codeobject.c:851: bad argument to internal function

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "_pydevd_frame_eval\pydevd_frame_evaluator.pyx", line 187, in _pydevd_frame_eval.pydevd_frame_evaluator.get_func_code_info
Fatal Python error: GC object already tracked

...

Thread 0x000031b8 (most recent call first):
  File "C:\Python 3.6\lib\threading.py", line 295 in wait
  File "C:\Python 3.6\lib\threading.py", line 551 in wait
  File "C:\Python 3.6\lib\site-packages\ptvsd\attach_server.py", line 42 in wait_for_attach
  File "mytest_ptvsd.py", line 12 in myfunc
  File "mytest_ptvsd.py", line 21 in <module>

The error happens when I press the "Start Debugging" button on VS Code.
Enabling gevent support because I want to debug some code that uses socketio.

Expected behavior

This used to work fine with PTVSD 4.2.10.

Steps to reproduce:

import os

os.environ["GEVENT_SUPPORT"] = 'True'

import ptvsd

DEBUG_PORT = 5678

def myfunc():
    ptvsd.enable_attach(('localhost', DEBUG_PORT))
    print("Please attach debugger to port ", DEBUG_PORT)
    ptvsd.wait_for_attach()  # blocks execution until debugger is attached

    print("Debugger attached")

    print("testing 1 2 3")
    x = 0
    print(x)

if __name__ == "__main__":
    myfunc()
@dalmago dalmago changed the title GEVENT_SUPPORT generating exception Error when using GEVENT_SUPPORT Jul 30, 2019
@karthiknadig
Copy link
Member

Try this workaround and see if it helps:

os.environ["PYDEVD_USE_FRAME_EVAL"] = "NO"

@dalmago
Copy link
Author

dalmago commented Jul 30, 2019

Try this workaround and see if it helps:

os.environ["PYDEVD_USE_FRAME_EVAL"] = "NO"

It does the trick! What is the impact of having both environment variables always set (besides the overhead of importing the libraries again to protect them from patching by external libraries)?

@karthiknadig
Copy link
Member

@fabioz can answer that better. He is currently on vacation and will be back in a couple of days.

@dalmago
Copy link
Author

dalmago commented Aug 3, 2019

It seems to work fine on:

  • PTVSD version: 4.3.0
  • OS and version: Debian 9.0
  • Python version: 3.5.3 and 3.6.3
  • Using VS Code

And:

  • PTVSD version: 4.3.0
  • OS and version: macOS 10.14.5
  • Python version: 3.7.2
  • Using VS Code

@fabioz
Copy link
Collaborator

fabioz commented Aug 6, 2019

Using os.environ["PYDEVD_USE_FRAME_EVAL"] = "NO" should be Ok (I think we don't have tests for the frame eval mode + gevent, so, until that's resolved we should probably disable the frame eval mode when gevent mode is enabled by default).

@fabioz fabioz changed the title Error when using GEVENT_SUPPORT Support gevent with frame eval mode Aug 8, 2019
@fabioz
Copy link
Collaborator

fabioz commented Aug 8, 2019

I'm reopening as we should address the actual error when working with frame eval and gevent (or disable the frame eval mode ourselves when gevent mode is enabled if it turns out to be too complicated).

@fabioz fabioz reopened this Aug 8, 2019
@fabioz
Copy link
Collaborator

fabioz commented Jan 16, 2020

The actual error reported here seems to be fixed already (I tried in a number of configurations and it seems to work well).

Still, while testing I found a different issue which I'm investigating.

i.e.: Doing a remote attach with a gevent.monkey.patch_all() is not working.

Using the code below:

import os
import sys
sys.path.insert(0, 'D:\\x\\ptvsd_workspace\\ptvsd\\src')
os.environ["GEVENT_SUPPORT"] = 'True'

import ptvsd
print(ptvsd)

from gevent import monkey
monkey.patch_all()

DEBUG_PORT = 5679

def myfunc():
    ptvsd.enable_attach(('127.0.0.1', DEBUG_PORT), log_dir='c:/temp')
    print("Please attach debugger to port ", DEBUG_PORT)
    ptvsd.wait_for_attach()  # blocks execution until debugger is attached

    print("Debugger attached")
    import time
    while True:
        time.sleep(1)
        print('.')

if __name__ == "__main__":
    myfunc()

gives the following error:

<module 'ptvsd' from 'D:\\x\\ptvsd_workspace\\ptvsd\\src\\ptvsd\\__init__.py'>
Traceback (most recent call last):
  File "D:\x\ptvsd_workspace\ptvsd\src\ptvsd\_vendored\pydevd\_pydevd_bundle\pydevd_comm.py", line 287, in _on_run
    line = self._read_line()
  File "D:\x\ptvsd_workspace\ptvsd\src\ptvsd\_vendored\pydevd\_pydevd_bundle\pydevd_comm.py", line 270, in _read_line
    r = self.sock.recv(1024)
  File "C:\bin\Miniconda\envs\py36_tests\lib\site-packages\gevent\_socket3.py", line 382, in recv
    self._wait(self._read_event)
  File "src\gevent\_hub_primitives.py", line 284, in gevent.__hub_primitives.wait_on_socket
  File "src\gevent\_hub_primitives.py", line 289, in gevent.__hub_primitives.wait_on_socket
  File "src\gevent\_hub_primitives.py", line 271, in gevent.__hub_primitives._primitive_wait
  File "src\gevent\_hub_primitives.py", line 46, in gevent.__hub_primitives.WaitOperationsGreenlet.wait
  File "src\gevent\_hub_primitives.py", line 46, in gevent.__hub_primitives.WaitOperationsGreenlet.wait
  File "src\gevent\_hub_primitives.py", line 55, in gevent.__hub_primitives.WaitOperationsGreenlet.wait
  File "src\gevent\_waiter.py", line 151, in gevent.__waiter.Waiter.get
  File "src\gevent\_greenlet_primitives.py", line 60, in gevent.__greenlet_primitives.SwitchOutGreenletWithLoop.switch
  File "src\gevent\_greenlet_primitives.py", line 60, in gevent.__greenlet_primitives.SwitchOutGreenletWithLoop.switch
  File "src\gevent\_greenlet_primitives.py", line 64, in gevent.__greenlet_primitives.SwitchOutGreenletWithLoop.switch
  File "src\gevent\__greenlet_primitives.pxd", line 35, in gevent.__greenlet_primitives._greenlet_switch
greenlet.error: cannot switch to a different thread

@fabioz
Copy link
Collaborator

fabioz commented Jan 20, 2020

Note: add a gevent flag to enable_attach (so that we don't need to do: os.environ["GEVENT_SUPPORT"] = 'True').

@fabioz
Copy link
Collaborator

fabioz commented Jan 20, 2020

Note: verify if debugging works properly if enable_attach is called after gevent.monkey.patch_all().

@fabioz
Copy link
Collaborator

fabioz commented Jan 21, 2020

I've investigated more on gevent support for having flags instead of using the environment variable and I'm not sure it's worth it...

Just for some background on how things work in the event and the workarounds in the debugger:

gevent has a feature which monkey-patches many bits of the standard library (i.e.: threading, socket, time, etc.) and it does that in a very intrusive way (for instance, it changes start_new_thread, _allocate_lock, get_ident, etc. inside of the threading module, so, although it saves what was changed, we can't really restore it nor use it directly in the original loaded module).

Given that, the current workaround done in the debugger is the following:

When loading the modules which will be affected by gevent, the debugger forces loading a 2nd instance of the needed modules from the standard library and uses that private version instead of the ones which will be (or were already) patched by gevent (this is done in: https://github.com/microsoft/ptvsd/blob/master/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py#L185).

This is done at load time because afterwards all the other modules in pydevd need to use that private copy.

So, to overcome this limitation, the other feasible approach which I see is not relying on the standard library at all (so, for instance, the debugger wouldn't be able rely on the threading module and would need to have its own version just using the core bits it needs, verifying if it should get the needed core bits from the current module or from the internal version that gevent saved -- these modules would need to support all the versions that the debugger uses).

Note that with this approach we probably wouldn't need the gevent flag at all... but as it requires rewriting and keeping an internal version of many things currently provided by the standard library, I'm not sure if it's worth it given the time to implement and maintain it afterwards.

@int19h int19h transferred this issue from microsoft/ptvsd May 4, 2020
@int19h int19h added the enhancement New feature or request label Jun 19, 2020
@pirumpi
Copy link

pirumpi commented May 21, 2021

It would be nice to add a simple example on adding the enable_attach flag to gevent, those post don't provide any real help 👎

@fabioz
Copy link
Collaborator

fabioz commented May 21, 2021

@pirumpi

The enable attach should work in gevent provided you have a GEVENT_SUPPORT=1 environment variable set (this issue is unrelated to that).

Note that the latest gevent broke the debugger, so, you must pin to a previous version (the last supported version is 20.9.0) to be able to use debugpy along with gevent. See: #515

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

No branches or pull requests

6 participants