Skip to content

Commit c0fc88f

Browse files
ericsnowcurrentlyAlexWaygoodhugovk
authored
[3.12] gh-112826: Fix the threading Module When _thread is Missing _is_main_interpreter() (#112850)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
1 parent 34e9e20 commit c0fc88f

File tree

3 files changed

+51
-1
lines changed

3 files changed

+51
-1
lines changed

Doc/whatsnew/3.12.rst

+9
Original file line numberDiff line numberDiff line change
@@ -1895,6 +1895,15 @@ Changes in the Python API
18951895
* Mixing tabs and spaces as indentation in the same file is not supported anymore and will
18961896
raise a :exc:`TabError`.
18971897

1898+
* The :mod:`threading` module now expects the :mod:`!_thread` module to have
1899+
an ``_is_main_interpreter`` attribute. It is a function with no
1900+
arguments that returns ``True`` if the current interpreter is the
1901+
main interpreter.
1902+
1903+
Any library or application that provides a custom ``_thread`` module
1904+
should provide ``_is_main_interpreter()``.
1905+
(See :gh:`112826`.)
1906+
18981907
Build Changes
18991908
=============
19001909

Lib/test/test_threading.py

+28
Original file line numberDiff line numberDiff line change
@@ -1827,6 +1827,34 @@ def test__all__(self):
18271827
support.check__all__(self, threading, ('threading', '_thread'),
18281828
extra=extra, not_exported=not_exported)
18291829

1830+
@requires_subprocess()
1831+
def test_gh112826_missing__thread__is_main_interpreter(self):
1832+
with os_helper.temp_dir() as tempdir:
1833+
modname = '_thread_fake'
1834+
import os.path
1835+
filename = os.path.join(tempdir, modname + '.py')
1836+
with open(filename, 'w') as outfile:
1837+
outfile.write("""if True:
1838+
import _thread
1839+
globals().update(vars(_thread))
1840+
del _is_main_interpreter
1841+
""")
1842+
1843+
expected_output = b'success!'
1844+
_, out, err = assert_python_ok("-c", f"""if True:
1845+
import sys
1846+
sys.path.insert(0, {tempdir!r})
1847+
import {modname}
1848+
sys.modules['_thread'] = {modname}
1849+
del sys.modules[{modname!r}]
1850+
1851+
import threading
1852+
print({expected_output.decode('utf-8')!r}, end='')
1853+
""")
1854+
1855+
self.assertEqual(out, expected_output)
1856+
self.assertEqual(err, b'')
1857+
18301858

18311859
class InterruptMainTests(unittest.TestCase):
18321860
def check_interrupt_main_with_signal_handler(self, signum):

Lib/threading.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,20 @@
3737
_allocate_lock = _thread.allocate_lock
3838
_set_sentinel = _thread._set_sentinel
3939
get_ident = _thread.get_ident
40-
_is_main_interpreter = _thread._is_main_interpreter
40+
try:
41+
_is_main_interpreter = _thread._is_main_interpreter
42+
except AttributeError:
43+
# See https://github.com/python/cpython/issues/112826.
44+
# We can pretend a subinterpreter is the main interpreter for the
45+
# sake of _shutdown(), since that only means we do not wait for the
46+
# subinterpreter's threads to finish. Instead, they will be stopped
47+
# later by the mechanism we use for daemon threads. The likelihood
48+
# of this case is small because rarely will the _thread module be
49+
# replaced by a module without _is_main_interpreter().
50+
# Furthermore, this is all irrelevant in applications
51+
# that do not use subinterpreters.
52+
def _is_main_interpreter():
53+
return True
4154
try:
4255
get_native_id = _thread.get_native_id
4356
_HAVE_THREAD_NATIVE_ID = True

0 commit comments

Comments
 (0)