Skip to content
Open
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
8 changes: 4 additions & 4 deletions Lib/_threading_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ def __init__(self):
def get_dict(self):
"""Return the dict for the current thread. Raises KeyError if none
defined."""
thread = current_thread()
thread = threading.current_thread()
return self.dicts[id(thread)][1]

def create_dict(self):
"""Create a new dict for the current thread, and return it."""
localdict = {}
key = self.key
thread = current_thread()
thread = threading.current_thread()
idt = id(thread)
def local_deleted(_, key=key):
# When the localimpl is deleted, remove the thread attribute.
Expand Down Expand Up @@ -88,7 +88,7 @@ def __new__(cls, /, *args, **kw):
self = object.__new__(cls)
impl = _localimpl()
impl.localargs = (args, kw)
impl.locallock = RLock()
impl.locallock = threading.RLock()
object.__setattr__(self, '_local__impl', impl)
# We need to create the thread dict in anticipation of
# __init__ being called, to make sure we don't call it
Expand Down Expand Up @@ -117,4 +117,4 @@ def __delattr__(self, name):
return object.__delattr__(self, name)


from threading import current_thread, RLock
import threading
21 changes: 21 additions & 0 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -2608,6 +2608,27 @@ def test_concurrent_tee_negative_n(self):


class MiscTestCase(unittest.TestCase):
def test_threading_local_fallback_import(self):
# gh-156341: threading must still be importable when the platform's
# `_thread` module doesn't provide the `_local` type, forcing the
# pure Python fallback implementation in `_threading_local`. This
# used to raise ImportError due to a circular import between the
# two modules.
rc, out, err = assert_python_ok("-c", """if 1:
import _thread
del _thread._local
import threading
import _threading_local
# Sanity check: the pure Python fallback is actually in use,
# and it still behaves like a normal thread-local object.
assert threading.local is _threading_local.local
tlocal = threading.local()
tlocal.x = 1
assert tlocal.x == 1
print("ok")
""")
self.assertEqual(out.strip(), b"ok")

def test__all__(self):
restore_default_excepthook(self)

Expand Down
23 changes: 18 additions & 5 deletions Lib/threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -1537,11 +1537,6 @@ def __init__(self):
_active[self._ident] = self


# Helper thread-local instance to detect when a _DummyThread
# is collected. Not a part of the public API.
_thread_local_info = local()


class _DeleteDummyThreadOnDel:
'''
Helper class to remove a dummy thread from threading._active on __del__.
Expand Down Expand Up @@ -1692,6 +1687,24 @@ def _register_atexit(func, *arg, **kwargs):

_main_thread = _MainThread()

# Helper thread-local instance to detect when a _DummyThread is collected.
# Not a part of the public API.
#
# This must be created only after _main_thread has been constructed and
# registered in `_active` above (rather than immediately after the
# _MainThread class, where it conceptually belongs). Constructing a
# local() instance immediately populates the thread-local dict for the
# creating thread, which calls current_thread(). When the platform's
# `_thread` module doesn't provide `_local` (see the `local` import near
# the top of this file), that population is done by the pure Python
# fallback implementation in `_threading_local`. Creating this any earlier
# either reintroduces the circular import described in gh-156341 (if
# current_thread isn't defined yet), or -- if current_thread is defined
# but the main thread hasn't been registered in `_active` yet -- causes
# current_thread() to construct a _DummyThread, whose __init__ itself
# needs _thread_local_info to already exist.
_thread_local_info = local()

def _shutdown():
"""
Wait until the Python thread state of all non-daemon threads get deleted.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix fallback importing of the :mod:`_threading_local` module when the
standard library `threading` module has not yet been fully initialized.
Loading