Skip to content

[3.12] gh-112826: Fix the threading Module When _thread is Missing _is_main_interpreter() #112850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1895,6 +1895,15 @@ Changes in the Python API
* Mixing tabs and spaces as indentation in the same file is not supported anymore and will
raise a :exc:`TabError`.

* The :mod:`threading` module now expects the :mod:`!_thread` module to have
an ``_is_main_interpreter`` attribute. It is a function with no
arguments that returns ``True`` if the current interpreter is the
main interpreter.

Any library or application that provides a custom ``_thread`` module
should provide ``_is_main_interpreter()``.
(See :gh:`112826`.)

Build Changes
=============

Expand Down
28 changes: 28 additions & 0 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,34 @@ def test__all__(self):
support.check__all__(self, threading, ('threading', '_thread'),
extra=extra, not_exported=not_exported)

@requires_subprocess()
def test_gh112826_missing__thread__is_main_interpreter(self):
with os_helper.temp_dir() as tempdir:
modname = '_thread_fake'
import os.path
filename = os.path.join(tempdir, modname + '.py')
with open(filename, 'w') as outfile:
outfile.write("""if True:
import _thread
globals().update(vars(_thread))
del _is_main_interpreter
""")

expected_output = b'success!'
_, out, err = assert_python_ok("-c", f"""if True:
import sys
sys.path.insert(0, {tempdir!r})
import {modname}
sys.modules['_thread'] = {modname}
del sys.modules[{modname!r}]

import threading
print({expected_output.decode('utf-8')!r}, end='')
""")

self.assertEqual(out, expected_output)
self.assertEqual(err, b'')


class InterruptMainTests(unittest.TestCase):
def check_interrupt_main_with_signal_handler(self, signum):
Expand Down
15 changes: 14 additions & 1 deletion Lib/threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,20 @@
_allocate_lock = _thread.allocate_lock
_set_sentinel = _thread._set_sentinel
get_ident = _thread.get_ident
_is_main_interpreter = _thread._is_main_interpreter
try:
_is_main_interpreter = _thread._is_main_interpreter
except AttributeError:
# See https://github.com/python/cpython/issues/112826.
# We can pretend a subinterpreter is the main interpreter for the
# sake of _shutdown(), since that only means we do not wait for the
# subinterpreter's threads to finish. Instead, they will be stopped
# later by the mechanism we use for daemon threads. The likelihood
# of this case is small because rarely will the _thread module be
# replaced by a module without _is_main_interpreter().
# Furthermore, this is all irrelevant in applications
# that do not use subinterpreters.
def _is_main_interpreter():
return True
try:
get_native_id = _thread.get_native_id
_HAVE_THREAD_NATIVE_ID = True
Expand Down