Skip to content

Commit cf7fe45

Browse files
committed
impl more threading
1 parent b2efb64 commit cf7fe45

7 files changed

Lines changed: 665 additions & 25 deletions

File tree

‎Lib/test/test_threading.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,8 @@ def import_threading():
11641164
self.assertEqual(out, b'')
11651165
self.assertEqual(err, b'')
11661166

1167+
# TODO: RUSTPYTHON - __del__ not called during interpreter finalization (no cyclic GC)
1168+
@unittest.expectedFailure
11671169
def test_start_new_thread_at_finalization(self):
11681170
code = """if 1:
11691171
import _thread
@@ -1340,7 +1342,6 @@ def joiningfunc(mainthread):
13401342
data = out.decode().replace('\r', '')
13411343
self.assertEqual(data, "end of main\nend of thread\n")
13421344

1343-
@unittest.expectedFailure # TODO: RUSTPYTHON
13441345
def test_1_join_on_shutdown(self):
13451346
# The usual case: on exit, wait for a non-daemon thread
13461347
script = """if 1:
@@ -1353,7 +1354,6 @@ def test_1_join_on_shutdown(self):
13531354
"""
13541355
self._run_and_join(script)
13551356

1356-
@unittest.expectedFailure # TODO: RUSTPYTHON; need to fix test_1_join_on_shutdown then this might work
13571357
@skip_unless_reliable_fork
13581358
def test_2_join_in_forked_process(self):
13591359
# Like the test above, but from a forked interpreter
@@ -2271,7 +2271,6 @@ def worker(started, cont, interrupted):
22712271

22722272
class AtexitTests(unittest.TestCase):
22732273

2274-
@unittest.expectedFailure # TODO: RUSTPYTHON
22752274
def test_atexit_output(self):
22762275
rc, out, err = assert_python_ok("-c", """if True:
22772276
import threading
@@ -2300,7 +2299,6 @@ def test_atexit_called_once(self):
23002299

23012300
self.assertFalse(err)
23022301

2303-
@unittest.expectedFailure # TODO: RUSTPYTHON
23042302
def test_atexit_after_shutdown(self):
23052303
# The only way to do this is by registering an atexit within
23062304
# an atexit, which is intended to raise an exception.

‎crates/vm/src/stdlib/posix.rs‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,11 @@ pub mod module {
661661
}
662662

663663
fn py_os_after_fork_child(vm: &VirtualMachine) {
664+
// Mark all other threads as done before running Python callbacks
665+
// See _PyThread_AfterFork behavior
666+
#[cfg(feature = "threading")]
667+
crate::stdlib::thread::after_fork_child();
668+
664669
let after_forkers_child: Vec<PyObjectRef> = vm.state.after_forkers_child.lock().clone();
665670
run_at_forkers(after_forkers_child, false, vm);
666671
}
@@ -670,8 +675,59 @@ pub mod module {
670675
run_at_forkers(after_forkers_parent, false, vm);
671676
}
672677

678+
/// Warn if forking from a multi-threaded process
679+
fn warn_if_multi_threaded(name: &str, vm: &VirtualMachine) {
680+
// Check threading._active and threading._limbo to count threads
681+
let threading = match vm.import("threading", 0) {
682+
Ok(m) => m,
683+
Err(_) => return,
684+
};
685+
let active = threading.get_attr("_active", vm).ok();
686+
let limbo = threading.get_attr("_limbo", vm).ok();
687+
688+
let count_dict = |obj: Option<crate::PyObjectRef>| -> usize {
689+
obj.and_then(|o| o.length_opt(vm))
690+
.and_then(|r| r.ok())
691+
.unwrap_or(0)
692+
};
693+
694+
let num_threads = count_dict(active) + count_dict(limbo);
695+
if num_threads > 1 {
696+
// Use Python warnings module to ensure filters are applied correctly
697+
let Ok(warnings) = vm.import("warnings", 0) else {
698+
return;
699+
};
700+
let Ok(warn_fn) = warnings.get_attr("warn", vm) else {
701+
return;
702+
};
703+
704+
let pid = unsafe { libc::getpid() };
705+
let msg = format!(
706+
"This process (pid={}) is multi-threaded, use of {}() may lead to deadlocks in the child.",
707+
pid, name
708+
);
709+
710+
// Call warnings.warn(message, DeprecationWarning, stacklevel=2)
711+
// stacklevel=2 to point to the caller of fork()
712+
let args = crate::function::FuncArgs::new(
713+
vec![
714+
vm.ctx.new_str(msg).into(),
715+
vm.ctx.exceptions.deprecation_warning.as_object().to_owned(),
716+
],
717+
crate::function::KwArgs::new(
718+
[("stacklevel".to_owned(), vm.ctx.new_int(2).into())]
719+
.into_iter()
720+
.collect(),
721+
),
722+
);
723+
let _ = warn_fn.call(args, vm);
724+
}
725+
}
726+
673727
#[pyfunction]
674728
fn fork(vm: &VirtualMachine) -> i32 {
729+
warn_if_multi_threaded("fork", vm);
730+
675731
let pid: i32;
676732
py_os_before_fork(vm);
677733
unsafe {

‎crates/vm/src/stdlib/sys.rs‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,22 @@ mod sys {
11071107
update_use_tracing(vm);
11081108
}
11091109

1110+
#[pyfunction]
1111+
fn _settraceallthreads(tracefunc: PyObjectRef, vm: &VirtualMachine) {
1112+
let func = (!vm.is_none(&tracefunc)).then(|| tracefunc.clone());
1113+
*vm.state.global_trace_func.lock() = func;
1114+
vm.trace_func.replace(tracefunc);
1115+
update_use_tracing(vm);
1116+
}
1117+
1118+
#[pyfunction]
1119+
fn _setprofileallthreads(profilefunc: PyObjectRef, vm: &VirtualMachine) {
1120+
let func = (!vm.is_none(&profilefunc)).then(|| profilefunc.clone());
1121+
*vm.state.global_profile_func.lock() = func;
1122+
vm.profile_func.replace(profilefunc);
1123+
update_use_tracing(vm);
1124+
}
1125+
11101126
#[cfg(feature = "threading")]
11111127
#[pyattr]
11121128
fn thread_info(vm: &VirtualMachine) -> PyTupleRef {

0 commit comments

Comments
 (0)