Skip to content

Commit d8d36c6

Browse files
committed
impl more threading
1 parent b5e93d1 commit d8d36c6

File tree

13 files changed

+839
-55
lines changed

13 files changed

+839
-55
lines changed

Lib/_dummy_thread.py

Lines changed: 142 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,43 @@
1111
import _dummy_thread as _thread
1212
1313
"""
14+
1415
# Exports only things specified by thread documentation;
1516
# skipping obsolete synonyms allocate(), start_new(), exit_thread().
16-
__all__ = ['error', 'start_new_thread', 'exit', 'get_ident', 'allocate_lock',
17-
'interrupt_main', 'LockType', 'RLock',
18-
'_count']
17+
__all__ = [
18+
"error",
19+
"start_new_thread",
20+
"exit",
21+
"get_ident",
22+
"allocate_lock",
23+
"interrupt_main",
24+
"LockType",
25+
"RLock",
26+
"_count",
27+
"start_joinable_thread",
28+
"daemon_threads_allowed",
29+
"_shutdown",
30+
"_make_thread_handle",
31+
"_ThreadHandle",
32+
"_get_main_thread_ident",
33+
"_is_main_interpreter",
34+
"_local",
35+
]
1936

2037
# A dummy value
2138
TIMEOUT_MAX = 2**31
2239

40+
# Main thread ident for dummy implementation
41+
_MAIN_THREAD_IDENT = -1
42+
2343
# NOTE: this module can be imported early in the extension building process,
2444
# and so top level imports of other modules should be avoided. Instead, all
2545
# imports are done when needed on a function-by-function basis. Since threads
2646
# are disabled, the import lock should not be an issue anyway (??).
2747

2848
error = RuntimeError
2949

50+
3051
def start_new_thread(function, args, kwargs={}):
3152
"""Dummy implementation of _thread.start_new_thread().
3253
@@ -52,44 +73,98 @@ def start_new_thread(function, args, kwargs={}):
5273
pass
5374
except:
5475
import traceback
76+
5577
traceback.print_exc()
5678
_main = True
5779
global _interrupt
5880
if _interrupt:
5981
_interrupt = False
6082
raise KeyboardInterrupt
6183

84+
85+
def start_joinable_thread(function, handle=None, daemon=True):
86+
"""Dummy implementation of _thread.start_joinable_thread().
87+
88+
In dummy thread, we just run the function synchronously.
89+
"""
90+
if handle is None:
91+
handle = _ThreadHandle()
92+
try:
93+
function()
94+
except SystemExit:
95+
pass
96+
except:
97+
import traceback
98+
99+
traceback.print_exc()
100+
handle._set_done()
101+
return handle
102+
103+
104+
def daemon_threads_allowed():
105+
"""Dummy implementation of _thread.daemon_threads_allowed()."""
106+
return True
107+
108+
109+
def _shutdown():
110+
"""Dummy implementation of _thread._shutdown()."""
111+
pass
112+
113+
114+
def _make_thread_handle(ident):
115+
"""Dummy implementation of _thread._make_thread_handle()."""
116+
handle = _ThreadHandle()
117+
handle._ident = ident
118+
return handle
119+
120+
121+
def _get_main_thread_ident():
122+
"""Dummy implementation of _thread._get_main_thread_ident()."""
123+
return _MAIN_THREAD_IDENT
124+
125+
126+
def _is_main_interpreter():
127+
"""Dummy implementation of _thread._is_main_interpreter()."""
128+
return True
129+
130+
62131
def exit():
63132
"""Dummy implementation of _thread.exit()."""
64133
raise SystemExit
65134

135+
66136
def get_ident():
67137
"""Dummy implementation of _thread.get_ident().
68138
69139
Since this module should only be used when _threadmodule is not
70140
available, it is safe to assume that the current process is the
71141
only thread. Thus a constant can be safely returned.
72142
"""
73-
return -1
143+
return _MAIN_THREAD_IDENT
144+
74145

75146
def allocate_lock():
76147
"""Dummy implementation of _thread.allocate_lock()."""
77148
return LockType()
78149

150+
79151
def stack_size(size=None):
80152
"""Dummy implementation of _thread.stack_size()."""
81153
if size is not None:
82154
raise error("setting thread stack size not supported")
83155
return 0
84156

157+
85158
def _set_sentinel():
86159
"""Dummy implementation of _thread._set_sentinel()."""
87160
return LockType()
88161

