Skip to content

os.fork() called from DummyThread confuses threading shutdown logic #102512

Closed
@marmarek

Description

@marmarek

Bug report

threading._shutdown() relies on _main_thread having _tstate_lock not None (there is assert for that). When fork is called from a DummyThread (in my case, that's a thread created by (Py)Qt), it gets promoted to main thread, but remains very simplistic DummyThread. Especially, nobody initializes its _tstate_lock. threading._after_fork() handles the case of current thread not being in _active dict at all (by creating new MainThread object), but it doesn't handle the case of having DummyThread there already. This results in AssertionError in thread shutdown method - which for example confuses multiprocessing.Process (it gets exit code 1, even if the process function was successful).

Reproducer:

#!/usr/bin/python3

import threading
import multiprocessing
import _thread

class Bar(multiprocessing.Process):
    def run(self):
        print("process")

def run_thread(lock):
    # the call to current_thread() is crucial for reproducer - it allocates
    # DummyThread()
    print(f"thread: {threading.current_thread()}")
    p = Bar()
    p.start()
    p.join()
    print(f"proc exit code: {p.exitcode}")
    lock.release()


def main():
    lock = _thread.allocate_lock()
    lock.acquire()
    t = _thread.start_new_thread(run_thread, (lock,))
    # t.join
    lock.acquire()
    print(f"thread exit")

main()

It should print:

thread: <_DummyThread(Dummy-1, started daemon 135243893053120)>
process
proc exit code: 0
thread exit

but it prints:

thread: <_DummyThread(Dummy-1, started daemon 135243893053120)>
process
proc exit code: 1
thread exit

(see exit code difference)

multiprocessing.Process (or rather multiprocessing.popen_fork.Popen._launch() to be specific) swallows the exception, but adding some debug prints there I get:

Traceback (most recent call last):
  File "/usr/lib64/python3.11/multiprocessing/popen_fork.py", line 71, in _launch
    code = process_obj._bootstrap(parent_sentinel=child_r)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/multiprocessing/process.py", line 332, in _bootstrap
    threading._shutdown()
  File "/usr/lib64/python3.11/threading.py", line 1553, in _shutdown
    assert tlock is not None
           ^^^^^^^^^^^^^^^^^

Your environment

  • CPython versions tested on: 3.11.2
  • Operating system and architecture: Fedora 37, x86_64

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.11only security fixes3.12only security fixes3.13bugs and security fixestopic-multiprocessingtype-bugAn unexpected behavior, bug, or error

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions