Skip to content

Commit ce3d5ec

Browse files
committed
_io, _winapi: detach on the remaining stopped-holdable lock takes
`TextIOWrapper.__repr__` took `data` directly while every other method takes it through `lock_opt`, which detaches. `Overlapped` holds `inner` across the `allow_threads` in `GetOverlappedResult`, and all four of its takes were direct. A thread stopped by stop-the-world can be holding either mutex, so taking one while attached left the blocked thread with no safepoint to reach. Assisted-by: Claude
1 parent ed68013 commit ce3d5ec

2 files changed

Lines changed: 19 additions & 7 deletions

File tree

crates/vm/src/stdlib/_io.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4078,7 +4078,10 @@ mod _io {
40784078
vm.new_runtime_error(format!("reentrant call inside {type_name}.__repr__"))
40794079
);
40804080
};
4081-
let Some(data) = zelf.data.lock() else {
4081+
// Detach while blocked, like `lock_opt`: another thread can be
4082+
// stopped holding this mutex, and blocking on it while attached
4083+
// would leave no safepoint for that stop to complete at.
4084+
let Some(data) = zelf.data.lock_wrapped(|do_lock| vm.allow_threads(do_lock)) else {
40824085
// Reentrant call
40834086
return Ok(vm.ctx.new_str(Wtf8Buf::from(format!("<{type_name}>"))));
40844087
};

crates/vm/src/stdlib/_winapi.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ mod _winapi {
88
use crate::{
99
Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine,
1010
builtins::PyStrRef,
11-
common::lock::PyMutex,
11+
common::lock::{PyMutex, PyMutexGuard},
1212
convert::ToPyException,
1313
function::{ArgMapping, ArgSequence, OptionalArg},
1414
types::Constructor,
@@ -566,17 +566,26 @@ mod _winapi {
566566
.map_err(|e| e.to_pyexception(vm))
567567
}
568568

569+
/// Take `inner`, detaching while blocked.
570+
///
571+
/// `GetOverlappedResult` holds this mutex across its `allow_threads`
572+
/// wait, so a stopped thread can still be holding it. Blocking on it
573+
/// while attached would leave no safepoint for that stop to complete at.
574+
fn lock_inner(&self, vm: &VirtualMachine) -> PyMutexGuard<'_, host_overlapped::Operation> {
575+
vm.allow_threads(|| self.inner.lock())
576+
}
577+
569578
#[pymethod]
570579
fn GetOverlappedResult(&self, wait: bool, vm: &VirtualMachine) -> PyResult<(u32, u32)> {
571-
let mut inner = self.inner.lock();
580+
let mut inner = self.lock_inner(vm);
572581
vm.allow_threads(|| inner.get_result(wait))
573582
.map(|result| (result.transferred, result.error))
574583
.map_err(|e| e.to_pyexception(vm))
575584
}
576585

577586
#[pymethod]
578587
fn getbuffer(&self, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
579-
let inner = self.inner.lock();
588+
let inner = self.lock_inner(vm);
580589
if !inner.is_completed() {
581590
return Err(vm.new_value_error(
582591
"can't get read buffer before GetOverlappedResult() signals the operation completed",
@@ -589,13 +598,13 @@ mod _winapi {
589598

590599
#[pymethod]
591600
fn cancel(&self, vm: &VirtualMachine) -> PyResult<()> {
592-
let mut inner = self.inner.lock();
601+
let mut inner = self.lock_inner(vm);
593602
inner.cancel().map_err(|e| e.to_pyexception(vm))
594603
}
595604

596605
#[pygetset]
597-
fn event(&self) -> isize {
598-
let inner = self.inner.lock();
606+
fn event(&self, vm: &VirtualMachine) -> isize {
607+
let inner = self.lock_inner(vm);
599608
inner.event() as isize
600609
}
601610
}

0 commit comments

Comments
 (0)