162+
89163
def _count():
90164
"""Dummy implementation of _thread._count()."""
91165
return 0
92166

167+
93168
class LockType(object):
94169
"""Class implementing dummy implementation of _thread.LockType.
95170
@@ -125,6 +200,7 @@ def acquire(self, waitflag=None, timeout=-1):
125200
else:
126201
if timeout > 0:
127202
import time
203+
128204
time.sleep(timeout)
129205
return False
130206

@@ -153,14 +229,41 @@ def __repr__(self):
153229
"locked" if self.locked_status else "unlocked",
154230
self.__class__.__module__,
155231
self.__class__.__qualname__,
156-
hex(id(self))
232+
hex(id(self)),
157233
)
158234

235+
236+
class _ThreadHandle:
237+
"""Dummy implementation of _thread._ThreadHandle."""
238+
239+
def __init__(self):
240+
self._ident = _MAIN_THREAD_IDENT
241+
self._done = False
242+
243+
@property
244+
def ident(self):
245+
return self._ident
246+
247+
def _set_done(self):
248+
self._done = True
249+
250+
def is_done(self):
251+
return self._done
252+
253+
def join(self, timeout=None):
254+
# In dummy thread, thread is always done
255+
return
256+
257+
def __repr__(self):
258+
return f"<_ThreadHandle ident={self._ident}>"
259+
260+
159261
# Used to signal that interrupt_main was called in a "thread"
160262
_interrupt = False
161263
# True when not executing in a "thread"
162264
_main = True
163265

266+
164267
def interrupt_main():
165268
"""Set _interrupt flag to True to have start_new_thread raise
166269
KeyboardInterrupt upon exiting."""
@@ -170,6 +273,7 @@ def interrupt_main():
170273
global _interrupt
171274
_interrupt = True
172275

276+
173277
class RLock:
174278
def __init__(self):
175279
self.locked_count = 0
@@ -190,7 +294,7 @@ def release(self):
190294
return True
191295

192296
def locked(self):
193-
return self.locked_status != 0
297+
return self.locked_count != 0
194298

195299
def __repr__(self):
196300
return "<%s %s.%s object owner=%s count=%s at %s>" % (
@@ -199,5 +303,36 @@ def __repr__(self):
199303
self.__class__.__qualname__,
200304
get_ident() if self.locked_count else 0,
201305
self.locked_count,
202-
hex(id(self))
306+
hex(id(self)),
203307
)
308+
309+
310+
class _local:
311+
"""Dummy implementation of _thread._local (thread-local storage)."""
312+
313+
def __init__(self):
314+
object.__setattr__(self, "_local__impl", {})
315+
316+
def __getattribute__(self, name):
317+
if name.startswith("_local__"):
318+
return object.__getattribute__(self, name)
319+
impl = object.__getattribute__(self, "_local__impl")
320+
try:
321+
return impl[name]
322+
except KeyError:
323+
raise AttributeError(name)
324+
325+
def __setattr__(self, name, value):
326+
if name.startswith("_local__"):
327+
return object.__setattr__(self, name, value)
328+
impl = object.__getattribute__(self, "_local__impl")
329+
impl[name] = value
330+
331+
def __delattr__(self, name):
332+
if name.startswith("_local__"):
333+
return object.__delattr__(self, name)
334+
impl = object.__getattribute__(self, "_local__impl")
335+
try:
336+
del impl[name]
337+
except KeyError:
338+
raise AttributeError(name)

Lib/dummy_threading.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
regardless of whether ``_thread`` was available which is not desired.
77
88
"""
9+
910
from sys import modules as sys_modules
1011

1112
import _dummy_thread
@@ -19,59 +20,62 @@
1920
# Could have checked if ``_thread`` was not in sys.modules and gone
2021
# a different route, but decided to mirror technique used with
2122
# ``threading`` below.
22-
if '_thread' in sys_modules:
23-
held_thread = sys_modules['_thread']
23+
if "_thread" in sys_modules:
24+
held_thread = sys_modules["_thread"]
2425
holding_thread = True
2526
# Must have some module named ``_thread`` that implements its API
2627
# in order to initially import ``threading``.
27-
sys_modules['_thread'] = sys_modules['_dummy_thread']
28+
sys_modules["_thread"] = sys_modules["_dummy_thread"]
2829

