Skip to content

Commit 1d1685f

Browse files
committed
Fix threading local fallback import
1 parent 90ac539 commit 1d1685f

3 files changed

Lines changed: 43 additions & 9 deletions

File tree

Lib/_threading_local.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ def __init__(self):
3636
def get_dict(self):
3737
"""Return the dict for the current thread. Raises KeyError if none
3838
defined."""
39-
thread = current_thread()
39+
thread = threading.current_thread()
4040
return self.dicts[id(thread)][1]
4141

4242
def create_dict(self):
4343
"""Create a new dict for the current thread, and return it."""
4444
localdict = {}
4545
key = self.key
46-
thread = current_thread()
46+
thread = threading.current_thread()
4747
idt = id(thread)
4848
def local_deleted(_, key=key):
4949
# When the localimpl is deleted, remove the thread attribute.
@@ -88,7 +88,7 @@ def __new__(cls, /, *args, **kw):
8888
self = object.__new__(cls)
8989
impl = _localimpl()
9090
impl.localargs = (args, kw)
91-
impl.locallock = RLock()
91+
impl.locallock = threading.RLock()
9292
object.__setattr__(self, '_local__impl', impl)
9393
# We need to create the thread dict in anticipation of
9494
# __init__ being called, to make sure we don't call it
@@ -117,4 +117,4 @@ def __delattr__(self, name):
117117
return object.__delattr__(self, name)
118118

119119

120-
from threading import current_thread, RLock
120+
import threading

Lib/test/test_threading.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,27 @@ def test_concurrent_tee_negative_n(self):
26082608

26092609

26102610
class MiscTestCase(unittest.TestCase):
2611+
def test_threading_local_fallback_import(self):
2612+
# gh-156341: threading must still be importable when the platform's
2613+
# `_thread` module doesn't provide the `_local` type, forcing the
2614+
# pure Python fallback implementation in `_threading_local`. This
2615+
# used to raise ImportError due to a circular import between the
2616+
# two modules.
2617+
rc, out, err = assert_python_ok("-c", """if 1:
2618+
import _thread
2619+
del _thread._local
2620+
import threading
2621+
import _threading_local
2622+
# Sanity check: the pure Python fallback is actually in use,
2623+
# and it still behaves like a normal thread-local object.
2624+
assert threading.local is _threading_local.local
2625+
tlocal = threading.local()
2626+
tlocal.x = 1
2627+
assert tlocal.x == 1
2628+
print("ok")
2629+
""")
2630+
self.assertEqual(out.strip(), b"ok")
2631+
26112632
def test__all__(self):
26122633
restore_default_excepthook(self)
26132634

Lib/threading.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,11 +1537,6 @@ def __init__(self):
15371537
_active[self._ident] = self
15381538

15391539

1540-
# Helper thread-local instance to detect when a _DummyThread
1541-
# is collected. Not a part of the public API.
1542-
_thread_local_info = local()
1543-
1544-
15451540
class _DeleteDummyThreadOnDel:
15461541
'''
15471542
Helper class to remove a dummy thread from threading._active on __del__.
@@ -1692,6 +1687,24 @@ def _register_atexit(func, *arg, **kwargs):
16921687

16931688
_main_thread = _MainThread()
16941689

1690+
# Helper thread-local instance to detect when a _DummyThread is collected.
1691+
# Not a part of the public API.
1692+
#
1693+
# This must be created only after _main_thread has been constructed and
1694+
# registered in `_active` above (rather than immediately after the
1695+
# _MainThread class, where it conceptually belongs). Constructing a
1696+
# local() instance immediately populates the thread-local dict for the
1697+
# creating thread, which calls current_thread(). When the platform's
1698+
# `_thread` module doesn't provide `_local` (see the `local` import near
1699+
# the top of this file), that population is done by the pure Python
1700+
# fallback implementation in `_threading_local`. Creating this any earlier
1701+
# either reintroduces the circular import described in gh-156341 (if
1702+
# current_thread isn't defined yet), or -- if current_thread is defined
1703+
# but the main thread hasn't been registered in `_active` yet -- causes
1704+
# current_thread() to construct a _DummyThread, whose __init__ itself
1705+
# needs _thread_local_info to already exist.
1706+
_thread_local_info = local()
1707+
16951708
def _shutdown():
16961709
"""
16971710
Wait until the Python thread state of all non-daemon threads get deleted.

0 commit comments

Comments
 (0)