Skip to content

Commit 4293350

Browse files
youknowoneCopilot
authored andcommitted
Handle EINTR retry in os.write() (PEP 475) (#7482)
* Handle EINTR retry in os.write() (PEP 475) Add EINTR retry loop to os.write(), matching the existing pattern in os.read() and os.readinto(). Remove the expectedFailure marker from test_write in _test_eintr.py. * Add atomic snapshot for dict/dict_keys in extract_elements Add fast paths for dict and dict_keys types in extract_elements_with, matching _list_extend() in CPython Objects/listobject.c. Each branch takes an atomic snapshot under a single read lock, preventing race conditions from concurrent dict mutation without the GIL. Remove expectedFailure from test_thread_safety.
1 parent 76ddf11 commit 4293350

File tree

4 files changed

+23
-13
lines changed

4 files changed

+23
-13
lines changed

Lib/test/_test_eintr.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,6 @@ def test_readinto(self):
163163
self.assertEqual(os.readinto(fd, buffer), len(expected))
164164
self.assertEqual(buffer, expected)
165165

166-
@unittest.expectedFailure # TODO: RUSTPYTHON; InterruptedError: [Errno 4] Interrupted system call
167166
def test_write(self):
168167
rd, wr = os.pipe()
169168
self.addCleanup(os.close, wr)

Lib/test/_test_multiprocessing.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4814,8 +4814,6 @@ def test_finalize(self):
48144814
self.assertEqual(result, ['a', 'b', 'd10', 'd03', 'd02', 'd01', 'e'])
48154815

48164816
@support.requires_resource('cpu')
4817-
# TODO: RUSTPYTHON; dict iteration races with concurrent GC mutations
4818-
@unittest.expectedFailure
48194817
def test_thread_safety(self):
48204818
# bpo-24484: _run_finalizers() should be thread-safe
48214819
def cb():

crates/vm/src/stdlib/os.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,19 @@ pub(super) mod _os {
321321
}
322322

323323
#[pyfunction]
324-
fn write(
325-
fd: crt_fd::Borrowed<'_>,
326-
data: ArgBytesLike,
327-
vm: &VirtualMachine,
328-
) -> io::Result<usize> {
329-
data.with_ref(|b| vm.allow_threads(|| crt_fd::write(fd, b)))
324+
fn write(fd: crt_fd::Borrowed<'_>, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> {
325+
data.with_ref(|b| {
326+
loop {
327+
match vm.allow_threads(|| crt_fd::write(fd, b)) {
328+
Ok(n) => return Ok(n),
329+
Err(e) if e.raw_os_error() == Some(libc::EINTR) => {
330+
vm.check_signals()?;
331+
continue;
332+
}
333+
Err(e) => return Err(e.into_pyexception(vm)),
334+
}
335+
}
336+
})
330337
}
331338

332339
#[cfg(not(windows))]

crates/vm/src/vm/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::{
2222
self, PyBaseExceptionRef, PyDict, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned,
2323
PyStrRef, PyTypeRef, PyUtf8Str, PyUtf8StrInterned, PyWeak,
2424
code::PyCode,
25-
dict::{PyDictItems, PyDictValues},
25+
dict::{PyDictItems, PyDictKeys, PyDictValues},
2626
pystr::AsPyStr,
2727
tuple::PyTuple,
2828
},
@@ -1822,24 +1822,30 @@ impl VirtualMachine {
18221822
where
18231823
F: Fn(PyObjectRef) -> PyResult<T>,
18241824
{
1825-
// Extract elements from item, if possible:
1825+
// Type-specific fast paths corresponding to _list_extend() in CPython
1826+
// Objects/listobject.c. Each branch takes an atomic snapshot to avoid
1827+
// race conditions from concurrent mutation (no GIL).
18261828
let cls = value.class();
18271829
let list_borrow;
18281830
let slice = if cls.is(self.ctx.types.tuple_type) {
18291831
value.downcast_ref::<PyTuple>().unwrap().as_slice()
18301832
} else if cls.is(self.ctx.types.list_type) {
18311833
list_borrow = value.downcast_ref::<PyList>().unwrap().borrow_vec();
18321834
&list_borrow
1835+
} else if cls.is(self.ctx.types.dict_type) {
1836+
let keys = value.downcast_ref::<PyDict>().unwrap().keys_vec();
1837+
return keys.into_iter().map(func).collect();
1838+
} else if cls.is(self.ctx.types.dict_keys_type) {
1839+
let keys = value.downcast_ref::<PyDictKeys>().unwrap().dict.keys_vec();
1840+
return keys.into_iter().map(func).collect();
18331841
} else if cls.is(self.ctx.types.dict_values_type) {
1834-
// Atomic snapshot of dict values - prevents race condition during iteration
18351842
let values = value
18361843
.downcast_ref::<PyDictValues>()
18371844
.unwrap()
18381845
.dict
18391846
.values_vec();
18401847
return values.into_iter().map(func).collect();
18411848
} else if cls.is(self.ctx.types.dict_items_type) {
1842-
// Atomic snapshot of dict items - prevents race condition during iteration
18431849
let items = value
18441850
.downcast_ref::<PyDictItems>()
18451851
.unwrap()

0 commit comments

Comments
 (0)