29-
if 'threading' in sys_modules:
30+
if "threading" in sys_modules:
3031
# If ``threading`` is already imported, might as well prevent
3132
# trying to import it more than needed by saving it if it is
3233
# already imported before deleting it.
33-
held_threading = sys_modules['threading']
34+
held_threading = sys_modules["threading"]
3435
holding_threading = True
35-
del sys_modules['threading']
36+
del sys_modules["threading"]
3637

37-
if '_threading_local' in sys_modules:
38+
if "_threading_local" in sys_modules:
3839
# If ``_threading_local`` is already imported, might as well prevent
3940
# trying to import it more than needed by saving it if it is
4041
# already imported before deleting it.
41-
held__threading_local = sys_modules['_threading_local']
42+
held__threading_local = sys_modules["_threading_local"]
4243
holding__threading_local = True
43-
del sys_modules['_threading_local']
44+
del sys_modules["_threading_local"]
4445

4546
import threading
47+
4648
# Need a copy of the code kept somewhere...
47-
sys_modules['_dummy_threading'] = sys_modules['threading']
48-
del sys_modules['threading']
49-
sys_modules['_dummy__threading_local'] = sys_modules['_threading_local']
50-
del sys_modules['_threading_local']
49+
sys_modules["_dummy_threading"] = sys_modules["threading"]
50+
del sys_modules["threading"]
51+
# _threading_local may not be imported if _thread._local is available
52+
if "_threading_local" in sys_modules:
53+
sys_modules["_dummy__threading_local"] = sys_modules["_threading_local"]
54+
del sys_modules["_threading_local"]
5155
from _dummy_threading import *
5256
from _dummy_threading import __all__
5357

5458
finally:
5559
# Put back ``threading`` if we overwrote earlier
5660

5761
if holding_threading:
58-
sys_modules['threading'] = held_threading
62+
sys_modules["threading"] = held_threading
5963
del held_threading
6064
del holding_threading
6165

6266
# Put back ``_threading_local`` if we overwrote earlier
6367

6468
if holding__threading_local:
65-
sys_modules['_threading_local'] = held__threading_local
69+
sys_modules["_threading_local"] = held__threading_local
6670
del held__threading_local
6771
del holding__threading_local
6872

6973
# Put back ``thread`` if we overwrote, else del the entry we made
7074
if holding_thread:
71-
sys_modules['_thread'] = held_thread
75+
sys_modules["_thread"] = held_thread
7276
del held_thread
7377
else:
74-
del sys_modules['_thread']
78+
del sys_modules["_thread"]
7579
del holding_thread
7680

7781
del _dummy_thread

Lib/test/test_concurrent_futures/test_process_pool.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ def test_max_tasks_early_shutdown(self):
190190
for i, future in enumerate(futures):
191191
self.assertEqual(future.result(), mul(i, i))
192192

193-
@unittest.expectedFailure # TODO: RUSTPYTHON AttributeError: module 'threading' has no attribute '_start_joinable_thread'. Did you mean: '_start_new_thread'?
194193
def test_python_finalization_error(self):
195194
# gh-109047: Catch RuntimeError on thread creation
196195
# during Python finalization.

Lib/test/test_concurrent_futures/test_shutdown.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ def test_cancel_futures(self):
108108
# one finished.
109109
self.assertGreater(len(others), 0)
110110

111-
@unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: b'' != b'apple'
112111
def test_hang_gh83386(self):
113112
"""shutdown(wait=False) doesn't hang at exit with running futures.
114113

Lib/test/test_concurrent_futures/test_thread_pool.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ def submit(pool):
7373
)
7474
@support.requires_fork()
7575
@unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork')
76-
@unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: DeprecationWarning not triggered
7776
def test_process_fork_from_a_threadpool(self):
7877
# bpo-43944: clear concurrent.futures.thread._threads_queues after fork,
7978
# otherwise child process will try to join parent thread

Lib/test/test_importlib/test_locks.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class ModuleLockAsRLockTests:
2929
test_timeout = None
3030
# _release_save() unsupported
3131
test_release_save_unacquired = None
32+
# _recursion_count() unsupported
33+
test_recursion_count = None
3234
# lock status in repr unsupported
3335
test_repr = None
3436
test_locked_repr = None
@@ -92,7 +94,8 @@ def f():
9294
b.release()
9395
if ra:
9496
a.release()
95-
lock_tests.Bunch(f, NTHREADS).wait_for_finished()
97+
with lock_tests.Bunch(f, NTHREADS):
98+
pass
9699
self.assertEqual(len(results), NTHREADS)
97100
return results
98101

0 commit comments

Comments
 (0)