mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-100228: Warn from os.fork() if other threads exist. (#100229)
Not comprehensive, best effort warning. There are cases when threads exist on some platforms that this code cannot detect. macOS when API permissions allow and Linux with a readable /proc procfs present are the currently supported cases where a warning should show up reliably. Starting with a DeprecationWarning for now, it is less disruptive than something like RuntimeWarning and most likely to only be seen in people's CI tests - a good place to start with this messaging.
This commit is contained in:
parent
2df82db485
commit
894f2c3c16
12 changed files with 283 additions and 66 deletions
|
|
@ -20,6 +20,7 @@
|
|||
import signal
|
||||
import textwrap
|
||||
import traceback
|
||||
import warnings
|
||||
|
||||
from unittest import mock
|
||||
from test import lock_tests
|
||||
|
|
@ -563,7 +564,7 @@ def test_dummy_thread_after_fork(self):
|
|||
# Issue #14308: a dummy thread in the active list doesn't mess up
|
||||
# the after-fork mechanism.
|
||||
code = """if 1:
|
||||
import _thread, threading, os, time
|
||||
import _thread, threading, os, time, warnings
|
||||
|
||||
def background_thread(evt):
|
||||
# Creates and registers the _DummyThread instance
|
||||
|
|
@ -575,11 +576,16 @@ def background_thread(evt):
|
|||
_thread.start_new_thread(background_thread, (evt,))
|
||||
evt.wait()
|
||||
assert threading.active_count() == 2, threading.active_count()
|
||||
if os.fork() == 0:
|
||||
assert threading.active_count() == 1, threading.active_count()
|
||||
os._exit(0)
|
||||
else:
|
||||
os.wait()
|
||||
with warnings.catch_warnings(record=True) as ws:
|
||||
warnings.filterwarnings(
|
||||
"always", category=DeprecationWarning)
|
||||
if os.fork() == 0:
|
||||
assert threading.active_count() == 1, threading.active_count()
|
||||
os._exit(0)
|
||||
else:
|
||||
assert ws[0].category == DeprecationWarning, ws[0]
|
||||
assert 'fork' in str(ws[0].message), ws[0]
|
||||
os.wait()
|
||||
"""
|
||||
_, out, err = assert_python_ok("-c", code)
|
||||
self.assertEqual(out, b'')
|
||||
|
|
@ -598,13 +604,15 @@ def test_is_alive_after_fork(self):
|
|||
for i in range(20):
|
||||
t = threading.Thread(target=lambda: None)
|
||||
t.start()
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
os._exit(11 if t.is_alive() else 10)
|
||||
else:
|
||||
t.join()
|
||||
# Ignore the warning about fork with threads.
|
||||
with warnings.catch_warnings(category=DeprecationWarning,
|
||||
action="ignore"):
|
||||
if (pid := os.fork()) == 0:
|
||||
os._exit(11 if t.is_alive() else 10)
|
||||
else:
|
||||
t.join()
|
||||
|
||||
support.wait_process(pid, exitcode=10)
|
||||
support.wait_process(pid, exitcode=10)
|
||||
|
||||
def test_main_thread(self):
|
||||
main = threading.main_thread()
|
||||
|
|
@ -645,21 +653,26 @@ def test_main_thread_after_fork(self):
|
|||
@unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
|
||||
def test_main_thread_after_fork_from_nonmain_thread(self):
|
||||
code = """if 1:
|
||||
import os, threading, sys
|
||||
import os, threading, sys, warnings
|
||||
from test import support
|
||||
|
||||
def func():
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
main = threading.main_thread()
|
||||
print(main.name)
|
||||
print(main.ident == threading.current_thread().ident)
|
||||
print(main.ident == threading.get_ident())
|
||||
# stdout is fully buffered because not a tty,
|
||||
# we have to flush before exit.
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
support.wait_process(pid, exitcode=0)
|
||||
with warnings.catch_warnings(record=True) as ws:
|
||||
warnings.filterwarnings(
|
||||
"always", category=DeprecationWarning)
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
main = threading.main_thread()
|
||||
print(main.name)
|
||||
print(main.ident == threading.current_thread().ident)
|
||||
print(main.ident == threading.get_ident())
|
||||
# stdout is fully buffered because not a tty,
|
||||
# we have to flush before exit.
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
assert ws[0].category == DeprecationWarning, ws[0]
|
||||
assert 'fork' in str(ws[0].message), ws[0]
|
||||
support.wait_process(pid, exitcode=0)
|
||||
|
||||
th = threading.Thread(target=func)
|
||||
th.start()
|
||||
|
|
@ -667,7 +680,7 @@ def func():
|
|||
"""
|
||||
_, out, err = assert_python_ok("-c", code)
|
||||
data = out.decode().replace('\r', '')
|
||||
self.assertEqual(err, b"")
|
||||
self.assertEqual(err.decode('utf-8'), "")
|
||||
self.assertEqual(data, "Thread-1 (func)\nTrue\nTrue\n")
|
||||
|
||||
def test_main_thread_during_shutdown(self):
|
||||
|
|
@ -1173,15 +1186,18 @@ def do_fork_and_wait():
|
|||
else:
|
||||
os._exit(50)
|
||||
|
||||
# start a bunch of threads that will fork() child processes
|
||||
threads = []
|
||||
for i in range(16):
|
||||
t = threading.Thread(target=do_fork_and_wait)
|
||||
threads.append(t)
|
||||
t.start()
|
||||
# Ignore the warning about fork with threads.
|
||||
with warnings.catch_warnings(category=DeprecationWarning,
|
||||
action="ignore"):
|
||||
# start a bunch of threads that will fork() child processes
|
||||
threads = []
|
||||
for i in range(16):
|
||||
t = threading.Thread(target=do_fork_and_wait)
|
||||
threads.append(t)
|
||||
t.start()
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
@support.requires_fork()
|
||||
def test_clear_threads_states_after_fork(self):
|
||||
|
|
@ -1194,18 +1210,22 @@ def test_clear_threads_states_after_fork(self):
|
|||
threads.append(t)
|
||||
t.start()
|
||||
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
# check that threads states have been cleared
|
||||
if len(sys._current_frames()) == 1:
|
||||
os._exit(51)
|
||||
else:
|
||||
os._exit(52)
|
||||
else:
|
||||
support.wait_process(pid, exitcode=51)
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
try:
|
||||
# Ignore the warning about fork with threads.
|
||||
with warnings.catch_warnings(category=DeprecationWarning,
|
||||
action="ignore"):
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
# check that threads states have been cleared
|
||||
if len(sys._current_frames()) == 1:
|
||||
os._exit(51)
|
||||
else:
|
||||
os._exit(52)
|
||||
else:
|
||||
support.wait_process(pid, exitcode=51)
|
||||
finally:
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
|
||||
class SubinterpThreadingTests(BaseTestCase):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue