Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions crates/common/src/borrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,9 @@ impl_from!('a, T, BorrowedValue<'a, T>,
impl<'a, T: ?Sized> BorrowedValue<'a, T> {
/// Whether reaching the value holds a lock that other threads wait on.
///
/// An immutable object hands out a plain reference and answers `false`;
/// one whose storage can change hands out a guard. A caller about to wait
/// for something unrelated -- a peer, a file, a signal -- can use this to
/// decide whether it may keep the borrow for the duration.
/// A plain reference answers `false`, including references to externally
/// managed memory. This does not guarantee that the storage is immutable
/// or safe to borrow while another thread runs.
#[must_use]
pub const fn is_locked(&self) -> bool {
!matches!(self, Self::Ref(_))
Expand Down
11 changes: 5 additions & 6 deletions crates/vm/src/function/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
AsObject, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject,
VirtualMachine,
builtins::{PyStr, PyStrRef},
builtins::{PyBytes, PyStr, PyStrRef},
common::borrow::{BorrowedValue, BorrowedValueMut},
protocol::{BufferFlags, PyBuffer},
};
Expand Down Expand Up @@ -55,13 +55,12 @@ impl ArgBytesLike {
/// `borrow_buf` may answer with a lock that every other thread writing to
/// the same object waits on, and a thread waiting on a lock never reaches
/// a safepoint, so keeping one across a wait for a peer, a pipe or a
/// signal stops the world from being stopped at all. Bytes reached that
/// way are copied out first. Bytes that lock nothing -- an immutable
/// object's -- are borrowed where they lie, which is all CPython holds in
/// either case.
/// signal stops the world from being stopped at all. Only storage owned
/// by `bytes` can be borrowed across that wait. Other exporters are copied:
/// an unlocked or readonly buffer may still alias writable raw memory.
pub fn borrow_buf_unlocked(&self, vm: &VirtualMachine) -> PyResult<UnlockedBuf<'_>> {
let borrowed = self.borrow_buf();
if !borrowed.is_locked() {
if !borrowed.is_locked() && self.source_object().downcast_ref::<PyBytes>().is_some() {
return Ok(UnlockedBuf::Borrowed(borrowed));
}
let mut copy = Vec::new();
Expand Down
38 changes: 38 additions & 0 deletions crates/vm/src/stdlib/_ctypes/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2020,3 +2020,41 @@ impl Py<PyCThunk> {
self.callable.clone()
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{TryFromObject, class::PyClassImpl, function::ArgBytesLike};

#[test]
fn unlocked_raw_buffers_are_copied_even_when_readonly() {
crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
let _class = RawMemoryBuffer::make_static_type();
for readonly in [false, true] {
let mut storage = *b"before";
let view = memoryview_at_impl(
storage.as_mut_ptr() as usize,
storage.len() as isize,
readonly,
vm,
)
.unwrap();
let buffer = ArgBytesLike::try_from_object(vm, view).unwrap();
let snapshot = buffer.borrow_buf_unlocked(vm).unwrap();
assert_ne!(snapshot.as_ptr(), storage.as_ptr());
storage.copy_from_slice(b"after!");
assert_eq!(&*snapshot, b"before");
}

let bytes = vm.ctx.new_bytes(b"bytes".to_vec());
let view = crate::builtins::PyMemoryView::from_object(bytes.as_object(), vm)
.unwrap()
.into_pyobject(vm);
for obj in [bytes.clone().into(), view] {
let buffer = ArgBytesLike::try_from_object(vm, obj).unwrap();
let borrowed = buffer.borrow_buf_unlocked(vm).unwrap();
assert_eq!(borrowed.as_ptr(), bytes.as_bytes().as_ptr());
}
});
}
}
4 changes: 2 additions & 2 deletions crates/vm/src/stdlib/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,9 +460,9 @@ pub(super) mod _os {

#[pyfunction]
fn write(fd: crt_fd::Borrowed<'_>, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> {
let owned = data.with_ref(|b| b.to_vec());
let buffer = data.borrow_buf_unlocked(vm)?;
Comment thread
1ndahous3 marked this conversation as resolved.
loop {
match vm.allow_threads(|| crt_fd::write(fd, &owned)) {
match vm.allow_threads(|| crt_fd::write(fd, &buffer)) {
Ok(n) => return Ok(n),
Err(e) if e.raw_os_error() == Some(libc::EINTR) => {
vm.check_signals()?;
Expand Down
Loading