joerick/pyinstrument

RuntimeError: cannot release un-acquired lock w/ gevent

Opened this issue · 0 comments

Hi there! Firstly, I want to say that I love this project and pyinstrument is amazing, thank you! I'm running into a weird issue when running w/ gevent.

Bug Repro

Env details:

  • MBP M3 MAX (Sonoma 14.2.1)
  • Python 3.12.1
  • gevent==24.2.1, greenlet==3.0.3, pyinstrument==4.6.2

When I attempt to run pyinstrument against my codebase, it fails when importing a package which monkey-patches the process.

File: pkg_a/init.py

# Patch everything as soon as possible within the package.
import gevent.monkey
gevent.monkey.patch_all()

File: script.py

# All this does is try to import the package
import pkg_a

When run, it emits the following:

$ pyinstrument script.py        
Traceback (most recent call last):
  File "/Users/vkomarov/dev/divvy-dev/services/collector/env/bin/pyinstrument", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/Users/vkomarov/dev/divvy-dev/services/collector/env/lib/python3.12/site-packages/pyinstrument/__main__.py", line 367, in main
    exec(code, globs, None)
  File "<string>", line 1, in <module>
  File "<frozen runpy>", line 286, in run_path
  File "<frozen runpy>", line 98, in _run_module_code
  File "<frozen runpy>", line 88, in _run_code
  File "script.py", line 2, in <module>
    import pkg_a
  File "<frozen importlib._bootstrap>", line 1357, in _find_and_load
  File "<frozen importlib._bootstrap>", line 421, in __exit__
  File "<frozen importlib._bootstrap>", line 376, in release
RuntimeError: cannot release un-acquired lock

Expected behavior: It should work w/ gevent appropriately.

My Analysis

It looks like _thread.get_ident() changes from before the import of the gevent patching code to after. (I verified this by printing out _thread.get_ident() within pkg_a/__init__.py). This seems like the direct cause of the RuntimeError (cpython source).

Then I took a look at what is causing this behavior, since the code runs just fine without it. I narrowed it down to profiler.start(), but didn't dig any deeper than that.

It should be noted, however, this seems to only happen in a transitive import. i.e.: If I added the monkey patching to script.py, it works just fine. Also, not patching threading works fine too (it just breaks other parts of our app) (i.e. gevent.monkey.patch_all(thread=False)).

Any thoughts on this? The only thing I can think of is the Profiler is doing something with threads in the .start() method that gevent monkeypatching doesn't play nice with.