diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index f5ba16ea08ecec..a8f3c139b24be1 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -928,6 +928,39 @@ def test_debug_deprecation(self): b'is deprecated and will be removed in Python 3.12') self.assertIn(msg, err) + def test_import_from_another_thread(self): + # bpo-1596321: If the threading module is first import from a thread + # different than the main thread, threading._shutdown() must handle + # this case without logging an error at Python exit. + code = textwrap.dedent(''' + import _thread + import sys + + event = _thread.allocate_lock() + event.acquire() + + def import_threading(): + import threading + event.release() + + if 'threading' in sys.modules: + raise Exception('threading is already imported') + + _thread.start_new_thread(import_threading, ()) + + # wait until the threading module is imported + event.acquire() + event.release() + + if 'threading' not in sys.modules: + raise Exception('threading is not imported') + + # don't wait until the thread completes + ''') + rc, out, err = assert_python_ok("-c", code) + self.assertEqual(out, b'') + self.assertEqual(err, b'') + class ThreadJoinOnShutdown(BaseTestCase): diff --git a/Lib/threading.py b/Lib/threading.py index c2b94a5045514f..9d11c8906ad7ec 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1504,20 +1504,29 @@ def _shutdown(): global _SHUTTING_DOWN _SHUTTING_DOWN = True - # Main thread - tlock = _main_thread._tstate_lock - # The main thread isn't finished yet, so its thread state lock can't have - # been released. - assert tlock is not None - assert tlock.locked() - tlock.release() - _main_thread._stop() # Call registered threading atexit functions before threads are joined. # Order is reversed, similar to atexit. for atexit_call in reversed(_threading_atexits): atexit_call() + # Main thread + if _main_thread.ident == get_ident(): + tlock = _main_thread._tstate_lock + # The main thread isn't finished yet, so its thread state lock can't + # have been released. + assert tlock is not None + assert tlock.locked() + tlock.release() + _main_thread._stop() + else: + # bpo-1596321: _shutdown() must be called in the main thread. + # If the threading module was not imported by the main thread, + # _main_thread is the thread which imported the threading module. + # In this case, ignore _main_thread, similar behavior than for threads + # spawned by C libraries or using _thread.start_new_thread(). + pass + # Join all non-deamon threads while True: with _shutdown_locks_lock: diff --git a/Misc/NEWS.d/next/Library/2021-09-24-17-20-23.bpo-1596321.3nhPUk.rst b/Misc/NEWS.d/next/Library/2021-09-24-17-20-23.bpo-1596321.3nhPUk.rst new file mode 100644 index 00000000000000..61a3e5abf395e1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-09-24-17-20-23.bpo-1596321.3nhPUk.rst @@ -0,0 +1,3 @@ +Fix the :func:`threading._shutdown` function when the :mod:`threading` module +was imported first from a thread different than the main thread: no longer log +an error at Python exit.