Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 4400804

Browse files
committed
Refactor AnonSocket::read/write
1 parent 34ad8de commit 4400804

File tree

1 file changed

+80
-44
lines changed

1 file changed

+80
-44
lines changed

src/tools/miri/src/shims/unix/unnamed_socket.rs

Lines changed: 80 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,7 @@ impl FileDescription for AnonSocket {
146146
// corresponding ErrorKind variant.
147147
throw_unsup_format!("reading from the write end of a pipe");
148148
};
149-
let mut readbuf = readbuf.borrow_mut();
150-
if readbuf.buf.is_empty() {
149+
if readbuf.borrow().buf.is_empty() {
151150
if self.peer_fd().upgrade().is_none() {
152151
// Socketpair with no peer and empty buffer.
153152
// 0 bytes successfully read indicates end-of-file.
@@ -167,31 +166,8 @@ impl FileDescription for AnonSocket {
167166
}
168167
}
169168
}
170-
171-
// Synchronize with all previous writes to this buffer.
172-
// FIXME: this over-synchronizes; a more precise approach would be to
173-
// only sync with the writes whose data we will read.
174-
ecx.acquire_clock(&readbuf.clock);
175-
176-
// Do full read / partial read based on the space available.
177-
// Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior.
178-
let actual_read_size = readbuf.buf.read(&mut bytes).unwrap();
179-
180-
// Need to drop before others can access the readbuf again.
181-
drop(readbuf);
182-
183-
// A notification should be provided for the peer file description even when it can
184-
// only write 1 byte. This implementation is not compliant with the actual Linux kernel
185-
// implementation. For optimization reasons, the kernel will only mark the file description
186-
// as "writable" when it can write more than a certain number of bytes. Since we
187-
// don't know what that *certain number* is, we will provide a notification every time
188-
// a read is successful. This might result in our epoll emulation providing more
189-
// notifications than the real system.
190-
if let Some(peer_fd) = self.peer_fd().upgrade() {
191-
ecx.check_and_update_readiness(&peer_fd)?;
192-
}
193-
194-
ecx.return_read_success(ptr, &bytes, actual_read_size, dest)
169+
// TODO: We might need to decide what to do if peer_fd is closed when read is blocked.
170+
anonsocket_read(self, self.peer_fd().upgrade(), &mut bytes, ptr, dest, ecx)
195171
}
196172

197173
fn write<'tcx>(
@@ -221,9 +197,8 @@ impl FileDescription for AnonSocket {
221197
// corresponding ErrorKind variant.
222198
throw_unsup_format!("writing to the reading end of a pipe");
223199
};
224-
let mut writebuf = writebuf.borrow_mut();
225-
let data_size = writebuf.buf.len();
226-
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size);
200+
let available_space =
201+
MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len());
227202
if available_space == 0 {
228203
if self.is_nonblock {
229204
// Non-blocking socketpair with a full buffer.
@@ -233,24 +208,85 @@ impl FileDescription for AnonSocket {
233208
throw_unsup_format!("socketpair/pipe/pipe2 write: blocking isn't supported yet");
234209
}
235210
}
236-
// Remember this clock so `read` can synchronize with us.
237-
ecx.release_clock(|clock| {
238-
writebuf.clock.join(clock);
239-
});
240-
// Do full write / partial write based on the space available.
241-
let actual_write_size = len.min(available_space);
242-
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
243-
writebuf.buf.extend(&bytes[..actual_write_size]);
211+
anonsocket_write(available_space, &peer_fd, ptr, len, dest, ecx)
212+
}
213+
}
244214

245-
// Need to stop accessing peer_fd so that it can be notified.
246-
drop(writebuf);
215+
/// Write to AnonSocket based on the space available and return the written byte size.
216+
fn anonsocket_write<'tcx>(
217+
available_space: usize,
218+
peer_fd: &FileDescriptionRef,
219+
ptr: Pointer,
220+
len: usize,
221+
dest: &MPlaceTy<'tcx>,
222+
ecx: &mut MiriInterpCx<'tcx>,
223+
) -> InterpResult<'tcx> {
224+
let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf else {
225+
// FIXME: This should return EBADF, but there's no nice way to do that as there's no
226+
// corresponding ErrorKind variant.
227+
throw_unsup_format!("writing to the reading end of a pipe")
228+
};
229+
let mut writebuf = writebuf.borrow_mut();
230+
231+
// Remember this clock so `read` can synchronize with us.
232+
ecx.release_clock(|clock| {
233+
writebuf.clock.join(clock);
234+
});
235+
// Do full write / partial write based on the space available.
236+
let actual_write_size = len.min(available_space);
237+
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
238+
writebuf.buf.extend(&bytes[..actual_write_size]);
239+
240+
// Need to stop accessing peer_fd so that it can be notified.
241+
drop(writebuf);
242+
243+
// Notification should be provided for peer fd as it became readable.
244+
// The kernel does this even if the fd was already readable before, so we follow suit.
245+
ecx.check_and_update_readiness(peer_fd)?;
246+
247+
ecx.return_write_success(actual_write_size, dest)
248+
}
247249

248-
// Notification should be provided for peer fd as it became readable.
249-
// The kernel does this even if the fd was already readable before, so we follow suit.
250+
/// Read from AnonSocket and return the number of bytes read.
251+
fn anonsocket_read<'tcx>(
252+
anonsocket: &AnonSocket,
253+
peer_fd: Option<FileDescriptionRef>,
254+
bytes: &mut [u8],
255+
ptr: Pointer,
256+
dest: &MPlaceTy<'tcx>,
257+
ecx: &mut MiriInterpCx<'tcx>,
258+
) -> InterpResult<'tcx> {
259+
let Some(readbuf) = &anonsocket.readbuf else {
260+
// FIXME: This should return EBADF, but there's no nice way to do that as there's no
261+
// corresponding ErrorKind variant.
262+
throw_unsup_format!("reading from the write end of a pipe")
263+
};
264+
let mut readbuf = readbuf.borrow_mut();
265+
266+
// Synchronize with all previous writes to this buffer.
267+
// FIXME: this over-synchronizes; a more precise approach would be to
268+
// only sync with the writes whose data we will read.
269+
ecx.acquire_clock(&readbuf.clock);
270+
271+
// Do full read / partial read based on the space available.
272+
// Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior.
273+
let actual_read_size = readbuf.buf.read(bytes).unwrap();
274+
275+
// Need to drop before others can access the readbuf again.
276+
drop(readbuf);
277+
278+
// A notification should be provided for the peer file description even when it can
279+
// only write 1 byte. This implementation is not compliant with the actual Linux kernel
280+
// implementation. For optimization reasons, the kernel will only mark the file description
281+
// as "writable" when it can write more than a certain number of bytes. Since we
282+
// don't know what that *certain number* is, we will provide a notification every time
283+
// a read is successful. This might result in our epoll emulation providing more
284+
// notifications than the real system.
285+
if let Some(peer_fd) = peer_fd {
250286
ecx.check_and_update_readiness(&peer_fd)?;
251-
252-
ecx.return_write_success(actual_write_size, dest)
253287
}
288+
289+
ecx.return_read_success(ptr, bytes, actual_read_size, dest)
254290
}
255291

256292
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}

0 commit comments

Comments
 (0)