mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Update threading.py to version from CPython 3.8
This commit is contained in:
1 file changed
+196
-69
Vendored
+196
-69
@@ -5,7 +5,6 @@ import sys as _sys
|
||||
import _thread
|
||||
|
||||
from time import monotonic as _time
|
||||
from traceback import format_exc as _format_exc
|
||||
from _weakrefset import WeakSet
|
||||
from itertools import islice as _islice, count as _count
|
||||
try:
|
||||
@@ -27,13 +26,20 @@ __all__ = ['get_ident', 'active_count', 'Condition', 'current_thread',
|
||||
'enumerate', 'main_thread', 'TIMEOUT_MAX',
|
||||
'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread',
|
||||
'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError',
|
||||
'setprofile', 'settrace', 'local', 'stack_size']
|
||||
'setprofile', 'settrace', 'local', 'stack_size',
|
||||
'excepthook', 'ExceptHookArgs']
|
||||
|
||||
# Rename some stuff so "from threading import *" is safe
|
||||
_start_new_thread = _thread.start_new_thread
|
||||
_allocate_lock = _thread.allocate_lock
|
||||
_set_sentinel = _thread._set_sentinel
|
||||
get_ident = _thread.get_ident
|
||||
try:
|
||||
get_native_id = _thread.get_native_id
|
||||
_HAVE_THREAD_NATIVE_ID = True
|
||||
__all__.append('get_native_id')
|
||||
except AttributeError:
|
||||
_HAVE_THREAD_NATIVE_ID = False
|
||||
ThreadError = _thread.error
|
||||
try:
|
||||
_CRLock = _thread.RLock
|
||||
@@ -568,8 +574,8 @@ class Barrier:
|
||||
"""Implements a Barrier.
|
||||
|
||||
Useful for synchronizing a fixed number of threads at known synchronization
|
||||
points. Threads block on 'wait()' and are simultaneously once they have all
|
||||
made that call.
|
||||
points. Threads block on 'wait()' and are simultaneously awoken once they
|
||||
have all made that call.
|
||||
|
||||
"""
|
||||
|
||||
@@ -578,7 +584,7 @@ class Barrier:
|
||||
|
||||
'action' is a callable which, when supplied, will be called by one of
|
||||
the threads after they have all entered the barrier and just prior to
|
||||
releasing them all. If a 'timeout' is provided, it is uses as the
|
||||
releasing them all. If a 'timeout' is provided, it is used as the
|
||||
default for all subsequent 'wait()' calls.
|
||||
|
||||
"""
|
||||
@@ -733,6 +739,11 @@ _active_limbo_lock = _allocate_lock()
|
||||
_active = {} # maps thread id to Thread object
|
||||
_limbo = {}
|
||||
_dangling = WeakSet()
|
||||
# Set of Thread._tstate_lock locks of non-daemon threads used by _shutdown()
|
||||
# to wait until all Python thread states get deleted:
|
||||
# see Thread._set_tstate_lock().
|
||||
_shutdown_locks_lock = _allocate_lock()
|
||||
_shutdown_locks = set()
|
||||
|
||||
# Main class for threads
|
||||
|
||||
@@ -746,14 +757,6 @@ class Thread:
|
||||
"""
|
||||
|
||||
_initialized = False
|
||||
# Need to store a reference to sys.exc_info for printing
|
||||
# out exceptions when a thread tries to use a global var. during interp.
|
||||
# shutdown and thus raises an exception about trying to perform some
|
||||
# operation on/with a NoneType
|
||||
_exc_info = _sys.exc_info
|
||||
# Keep sys.exc_clear too to clear the exception just before
|
||||
# allowing .join() to return.
|
||||
#XXX __exc_clear = _sys.exc_clear
|
||||
|
||||
def __init__(self, group=None, target=None, name=None,
|
||||
args=(), kwargs=None, *, daemon=None):
|
||||
@@ -790,13 +793,15 @@ class Thread:
|
||||
else:
|
||||
self._daemonic = current_thread().daemon
|
||||
self._ident = None
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
self._native_id = None
|
||||
self._tstate_lock = None
|
||||
self._started = Event()
|
||||
self._is_stopped = False
|
||||
self._initialized = True
|
||||
# sys.stderr is not stored in the class like
|
||||
# sys.exc_info since it can be changed between instances
|
||||
# Copy of sys.stderr used by self._invoke_excepthook()
|
||||
self._stderr = _sys.stderr
|
||||
self._invoke_excepthook = _make_invoke_excepthook()
|
||||
# For debugging and _after_fork()
|
||||
_dangling.add(self)
|
||||
|
||||
@@ -891,6 +896,10 @@ class Thread:
|
||||
def _set_ident(self):
|
||||
self._ident = get_ident()
|
||||
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
def _set_native_id(self):
|
||||
self._native_id = get_native_id()
|
||||
|
||||
def _set_tstate_lock(self):
|
||||
"""
|
||||
Set a lock object which will be released by the interpreter when
|
||||
@@ -899,10 +908,16 @@ class Thread:
|
||||
self._tstate_lock = _set_sentinel()
|
||||
self._tstate_lock.acquire()
|
||||
|
||||
if not self.daemon:
|
||||
with _shutdown_locks_lock:
|
||||
_shutdown_locks.add(self._tstate_lock)
|
||||
|
||||
def _bootstrap_inner(self):
|
||||
try:
|
||||
self._set_ident()
|
||||
self._set_tstate_lock()
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
self._set_native_id()
|
||||
self._started.set()
|
||||
with _active_limbo_lock:
|
||||
_active[self._ident] = self
|
||||
@@ -915,47 +930,8 @@ class Thread:
|
||||
|
||||
try:
|
||||
self.run()
|
||||
except SystemExit:
|
||||
pass
|
||||
except:
|
||||
# If sys.stderr is no more (most likely from interpreter
|
||||
# shutdown) use self._stderr. Otherwise still use sys (as in
|
||||
# _sys) in case sys.stderr was redefined since the creation of
|
||||
# self.
|
||||
if _sys and _sys.stderr is not None:
|
||||
print("Exception in thread %s:\n%s" %
|
||||
(self.name, _format_exc()), file=_sys.stderr)
|
||||
elif self._stderr is not None:
|
||||
# Do the best job possible w/o a huge amt. of code to
|
||||
# approximate a traceback (code ideas from
|
||||
# Lib/traceback.py)
|
||||
exc_type, exc_value, exc_tb = self._exc_info()
|
||||
try:
|
||||
print((
|
||||
"Exception in thread " + self.name +
|
||||
" (most likely raised during interpreter shutdown):"), file=self._stderr)
|
||||
print((
|
||||
"Traceback (most recent call last):"), file=self._stderr)
|
||||
while exc_tb:
|
||||
print((
|
||||
' File "%s", line %s, in %s' %
|
||||
(exc_tb.tb_frame.f_code.co_filename,
|
||||
exc_tb.tb_lineno,
|
||||
exc_tb.tb_frame.f_code.co_name)), file=self._stderr)
|
||||
exc_tb = exc_tb.tb_next
|
||||
print(("%s: %s" % (exc_type, exc_value)), file=self._stderr)
|
||||
self._stderr.flush()
|
||||
# Make sure that exc_tb gets deleted since it is a memory
|
||||
# hog; deleting everything else is just for thoroughness
|
||||
finally:
|
||||
del exc_type, exc_value, exc_tb
|
||||
finally:
|
||||
# Prevent a race in
|
||||
# test_threading.test_no_refcycle_through_target when
|
||||
# the exception keeps the target alive past when we
|
||||
# assert that it's dead.
|
||||
#XXX self._exc_clear()
|
||||
pass
|
||||
self._invoke_excepthook(self)
|
||||
finally:
|
||||
with _active_limbo_lock:
|
||||
try:
|
||||
@@ -987,6 +963,9 @@ class Thread:
|
||||
assert not lock.locked()
|
||||
self._is_stopped = True
|
||||
self._tstate_lock = None
|
||||
if not self.daemon:
|
||||
with _shutdown_locks_lock:
|
||||
_shutdown_locks.discard(lock)
|
||||
|
||||
def _delete(self):
|
||||
"Remove current thread from the dict of currently running threads."
|
||||
@@ -1007,7 +986,7 @@ class Thread:
|
||||
When the timeout argument is present and not None, it should be a
|
||||
floating point number specifying a timeout for the operation in seconds
|
||||
(or fractions thereof). As join() always returns None, you must call
|
||||
isAlive() after join() to decide whether a timeout happened -- if the
|
||||
is_alive() after join() to decide whether a timeout happened -- if the
|
||||
thread is still alive, the join() call timed out.
|
||||
|
||||
When the timeout argument is not present or None, the operation will
|
||||
@@ -1077,6 +1056,18 @@ class Thread:
|
||||
assert self._initialized, "Thread.__init__() not called"
|
||||
return self._ident
|
||||
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
@property
|
||||
def native_id(self):
|
||||
"""Native integral thread ID of this thread, or None if it has not been started.
|
||||
|
||||
This is a non-negative integer. See the get_native_id() function.
|
||||
This represents the Thread ID as reported by the kernel.
|
||||
|
||||
"""
|
||||
assert self._initialized, "Thread.__init__() not called"
|
||||
return self._native_id
|
||||
|
||||
def is_alive(self):
|
||||
"""Return whether the thread is alive.
|
||||
|
||||
@@ -1091,7 +1082,15 @@ class Thread:
|
||||
self._wait_for_tstate_lock(False)
|
||||
return not self._is_stopped
|
||||
|
||||
isAlive = is_alive
|
||||
def isAlive(self):
|
||||
"""Return whether the thread is alive.
|
||||
|
||||
This method is deprecated, use is_alive() instead.
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn('isAlive() is deprecated, use is_alive() instead',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return self.is_alive()
|
||||
|
||||
@property
|
||||
def daemon(self):
|
||||
@@ -1102,8 +1101,7 @@ class Thread:
|
||||
main thread is not a daemon thread and therefore all threads created in
|
||||
the main thread default to daemon = False.
|
||||
|
||||
The entire Python program exits when no alive non-daemon threads are
|
||||
left.
|
||||
The entire Python program exits when only daemon threads are left.
|
||||
|
||||
"""
|
||||
assert self._initialized, "Thread.__init__() not called"
|
||||
@@ -1129,6 +1127,104 @@ class Thread:
|
||||
def setName(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
try:
|
||||
from _thread import (_excepthook as excepthook,
|
||||
_ExceptHookArgs as ExceptHookArgs)
|
||||
except ImportError:
|
||||
# Simple Python implementation if _thread._excepthook() is not available
|
||||
from traceback import print_exception as _print_exception
|
||||
from collections import namedtuple
|
||||
|
||||
_ExceptHookArgs = namedtuple(
|
||||
'ExceptHookArgs',
|
||||
'exc_type exc_value exc_traceback thread')
|
||||
|
||||
def ExceptHookArgs(args):
|
||||
return _ExceptHookArgs(*args)
|
||||
|
||||
def excepthook(args, /):
|
||||
"""
|
||||
Handle uncaught Thread.run() exception.
|
||||
"""
|
||||
if args.exc_type == SystemExit:
|
||||
# silently ignore SystemExit
|
||||
return
|
||||
|
||||
if _sys is not None and _sys.stderr is not None:
|
||||
stderr = _sys.stderr
|
||||
elif args.thread is not None:
|
||||
stderr = args.thread._stderr
|
||||
if stderr is None:
|
||||
# do nothing if sys.stderr is None and sys.stderr was None
|
||||
# when the thread was created
|
||||
return
|
||||
else:
|
||||
# do nothing if sys.stderr is None and args.thread is None
|
||||
return
|
||||
|
||||
if args.thread is not None:
|
||||
name = args.thread.name
|
||||
else:
|
||||
name = get_ident()
|
||||
print(f"Exception in thread {name}:",
|
||||
file=stderr, flush=True)
|
||||
_print_exception(args.exc_type, args.exc_value, args.exc_traceback,
|
||||
file=stderr)
|
||||
stderr.flush()
|
||||
|
||||
|
||||
def _make_invoke_excepthook():
|
||||
# Create a local namespace to ensure that variables remain alive
|
||||
# when _invoke_excepthook() is called, even if it is called late during
|
||||
# Python shutdown. It is mostly needed for daemon threads.
|
||||
|
||||
old_excepthook = excepthook
|
||||
old_sys_excepthook = _sys.excepthook
|
||||
if old_excepthook is None:
|
||||
raise RuntimeError("threading.excepthook is None")
|
||||
if old_sys_excepthook is None:
|
||||
raise RuntimeError("sys.excepthook is None")
|
||||
|
||||
sys_exc_info = _sys.exc_info
|
||||
local_print = print
|
||||
local_sys = _sys
|
||||
|
||||
def invoke_excepthook(thread):
|
||||
global excepthook
|
||||
try:
|
||||
hook = excepthook
|
||||
if hook is None:
|
||||
hook = old_excepthook
|
||||
|
||||
args = ExceptHookArgs([*sys_exc_info(), thread])
|
||||
|
||||
hook(args)
|
||||
except Exception as exc:
|
||||
exc.__suppress_context__ = True
|
||||
del exc
|
||||
|
||||
if local_sys is not None and local_sys.stderr is not None:
|
||||
stderr = local_sys.stderr
|
||||
else:
|
||||
stderr = thread._stderr
|
||||
|
||||
local_print("Exception in threading.excepthook:",
|
||||
file=stderr, flush=True)
|
||||
|
||||
if local_sys is not None and local_sys.excepthook is not None:
|
||||
sys_excepthook = local_sys.excepthook
|
||||
else:
|
||||
sys_excepthook = old_sys_excepthook
|
||||
|
||||
sys_excepthook(*sys_exc_info())
|
||||
finally:
|
||||
# Break reference cycle (exception stored in a variable)
|
||||
args = None
|
||||
|
||||
return invoke_excepthook
|
||||
|
||||
|
||||
# The timer class was contributed by Itamar Shtull-Trauring
|
||||
|
||||
class Timer(Thread):
|
||||
@@ -1168,6 +1264,8 @@ class _MainThread(Thread):
|
||||
self._set_tstate_lock()
|
||||
self._started.set()
|
||||
self._set_ident()
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
self._set_native_id()
|
||||
with _active_limbo_lock:
|
||||
_active[self._ident] = self
|
||||
|
||||
@@ -1187,6 +1285,8 @@ class _DummyThread(Thread):
|
||||
|
||||
self._started.set()
|
||||
self._set_ident()
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
self._set_native_id()
|
||||
with _active_limbo_lock:
|
||||
_active[self._ident] = self
|
||||
|
||||
@@ -1253,6 +1353,9 @@ from _thread import stack_size
|
||||
_main_thread = _MainThread()
|
||||
|
||||
def _shutdown():
|
||||
"""
|
||||
Wait until the Python thread state of all non-daemon threads get deleted.
|
||||
"""
|
||||
# Obscure: other threads may be waiting to join _main_thread. That's
|
||||
# dubious, but some code does it. We can't wait for C code to release
|
||||
# the main thread's tstate_lock - that won't happen until the interpreter
|
||||
@@ -1261,6 +1364,8 @@ def _shutdown():
|
||||
if _main_thread._is_stopped:
|
||||
# _shutdown() was already called
|
||||
return
|
||||
|
||||
# Main thread
|
||||
tlock = _main_thread._tstate_lock
|
||||
# The main thread isn't finished yet, so its thread state lock can't have
|
||||
# been released.
|
||||
@@ -1268,16 +1373,24 @@ def _shutdown():
|
||||
assert tlock.locked()
|
||||
tlock.release()
|
||||
_main_thread._stop()
|
||||
t = _pickSomeNonDaemonThread()
|
||||
while t:
|
||||
t.join()
|
||||
t = _pickSomeNonDaemonThread()
|
||||
|
||||
def _pickSomeNonDaemonThread():
|
||||
for t in enumerate():
|
||||
if not t.daemon and t.is_alive():
|
||||
return t
|
||||
return None
|
||||
# Join all non-deamon threads
|
||||
while True:
|
||||
with _shutdown_locks_lock:
|
||||
locks = list(_shutdown_locks)
|
||||
_shutdown_locks.clear()
|
||||
|
||||
if not locks:
|
||||
break
|
||||
|
||||
for lock in locks:
|
||||
# mimick Thread.join()
|
||||
lock.acquire()
|
||||
lock.release()
|
||||
|
||||
# new threads can be spawned while we were waiting for the other
|
||||
# threads to complete
|
||||
|
||||
|
||||
def main_thread():
|
||||
"""Return the main thread object.
|
||||
@@ -1303,12 +1416,26 @@ def _after_fork():
|
||||
# Reset _active_limbo_lock, in case we forked while the lock was held
|
||||
# by another (non-forked) thread. http://bugs.python.org/issue874900
|
||||
global _active_limbo_lock, _main_thread
|
||||
global _shutdown_locks_lock, _shutdown_locks
|
||||
_active_limbo_lock = _allocate_lock()
|
||||
|
||||
# fork() only copied the current thread; clear references to others.
|
||||
new_active = {}
|
||||
current = current_thread()
|
||||
|
||||
try:
|
||||
current = _active[get_ident()]
|
||||
except KeyError:
|
||||
# fork() was called in a thread which was not spawned
|
||||
# by threading.Thread. For example, a thread spawned
|
||||
# by thread.start_new_thread().
|
||||
current = _MainThread()
|
||||
|
||||
_main_thread = current
|
||||
|
||||
# reset _shutdown() locks: threads re-register their _tstate_lock below
|
||||
_shutdown_locks_lock = _allocate_lock()
|
||||
_shutdown_locks = set()
|
||||
|
||||
with _active_limbo_lock:
|
||||
# Dangling thread instances must still have their locks reset,
|
||||
# because someone may join() them.
|
||||
|
||||
Reference in new issue
Block a user