X Tutup
Skip to content

Commit ca76cb7

Browse files
authored
thread_inherit_context, upgrade threading & contextvar (RustPython#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 <>
1 parent 11c9b0e commit ca76cb7

File tree

6 files changed

+98
-18
lines changed

6 files changed

+98
-18
lines changed

Lib/contextvars.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import _collections_abc
12
from _contextvars import Context, ContextVar, Token, copy_context
23

34

45
__all__ = ('Context', 'ContextVar', 'Token', 'copy_context')
6+
7+
8+
_collections_abc.Mapping.register(Context)

Lib/test/test_sys.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,10 +889,11 @@ def test_sys_flags(self):
889889
"dont_write_bytecode", "no_user_site", "no_site",
890890
"ignore_environment", "verbose", "bytes_warning", "quiet",
891891
"hash_randomization", "isolated", "dev_mode", "utf8_mode",
892-
"warn_default_encoding", "safe_path", "int_max_str_digits")
892+
"warn_default_encoding", "safe_path", "int_max_str_digits",
893+
"thread_inherit_context")
893894
for attr in attrs:
894895
self.assertTrue(hasattr(sys.flags, attr), attr)
895-
attr_type = bool if attr in ("dev_mode", "safe_path") else int
896+
attr_type = bool if attr in ("dev_mode", "safe_path", "thread_inherit_context") else int
896897
self.assertEqual(type(getattr(sys.flags, attr)), attr_type, attr)
897898
self.assertTrue(repr(sys.flags))
898899
self.assertEqual(len(sys.flags), len(attrs))

Lib/threading.py

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os as _os
44
import sys as _sys
55
import _thread
6-
import warnings
6+
import _contextvars
77

88
from time import monotonic as _time
99
from _weakrefset import WeakSet
@@ -48,6 +48,10 @@
4848
__all__.append('get_native_id')
4949
except AttributeError:
5050
_HAVE_THREAD_NATIVE_ID = False
51+
try:
52+
_set_name = _thread.set_name
53+
except AttributeError:
54+
_set_name = None
5155
ThreadError = _thread.error
5256
try:
5357
_CRLock = _thread.RLock
@@ -129,6 +133,7 @@ def RLock(*args, **kwargs):
129133
130134
"""
131135
if args or kwargs:
136+
import warnings
132137
warnings.warn(
133138
'Passing arguments to RLock is deprecated and will be removed in 3.15',
134139
DeprecationWarning,
@@ -160,7 +165,7 @@ def __repr__(self):
160165
except KeyError:
161166
pass
162167
return "<%s %s.%s object owner=%r count=%d at %s>" % (
163-
"locked" if self._block.locked() else "unlocked",
168+
"locked" if self.locked() else "unlocked",
164169
self.__class__.__module__,
165170
self.__class__.__qualname__,
166171
owner,
@@ -237,6 +242,10 @@ def release(self):
237242
def __exit__(self, t, v, tb):
238243
self.release()
239244

245+
def locked(self):
246+
"""Return whether this object is locked."""
247+
return self._block.locked()
248+
240249
# Internal methods used by condition variables
241250

242251
def _acquire_restore(self, state):
@@ -282,9 +291,10 @@ def __init__(self, lock=None):
282291
if lock is None:
283292
lock = RLock()
284293
self._lock = lock
285-
# Export the lock's acquire() and release() methods
294+
# Export the lock's acquire(), release(), and locked() methods
286295
self.acquire = lock.acquire
287296
self.release = lock.release
297+
self.locked = lock.locked
288298
# If the lock defines _release_save() and/or _acquire_restore(),
289299
# these override the default implementations (which just call
290300
# release() and acquire() on the lock). Ditto for _is_owned().
@@ -868,7 +878,7 @@ class Thread:
868878
_initialized = False
869879

870880
def __init__(self, group=None, target=None, name=None,
871-
args=(), kwargs=None, *, daemon=None):
881+
args=(), kwargs=None, *, daemon=None, context=None):
872882
"""This constructor should always be called with keyword arguments. Arguments are:
873883
874884
*group* should be None; reserved for future extension when a ThreadGroup
@@ -885,6 +895,14 @@ class is implemented.
885895
*kwargs* is a dictionary of keyword arguments for the target
886896
invocation. Defaults to {}.
887897
898+
*context* is the contextvars.Context value to use for the thread.
899+
The default value is None, which means to check
900+
sys.flags.thread_inherit_context. If that flag is true, use a copy
901+
of the context of the caller. If false, use an empty context. To
902+
explicitly start with an empty context, pass a new instance of
903+
contextvars.Context(). To explicitly start with a copy of the current
904+
context, pass the value from contextvars.copy_context().
905+
888906
If a subclass overrides the constructor, it must make sure to invoke
889907
the base class constructor (Thread.__init__()) before doing anything
890908
else to the thread.
@@ -914,10 +932,11 @@ class is implemented.
914932
self._daemonic = daemon
915933
else:
916934
self._daemonic = current_thread().daemon
935+
self._context = context
917936
self._ident = None
918937
if _HAVE_THREAD_NATIVE_ID:
919938
self._native_id = None
920-
self._handle = _ThreadHandle()
939+
self._os_thread_handle = _ThreadHandle()
921940
self._started = Event()
922941
self._initialized = True
923942
# Copy of sys.stderr used by self._invoke_excepthook()
@@ -932,7 +951,7 @@ def _after_fork(self, new_ident=None):
932951
if new_ident is not None:
933952
# This thread is alive.
934953
self._ident = new_ident
935-
assert self._handle.ident == new_ident
954+
assert self._os_thread_handle.ident == new_ident
936955
if _HAVE_THREAD_NATIVE_ID:
937956
self._set_native_id()
938957
else:
@@ -945,7 +964,7 @@ def __repr__(self):
945964
status = "initial"
946965
if self._started.is_set():
947966
status = "started"
948-
if self._handle.is_done():
967+
if self._os_thread_handle.is_done():
949968
status = "stopped"
950969
if self._daemonic:
951970
status += " daemon"
@@ -971,9 +990,19 @@ def start(self):
971990

