Open
Description
test suite:
import time
import multiprocessing
class TestWhatever:
def test_thing(self):
def go():
time.sleep(2)
ctx = multiprocessing.get_context("fork")
proc = ctx.Process(target=go, args=())
proc.start()
Running as pytest test.py -n1
, output:
[classic@framework tmp2]$ pytest test.py -n1
========================================================================================== test session starts ==========================================================================================
platform linux -- Python 3.12.9, pytest-8.1.0, pluggy-1.4.0
rootdir: /home/classic/tmp2
plugins: xdist-3.4.0, anyio-4.1.0, random-0.2, repeat-0.9.3
1 worker [1 item]
. [100%]
=========================================================================================== warnings summary ============================================================================================
test.py::TestWhatever::test_thing
/usr/lib64/python3.12/multiprocessing/popen_fork.py:66: DeprecationWarning: This process (pid=16078) is multi-threaded, use of fork() may lead to deadlocks in the child.
self.pid = os.fork()
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
===================================================================================== 1 passed, 1 warning in 2.19s ======================================================================================
Per the author of this warning, multithreaded code is never safe if it also spawns using fork (see discussion). However I cannot locate any threads running. Here's an extension of the example that lists out threads running, and I can find none that are not the "main" thread:
conftest.py:
# conftest.py
import threading
import os
import logging
logging.basicConfig()
logging.getLogger("main").setLevel(logging.INFO)
class XDistHooks:
def pytest_configure_node(self, node):
for t in threading.enumerate():
logging.getLogger("main").info(
f"THREAD FROM MAIN PROCESS {os.getpid()}: {t}\n")
def pytest_configure(config):
if config.pluginmanager.hasplugin("xdist"):
config.pluginmanager.register(XDistHooks())
test.py:
# test.py
import time
import os
import multiprocessing
import logging
import threading
logging.basicConfig()
logging.getLogger("main").setLevel(logging.INFO)
class TestWhatever:
def test_thing(self):
for t in threading.enumerate():
logging.getLogger("main").info(
f"THREAD FROM CHILD PROCESS {os.getpid()} "
f"(parent: {os.getppid()}): {t}\n")
def go():
time.sleep(10)
ctx = multiprocessing.get_context("fork")
proc = ctx.Process(target=go, args=())
proc.start()
run output:
[classic@framework tmp]$ pytest test.py -s -p no:logging -n1
========================================================================================== test session starts ==========================================================================================
platform linux -- Python 3.12.9, pytest-8.1.0, pluggy-1.4.0
rootdir: /home/classic/tmp
plugins: xdist-3.4.0, anyio-4.1.0, random-0.2, repeat-0.9.3
initialized: 1/1 workerINFO:main:THREAD FROM MAIN PROCESS 16341: <_MainThread(MainThread, started 139752741145472)>
1 worker [1 item]
INFO:main:THREAD FROM CHILD PROCESS 16342 (parent: 16341): <_MainThread(MainThread, started 139984508341120)>
.
=========================================================================================== warnings summary ============================================================================================
test.py::TestWhatever::test_thing
/usr/lib64/python3.12/multiprocessing/popen_fork.py:66: DeprecationWarning: This process (pid=16342) is multi-threaded, use of fork() may lead to deadlocks in the child.
self.pid = os.fork()
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
===================================================================================== 1 passed, 1 warning in 10.17s =====================================================================================
Basically I want to keep using fork() in my test code, since we are running functions inside the tests themselves in processes. Where is pytest-xdist and/or execnet spawning threads exactly (code is pretty opaque) and is this a bug in the python interpreter?