Visitar URL original
Deadlock when calling PyGILState_Ensure() from a fresh C thread · Issue #96071 · python/cpython · GitHub
Skip to content

Deadlock when calling PyGILState_Ensure() from a fresh C thread #96071

Description

@tom-pytel

Bug report

This used to work with py 3.8, 3.9 and 3.10 but now fails. The failure is due to a deadlock condition due to the move of an alloc from before a HEAD_LOCK() to after causing a recursive call to PyGILState_Ensure() which eventually deadlocks on the HEAD_LOCK().

The following is a trace of calls from the initial call to PyGILState_Ensure() to the eventual deadlock:

pystate:PyGILState_Ensure()
pystate:PyThreadState_New()
pystate:new_threadstate()
  HEAD_LOCK()  <-- Initial head locked, the alloc_threadstate() call below used to happen before this in previous versions of Python.
pystate:alloc_threadstate()
obmalloc:PyMem_RawCalloc()
_tracemalloc:_PyMem_Raw.calloc()
_tracemalloc:tracemalloc_raw_calloc()
  get_reentrant() = 0
  set_reenterant(1)
pystate:PyGILState_Ensure()
pystate:PyThreadState_New()
pystate:new_threadstate()
  HEAD_LOCK()  <--   DEADLOCK HERE!

Steps to reproduce: In a Python C extension module, create a C thread then try to call PyGILState_Ensure() in that thread with no previous existing Python threadstate.

Your environment

Python 3.11.0rc1
Linux tom-VirtualBox 5.15.0-46-generic #49~20.04.1-Ubuntu SMP Thu Aug 4 19:15:44 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Activity

  1. ronaldoussoren commented on Aug 18, 2022

    @ronaldoussoren
    Contributor

    Do you have a self-contained example that demonstrates the problem? I quickly checked using PyObjC on macOS (which basically does this when starting threads using the Cocoa interface), and that code did not deadlock.

  2. tom-pytel commented on Aug 18, 2022

    @tom-pytel
    ContributorAuthor

    Do you have a self-contained example that demonstrates the problem? I quickly checked using PyObjC on macOS (which basically does this when starting threads using the Cocoa interface), and that code did not deadlock.

    Ok, I investigated a little deeper and admittedly I have a bit more going on in the environment where I found this. Tried duplicating until I found the difference: tracemalloc. When tracemalloc is imported and started the function flow is as shown in the header of this issue. When it is not, then after obmalloc:PyMem_RawCalloc() it goes to obmalloc:_PyMem_DebugRawCalloc() instead of _tracemalloc:_PyMem_Raw.calloc() which avoids the recursive call to PyGILState_Ensure() and works fine. I will see about getting some poc module code here tomorrow or after.

  3. ronaldoussoren commented on Aug 18, 2022

    @ronaldoussoren
    Contributor

    I can reproduce using PyObjC by using python3.11 -X tracemalloc script.py to start the PyObjC using script I mentioned earlier.

    @pablogsal is this a release blocker?

  4. pablogsal commented on Aug 18, 2022

    @pablogsal
    Member

    @pablogsal is this a release blocker?

    Indeed it is. Thanks for pinging me.

  5. pablogsal commented on Aug 18, 2022

    @pablogsal
    Member

    @ronaldoussoren Can you share the reproducer?

  6. pablogsal commented on Aug 18, 2022

    @pablogsal
    Member

    Or alternatively, could you bisect the issue?

  7. tom-pytel commented on Aug 18, 2022

    @tom-pytel
    ContributorAuthor

    Or alternatively, could you bisect the issue?

    In previous versions the memory for the new PyThreadState was allocated before the HEAD_LOCK() in pystate:new_threadstate(), if you revert to that behavior the bug is fixed.

  8. ronaldoussoren commented on Aug 18, 2022

    @ronaldoussoren
    Contributor

    The reproducer needs PyObjC (from v8.5-branch in https://github.com/ronaldoussoren/pyobjc because the latest release does not work with Python 3.11 yet).

    The reproducer script:

    from Cocoa import NSObject, NSThread
    import time
    
    class Runner(NSObject):
       def doit_(self, arg):
           print("hello world")
           print(NSThread.currentThread().isMainThread())
    
    
    runner = Runner.alloc().init()
    
    thread = NSThread.alloc().initWithTarget_selector_object_(runner, b"doit:", None)
    thread.start()
    print(thread)
    time.sleep(1)
    while thread.isExecuting():
        time.sleep(1)
    

    This script hangs when using "python3.11 -X tracemalloc script.pyand works fine when using an older version of Python or leaving out-X tracemalloc``.

    It is probably easier to reproduce this using a small C extension that uses pthread_create to launch a thread that call PyGILState_Ensure.

    I can look into this during the weekend at the earliest, but also need to get a release of PyObjC out to fix Python 3.11 support (which is needed due to documented API changes, nothing to worry about there).

  9. pablogsal commented on Aug 18, 2022

    @pablogsal
    Member

    I will prepare a patch tomorrow. I have enough information to produce a fix.

  10. 16 remaining items

  11. ericsnowcurrently commented on Aug 22, 2022

    @ericsnowcurrently
    Member

    We can close this once gh-96184 and gh-96185 are merged.

  12. added 2 commits that reference this issue on Aug 23, 2022
  13. added a commit that references this issue on Aug 23, 2022
  14. added a commit that references this issue on Aug 23, 2022
  15. kumaraditya303 commented on Aug 24, 2022

    @kumaraditya303
    Contributor

    Closing as I have fixed both the issue and added tests to ensure no further regression. Thanks!

  16. Repository owner moved this from In Progress to Done in Release and Deferred blockers 🚫on Aug 24, 2022
  17. ericsnowcurrently commented on Aug 24, 2022

    @ericsnowcurrently
    Member
  18. added a commit that references this issue on Aug 24, 2022
  19. added 2 commits that reference this issue on Nov 19, 2024
  20. added a commit that references this issue on Dec 2, 2024
  21. added a commit that references this issue on Jan 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

3.11only security fixes3.12only security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions