mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
thread_inherit_context, upgrade threading & contextvar (#6727)
* thread_inherit_context * Update contextvar, threading from CPython 3.14.2 * partially patch test_sys to 3.14.2 --------- Co-authored-by: CPython Devleopers <>
This commit is contained in:
1 parent
11c9b0e783
commit
ca76cb7bb0
6 files changed
+98
-18
No files matched your search
Vendored
+4
@@ -1,4 +1,8 @@
|
||||
import _collections_abc
|
||||
from _contextvars import Context, ContextVar, Token, copy_context
|
||||
|
||||
|
||||
__all__ = ('Context', 'ContextVar', 'Token', 'copy_context')
|
||||
|
||||
|
||||
_collections_abc.Mapping.register(Context)
|
||||
Vendored
+3
-2
@@ -889,10 +889,11 @@ class SysModuleTest(unittest.TestCase):
|
||||
"dont_write_bytecode", "no_user_site", "no_site",
|
||||
"ignore_environment", "verbose", "bytes_warning", "quiet",
|
||||
"hash_randomization", "isolated", "dev_mode", "utf8_mode",
|
||||
"warn_default_encoding", "safe_path", "int_max_str_digits")
|
||||
"warn_default_encoding", "safe_path", "int_max_str_digits",
|
||||
"thread_inherit_context")
|
||||
for attr in attrs:
|
||||
self.assertTrue(hasattr(sys.flags, attr), attr)
|
||||
attr_type = bool if attr in ("dev_mode", "safe_path") else int
|
||||
attr_type = bool if attr in ("dev_mode", "safe_path", "thread_inherit_context") else int
|
||||
self.assertEqual(type(getattr(sys.flags, attr)), attr_type, attr)
|
||||
self.assertTrue(repr(sys.flags))
|
||||
self.assertEqual(len(sys.flags), len(attrs))
|
||||
|
||||
Vendored
+56
-16
@@ -3,7 +3,7 @@
|
||||
import os as _os
|
||||
import sys as _sys
|
||||
import _thread
|
||||
import warnings
|
||||
import _contextvars
|
||||
|
||||
from time import monotonic as _time
|
||||
from _weakrefset import WeakSet
|
||||
@@ -48,6 +48,10 @@ try:
|
||||
__all__.append('get_native_id')
|
||||
except AttributeError:
|
||||
_HAVE_THREAD_NATIVE_ID = False
|
||||
try:
|
||||
_set_name = _thread.set_name
|
||||
except AttributeError:
|
||||
_set_name = None
|
||||
ThreadError = _thread.error
|
||||
try:
|
||||
_CRLock = _thread.RLock
|
||||
@@ -129,6 +133,7 @@ def RLock(*args, **kwargs):
|
||||
|
||||
"""
|
||||
if args or kwargs:
|
||||
import warnings
|
||||
warnings.warn(
|
||||
'Passing arguments to RLock is deprecated and will be removed in 3.15',
|
||||
DeprecationWarning,
|
||||
@@ -160,7 +165,7 @@ class _RLock:
|
||||
except KeyError:
|
||||
pass
|
||||
return "<%s %s.%s object owner=%r count=%d at %s>" % (
|
||||
"locked" if self._block.locked() else "unlocked",
|
||||
"locked" if self.locked() else "unlocked",
|
||||
self.__class__.__module__,
|
||||
self.__class__.__qualname__,
|
||||
owner,
|
||||
@@ -237,6 +242,10 @@ class _RLock:
|
||||
def __exit__(self, t, v, tb):
|
||||
self.release()
|
||||
|
||||
def locked(self):
|
||||
"""Return whether this object is locked."""
|
||||
return self._block.locked()
|
||||
|
||||
# Internal methods used by condition variables
|
||||
|
||||
def _acquire_restore(self, state):
|
||||
@@ -282,9 +291,10 @@ class Condition:
|
||||
if lock is None:
|
||||
lock = RLock()
|
||||
self._lock = lock
|
||||
# Export the lock's acquire() and release() methods
|
||||
# Export the lock's acquire(), release(), and locked() methods
|
||||
self.acquire = lock.acquire
|
||||
self.release = lock.release
|
||||
self.locked = lock.locked
|
||||
# If the lock defines _release_save() and/or _acquire_restore(),
|
||||
# these override the default implementations (which just call
|
||||
# release() and acquire() on the lock). Ditto for _is_owned().
|
||||
@@ -868,7 +878,7 @@ class Thread:
|
||||
_initialized = False
|
||||
|
||||
def __init__(self, group=None, target=None, name=None,
|
||||
args=(), kwargs=None, *, daemon=None):
|
||||
args=(), kwargs=None, *, daemon=None, context=None):
|
||||
"""This constructor should always be called with keyword arguments. Arguments are:
|
||||
|
||||
*group* should be None; reserved for future extension when a ThreadGroup
|
||||
@@ -885,6 +895,14 @@ class Thread:
|
||||
*kwargs* is a dictionary of keyword arguments for the target
|
||||
invocation. Defaults to {}.
|
||||
|
||||
*context* is the contextvars.Context value to use for the thread.
|
||||
The default value is None, which means to check
|
||||
sys.flags.thread_inherit_context. If that flag is true, use a copy
|
||||
of the context of the caller. If false, use an empty context. To
|
||||
explicitly start with an empty context, pass a new instance of
|
||||
contextvars.Context(). To explicitly start with a copy of the current
|
||||
context, pass the value from contextvars.copy_context().
|
||||
|
||||
If a subclass overrides the constructor, it must make sure to invoke
|
||||
the base class constructor (Thread.__init__()) before doing anything
|
||||
else to the thread.
|
||||
@@ -914,10 +932,11 @@ class Thread:
|
||||
self._daemonic = daemon
|
||||
else:
|
||||
self._daemonic = current_thread().daemon
|
||||
self._context = context
|
||||
self._ident = None
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
self._native_id = None
|
||||
self._handle = _ThreadHandle()
|
||||
self._os_thread_handle = _ThreadHandle()
|
||||
self._started = Event()
|
||||
self._initialized = True
|
||||
# Copy of sys.stderr used by self._invoke_excepthook()
|
||||
@@ -932,7 +951,7 @@ class Thread:
|
||||
if new_ident is not None:
|
||||
# This thread is alive.
|
||||
self._ident = new_ident
|
||||
assert self._handle.ident == new_ident
|
||||
assert self._os_thread_handle.ident == new_ident
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
self._set_native_id()
|
||||
else:
|
||||
@@ -945,7 +964,7 @@ class Thread:
|
||||
status = "initial"
|
||||
if self._started.is_set():
|
||||
status = "started"
|
||||
if self._handle.is_done():
|
||||
if self._os_thread_handle.is_done():
|
||||
status = "stopped"
|
||||
if self._daemonic:
|
||||
status += " daemon"
|
||||
@@ -971,9 +990,19 @@ class Thread:
|
||||
|
||||
with _active_limbo_lock:
|
||||
_limbo[self] = self
|
||||
|
||||
if self._context is None:
|
||||
# No context provided
|
||||
if _sys.flags.thread_inherit_context:
|
||||
# start with a copy of the context of the caller
|
||||
self._context = _contextvars.copy_context()
|
||||
else:
|
||||
# start with an empty context
|
||||
self._context = _contextvars.Context()
|
||||
|
||||
try:
|
||||
# Start joinable thread
|
||||
_start_joinable_thread(self._bootstrap, handle=self._handle,
|
||||
_start_joinable_thread(self._bootstrap, handle=self._os_thread_handle,
|
||||
daemon=self.daemon)
|
||||
except Exception:
|
||||
with _active_limbo_lock:
|
||||
@@ -1025,11 +1054,20 @@ class Thread:
|
||||
def _set_native_id(self):
|
||||
self._native_id = get_native_id()
|
||||
|
||||
def _set_os_name(self):
|
||||
if _set_name is None or not self._name:
|
||||
return
|
||||
try:
|
||||
_set_name(self._name)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _bootstrap_inner(self):
|
||||
try:
|
||||
self._set_ident()
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
self._set_native_id()
|
||||
self._set_os_name()
|
||||
self._started.set()
|
||||
with _active_limbo_lock:
|
||||
_active[self._ident] = self
|
||||
@@ -1041,7 +1079,7 @@ class Thread:
|
||||
_sys.setprofile(_profile_hook)
|
||||
|
||||
try:
|
||||
self.run()
|
||||
self._context.run(self.run)
|
||||
except:
|
||||
self._invoke_excepthook(self)
|
||||
finally:
|
||||
@@ -1092,7 +1130,7 @@ class Thread:
|
||||
if timeout is not None:
|
||||
timeout = max(timeout, 0)
|
||||
|
||||
self._handle.join(timeout)
|
||||
self._os_thread_handle.join(timeout)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -1109,6 +1147,8 @@ class Thread:
|
||||
def name(self, name):
|
||||
assert self._initialized, "Thread.__init__() not called"
|
||||
self._name = str(name)
|
||||
if get_ident() == self._ident:
|
||||
self._set_os_name()
|
||||
|
||||
@property
|
||||
def ident(self):
|
||||
@@ -1143,7 +1183,7 @@ class Thread:
|
||||
|
||||
"""
|
||||
assert self._initialized, "Thread.__init__() not called"
|
||||
return self._started.is_set() and not self._handle.is_done()
|
||||
return self._started.is_set() and not self._os_thread_handle.is_done()
|
||||
|
||||
@property
|
||||
def daemon(self):
|
||||
@@ -1354,7 +1394,7 @@ class _MainThread(Thread):
|
||||
Thread.__init__(self, name="MainThread", daemon=False)
|
||||
self._started.set()
|
||||
self._ident = _get_main_thread_ident()
|
||||
self._handle = _make_thread_handle(self._ident)
|
||||
self._os_thread_handle = _make_thread_handle(self._ident)
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
self._set_native_id()
|
||||
with _active_limbo_lock:
|
||||
@@ -1402,7 +1442,7 @@ class _DummyThread(Thread):
|
||||
daemon=_daemon_threads_allowed())
|
||||
self._started.set()
|
||||
self._set_ident()
|
||||
self._handle = _make_thread_handle(self._ident)
|
||||
self._os_thread_handle = _make_thread_handle(self._ident)
|
||||
if _HAVE_THREAD_NATIVE_ID:
|
||||
self._set_native_id()
|
||||
with _active_limbo_lock:
|
||||
@@ -1410,7 +1450,7 @@ class _DummyThread(Thread):
|
||||
_DeleteDummyThreadOnDel(self)
|
||||
|
||||
def is_alive(self):
|
||||
if not self._handle.is_done() and self._started.is_set():
|
||||
if not self._os_thread_handle.is_done() and self._started.is_set():
|
||||
return True
|
||||
raise RuntimeError("thread is not alive")
|
||||
|
||||
@@ -1524,7 +1564,7 @@ def _shutdown():
|
||||
# dubious, but some code does it. We can't wait for it to be marked as done
|
||||
# normally - that won't happen until the interpreter is nearly dead. So
|
||||
# mark it done here.
|
||||
if _main_thread._handle.is_done() and _is_main_interpreter():
|
||||
if _main_thread._os_thread_handle.is_done() and _is_main_interpreter():
|
||||
# _shutdown() was already called
|
||||
return
|
||||
|
||||
@@ -1537,7 +1577,7 @@ def _shutdown():
|
||||
atexit_call()
|
||||
|
||||
if _is_main_interpreter():
|
||||
_main_thread._handle._set_done()
|
||||
_main_thread._os_thread_handle._set_done()
|
||||
|
||||
# Wait for all non-daemon threads to exit.
|
||||
_thread_shutdown()
|
||||
|
||||
@@ -1301,6 +1301,8 @@ mod sys {
|
||||
safe_path: bool,
|
||||
/// -X warn_default_encoding, PYTHONWARNDEFAULTENCODING
|
||||
warn_default_encoding: u8,
|
||||
/// -X thread_inherit_context, whether new threads inherit context from parent
|
||||
thread_inherit_context: bool,
|
||||
}
|
||||
|
||||
impl FlagsData {
|
||||
@@ -1324,6 +1326,7 @@ mod sys {
|
||||
int_max_str_digits: settings.int_max_str_digits,
|
||||
safe_path: settings.safe_path,
|
||||
warn_default_encoding: settings.warn_default_encoding as u8,
|
||||
thread_inherit_context: settings.thread_inherit_context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,9 @@ pub struct Settings {
|
||||
/// -X warn_default_encoding, PYTHONWARNDEFAULTENCODING
|
||||
pub warn_default_encoding: bool,
|
||||
|
||||
/// -X thread_inherit_context, whether new threads inherit context from parent
|
||||
pub thread_inherit_context: bool,
|
||||
|
||||
/// -i
|
||||
pub inspect: bool,
|
||||
|
||||
@@ -190,6 +193,7 @@ impl Default for Settings {
|
||||
isolated: false,
|
||||
dev_mode: false,
|
||||
warn_default_encoding: false,
|
||||
thread_inherit_context: false,
|
||||
warnoptions: vec![],
|
||||
path_list: vec![],
|
||||
argv: vec![],
|
||||
|
||||
@@ -285,6 +285,20 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
|
||||
}
|
||||
};
|
||||
}
|
||||
"thread_inherit_context" => {
|
||||
settings.thread_inherit_context = match value {
|
||||
Some("1") => true,
|
||||
Some("0") => false,
|
||||
_ => {
|
||||
error!(
|
||||
"Fatal Python error: config_init_thread_inherit_context: \
|
||||
-X thread_inherit_context=n: n is missing or invalid\n\
|
||||
Python runtime state: preinitialized"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
(name, value.map(str::to_owned))
|
||||
@@ -297,6 +311,20 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
|
||||
if env_bool("PYTHONNODEBUGRANGES") {
|
||||
settings.code_debug_ranges = false;
|
||||
}
|
||||
if let Some(val) = get_env("PYTHON_THREAD_INHERIT_CONTEXT") {
|
||||
settings.thread_inherit_context = match val.to_str() {
|
||||
Some("1") => true,
|
||||
Some("0") => false,
|
||||
_ => {
|
||||
error!(
|
||||
"Fatal Python error: config_init_thread_inherit_context: \
|
||||
PYTHON_THREAD_INHERIT_CONTEXT=N: N is missing or invalid\n\
|
||||
Python runtime state: preinitialized"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Parse PYTHONIOENCODING=encoding[:errors]
|
||||
if let Some(val) = get_env("PYTHONIOENCODING")
|
||||
|
||||
Reference in new issue
Block a user