Skip to content

Commit 29338d7

Browse files
committed
Snapshot buffers without immutable storage
Assisted-by: Codex:gpt-6
1 parent fb9ada1 commit 29338d7

3 files changed

Lines changed: 46 additions & 10 deletions

File tree

‎crates/common/src/borrow.rs‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ impl_from!('a, T, BorrowedValue<'a, T>,
3939
impl<'a, T: ?Sized> BorrowedValue<'a, T> {
4040
/// Whether reaching the value holds a lock that other threads wait on.
4141
///
42-
/// An immutable object hands out a plain reference and answers `false`;
43-
/// one whose storage can change hands out a guard. A caller about to wait
44-
/// for something unrelated -- a peer, a file, a signal -- can use this to
45-
/// decide whether it may keep the borrow for the duration.
42+
/// A plain reference answers `false`, including references to externally
43+
/// managed memory. This does not guarantee that the storage is immutable
44+
/// or safe to borrow while another thread runs.
4645
#[must_use]
4746
pub const fn is_locked(&self) -> bool {
4847
!matches!(self, Self::Ref(_))

‎crates/vm/src/function/buffer.rs‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
AsObject, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject,
33
VirtualMachine,
4-
builtins::{PyStr, PyStrRef},
4+
builtins::{PyBytes, PyStr, PyStrRef},
55
common::borrow::{BorrowedValue, BorrowedValueMut},
66
protocol::{BufferFlags, PyBuffer},
77
};
@@ -55,13 +55,12 @@ impl ArgBytesLike {
5555
/// `borrow_buf` may answer with a lock that every other thread writing to
5656
/// the same object waits on, and a thread waiting on a lock never reaches
5757
/// a safepoint, so keeping one across a wait for a peer, a pipe or a
58-
/// signal stops the world from being stopped at all. Bytes reached that
59-
/// way are copied out first. Bytes that lock nothing -- an immutable
60-
/// object's -- are borrowed where they lie, which is all CPython holds in
61-
/// either case.
58+
/// signal stops the world from being stopped at all. Only storage owned
59+
/// by `bytes` can be borrowed across that wait. Other exporters are copied:
60+
/// an unlocked or readonly buffer may still alias writable raw memory.
6261
pub fn borrow_buf_unlocked(&self, vm: &VirtualMachine) -> PyResult<UnlockedBuf<'_>> {
6362
let borrowed = self.borrow_buf();
64-
if !borrowed.is_locked() {
63+
if !borrowed.is_locked() && self.source_object().downcast_ref::<PyBytes>().is_some() {
6564
return Ok(UnlockedBuf::Borrowed(borrowed));
6665
}
6766
let mut copy = Vec::new();

‎crates/vm/src/stdlib/_ctypes/function.rs‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2020,3 +2020,41 @@ impl Py<PyCThunk> {
20202020
self.callable.clone()
20212021
}
20222022
}
2023+
2024+
#[cfg(test)]
2025+
mod tests {
2026+
use super::*;
2027+
use crate::{TryFromObject, class::PyClassImpl, function::ArgBytesLike};
2028+
2029+
#[test]
2030+
fn unlocked_raw_buffers_are_copied_even_when_readonly() {
2031+
crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
2032+
let _class = RawMemoryBuffer::make_static_type();
2033+
for readonly in [false, true] {
2034+
let mut storage = *b"before";
2035+
let view = memoryview_at_impl(
2036+
storage.as_mut_ptr() as usize,
2037+
storage.len() as isize,
2038+
readonly,
2039+
vm,
2040+
)
2041+
.unwrap();
2042+
let buffer = ArgBytesLike::try_from_object(vm, view).unwrap();
2043+
let snapshot = buffer.borrow_buf_unlocked(vm).unwrap();
2044+
assert_ne!(snapshot.as_ptr(), storage.as_ptr());
2045+
storage.copy_from_slice(b"after!");
2046+
assert_eq!(&*snapshot, b"before");
2047+
}
2048+
2049+
let bytes = vm.ctx.new_bytes(b"bytes".to_vec());
2050+
let view = crate::builtins::PyMemoryView::from_object(bytes.as_object(), vm)
2051+
.unwrap()
2052+
.into_pyobject(vm);
2053+
for obj in [bytes.clone().into(), view] {
2054+
let buffer = ArgBytesLike::try_from_object(vm, obj).unwrap();
2055+
let borrowed = buffer.borrow_buf_unlocked(vm).unwrap();
2056+
assert_eq!(borrowed.as_ptr(), bytes.as_bytes().as_ptr());
2057+
}
2058+
});
2059+
}
2060+
}

0 commit comments

Comments
 (0)