972991
with _active_limbo_lock:
973992
_limbo[self] = self
993+
994+
if self._context is None:
995+
# No context provided
996+
if _sys.flags.thread_inherit_context:
997+
# start with a copy of the context of the caller
998+
self._context = _contextvars.copy_context()
999+
else:
1000+
# start with an empty context
1001+
self._context = _contextvars.Context()
1002+
9741003
try:
9751004
# Start joinable thread
976-
_start_joinable_thread(self._bootstrap, handle=self._handle,
1005+
_start_joinable_thread(self._bootstrap, handle=self._os_thread_handle,
9771006
daemon=self.daemon)
9781007
except Exception:
9791008
with _active_limbo_lock:
@@ -1025,11 +1054,20 @@ def _set_ident(self):
10251054
def _set_native_id(self):
10261055
self._native_id = get_native_id()
10271056

1057+
def _set_os_name(self):
1058+
if _set_name is None or not self._name:
1059+
return
1060+
try:
1061+
_set_name(self._name)
1062+
except OSError:
1063+
pass
1064+
10281065
def _bootstrap_inner(self):
10291066
try:
10301067
self._set_ident()
10311068
if _HAVE_THREAD_NATIVE_ID:
10321069
self._set_native_id()
1070+
self._set_os_name()
10331071
self._started.set()
10341072
with _active_limbo_lock:
10351073
_active[self._ident] = self
@@ -1041,7 +1079,7 @@ def _bootstrap_inner(self):
10411079
_sys.setprofile(_profile_hook)
10421080

10431081
try:
1044-
self.run()
1082+
self._context.run(self.run)
10451083
except:
10461084
self._invoke_excepthook(self)
10471085
finally:
@@ -1092,7 +1130,7 @@ def join(self, timeout=None):
10921130
if timeout is not None:
10931131
timeout = max(timeout, 0)
10941132

1095-
self._handle.join(timeout)
1133+
self._os_thread_handle.join(timeout)
10961134

10971135
@property
10981136
def name(self):
@@ -1109,6 +1147,8 @@ def name(self):
11091147
def name(self, name):
11101148
assert self._initialized, "Thread.__init__() not called"
11111149
self._name = str(name)
1150+
if get_ident() == self._ident:
1151+
self._set_os_name()
11121152

11131153
@property
11141154
def ident(self):
@@ -1143,7 +1183,7 @@ def is_alive(self):
11431183
11441184
"""
11451185
assert self._initialized, "Thread.__init__() not called"
1146-
return self._started.is_set() and not self._handle.is_done()
1186+
return self._started.is_set() and not self._os_thread_handle.is_done()
11471187

11481188
@property
11491189
def daemon(self):
@@ -1354,7 +1394,7 @@ def __init__(self):
13541394
Thread.__init__(self, name="MainThread", daemon=False)
13551395
self._started.set()
13561396
self._ident = _get_main_thread_ident()
1357-
self._handle = _make_thread_handle(self._ident)
1397+
self._os_thread_handle = _make_thread_handle(self._ident)
13581398
if _HAVE_THREAD_NATIVE_ID:
13591399
self._set_native_id()
13601400
with _active_limbo_lock:
@@ -1402,15 +1442,15 @@ def __init__(self):
14021442
daemon=_daemon_threads_allowed())
14031443
self._started.set()
14041444
self._set_ident()
1405-
self._handle = _make_thread_handle(self._ident)
1445+
self._os_thread_handle = _make_thread_handle(self._ident)
14061446
if _HAVE_THREAD_NATIVE_ID:
14071447
self._set_native_id()
14081448
with _active_limbo_lock:
14091449
_active[self._ident] = self
14101450
_DeleteDummyThreadOnDel(self)
14111451

14121452
def is_alive(self):
1413-
if not self._handle.is_done() and self._started.is_set():
1453+
if not self._os_thread_handle.is_done() and self._started.is_set():
14141454
return True
14151455
raise RuntimeError("thread is not alive")
14161456

@@ -1524,7 +1564,7 @@ def _shutdown():
15241564
# dubious, but some code does it. We can't wait for it to be marked as done
15251565
# normally - that won't happen until the interpreter is nearly dead. So
15261566
# mark it done here.
1527-
if _main_thread._handle.is_done() and _is_main_interpreter():
1567+
if _main_thread._os_thread_handle.is_done() and _is_main_interpreter():
15281568
# _shutdown() was already called
15291569
return
15301570

@@ -1537,7 +1577,7 @@ def _shutdown():
15371577
atexit_call()
15381578

15391579
if _is_main_interpreter():
1540-
_main_thread._handle._set_done()
1580+
_main_thread._os_thread_handle._set_done()
15411581

15421582
# Wait for all non-daemon threads to exit.
15431583
_thread_shutdown()

crates/vm/src/stdlib/sys.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,8 @@ mod sys {
13011301
safe_path: bool,
13021302
/// -X warn_default_encoding, PYTHONWARNDEFAULTENCODING
13031303
warn_default_encoding: u8,
1304+
/// -X thread_inherit_context, whether new threads inherit context from parent
1305+
thread_inherit_context: bool,
13041306
}
13051307

13061308
impl FlagsData {
@@ -1324,6 +1326,7 @@ mod sys {
13241326
int_max_str_digits: settings.int_max_str_digits,
13251327
safe_path: settings.safe_path,
13261328
warn_default_encoding: settings.warn_default_encoding as u8,
1329+
thread_inherit_context: settings.thread_inherit_context,
13271330
}
13281331
}
13291332
}

crates/vm/src/vm/setting.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ pub struct Settings {
8888
/// -X warn_default_encoding, PYTHONWARNDEFAULTENCODING
8989
pub warn_default_encoding: bool,
9090

91+
/// -X thread_inherit_context, whether new threads inherit context from parent
92+
pub thread_inherit_context: bool,
93+
9194
/// -i
9295
pub inspect: bool,
9396

@@ -190,6 +193,7 @@ impl Default for Settings {
190193
isolated: false,
191194
dev_mode: false,
192195
warn_default_encoding: false,
196+
thread_inherit_context: false,
193197
warnoptions: vec![],
194198
path_list: vec![],
195199
argv: vec![],

src/settings.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,20 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
285285
}
286286
};
287287
}
288+
"thread_inherit_context" => {
289+
settings.thread_inherit_context = match value {
290+
Some("1") => true,
291+
Some("0") => false,
292+
_ => {
293+
error!(
294+
"Fatal Python error: config_init_thread_inherit_context: \
295+
-X thread_inherit_context=n: n is missing or invalid\n\
296+
Python runtime state: preinitialized"
297+
);
298+
std::process::exit(1);
299+
}
300+
};
301+
}
288302
_ => {}
289303
}
290304
(name, value.map(str::to_owned))
@@ -297,6 +311,20 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
297311
if env_bool("PYTHONNODEBUGRANGES") {
298312
settings.code_debug_ranges = false;
299313
}
314+
if let Some(val) = get_env("PYTHON_THREAD_INHERIT_CONTEXT") {
315+
settings.thread_inherit_context = match val.to_str() {
316+
Some("1") => true,
317+
Some("0") => false,
318+
_ => {
319+
error!(
320+
"Fatal Python error: config_init_thread_inherit_context: \
321+
PYTHON_THREAD_INHERIT_CONTEXT=N: N is missing or invalid\n\
322+
Python runtime state: preinitialized"
323+
);
324+
std::process::exit(1);
325+
}
326+
};
327+
}
300328

301329
// Parse PYTHONIOENCODING=encoding[:errors]
302330
if let Some(val) = get_env("PYTHONIOENCODING")

0 commit comments

Comments
 (0)
X Tutup