Skip to content

Commit

Permalink
Auto merge of rust-lang#3820 - tiif:epoller, r=RalfJung
Browse files Browse the repository at this point in the history
Add epollerr support

Fixes rust-lang#3816

For socketpair, if the peer fd is closed while having data in its read buffer, ``EPOLLER`` will be thrown.

This is still WIP because I am currently finding a way to check if peer fd still has something in its ``readbuf`` when it is closed and add the ``EPOLLER`` flag In ``get_epoll_ready_events``.
  • Loading branch information
bors committed Aug 18, 2024
2 parents 9370020 + 483120d commit c74ffd8
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 6 deletions.
23 changes: 20 additions & 3 deletions src/tools/miri/src/shims/unix/linux/epoll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,27 @@ pub struct EpollReadyEvents {
/// epollrdhup also gets set when only the write half is closed, which is possible
/// via `shutdown(_, SHUT_WR)`.
pub epollhup: bool,
/// Error condition happened on the associated file descriptor.
pub epollerr: bool,
}

impl EpollReadyEvents {
pub fn new() -> Self {
EpollReadyEvents { epollin: false, epollout: false, epollrdhup: false, epollhup: false }
EpollReadyEvents {
epollin: false,
epollout: false,
epollrdhup: false,
epollhup: false,
epollerr: false,
}
}

pub fn get_event_bitmask<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> u32 {
let epollin = ecx.eval_libc_u32("EPOLLIN");
let epollout = ecx.eval_libc_u32("EPOLLOUT");
let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
let epollhup = ecx.eval_libc_u32("EPOLLHUP");
let epollerr = ecx.eval_libc_u32("EPOLLERR");

let mut bitmask = 0;
if self.epollin {
Expand All @@ -102,6 +111,9 @@ impl EpollReadyEvents {
if self.epollhup {
bitmask |= epollhup;
}
if self.epollerr {
bitmask |= epollerr;
}
bitmask
}
}
Expand Down Expand Up @@ -229,6 +241,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let epollrdhup = this.eval_libc_u32("EPOLLRDHUP");
let epollet = this.eval_libc_u32("EPOLLET");
let epollhup = this.eval_libc_u32("EPOLLHUP");
let epollerr = this.eval_libc_u32("EPOLLERR");

// Fail on unsupported operations.
if op & epoll_ctl_add != epoll_ctl_add
Expand Down Expand Up @@ -261,10 +274,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {

// Unset the flag we support to discover if any unsupported flags are used.
let mut flags = events;
// epoll_wait(2) will always wait for epollhup; it is not
// epoll_wait(2) will always wait for epollhup and epollerr; it is not
// necessary to set it in events when calling epoll_ctl().
// So we will always set this event type.
// So we will always set these two event types.
events |= epollhup;
events |= epollerr;

if events & epollet != epollet {
// We only support edge-triggered notification for now.
Expand All @@ -284,6 +298,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
if flags & epollhup == epollhup {
flags &= !epollhup;
}
if flags & epollerr == epollerr {
flags &= !epollerr;
}
if flags != 0 {
throw_unsup_format!(
"epoll_ctl: encountered unknown unsupported flags {:#x}",
Expand Down
28 changes: 25 additions & 3 deletions src/tools/miri/src/shims/unix/unnamed_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! are entirely implemented inside Miri.
//! We also use the same infrastructure to implement unnamed pipes.
use std::cell::{OnceCell, RefCell};
use std::cell::{Cell, OnceCell, RefCell};
use std::collections::VecDeque;
use std::io;
use std::io::{Error, ErrorKind, Read};
Expand All @@ -27,6 +27,10 @@ struct AnonSocket {
/// writing to. This is a weak reference because the other side may be closed before us; all
/// future writes will then trigger EPIPE.
peer_fd: OnceCell<WeakFileDescriptionRef>,
/// Indicates whether the peer has lost data when the file description is closed.
/// This flag is set to `true` if the peer's `readbuf` is non-empty at the time
/// of closure.
peer_lost_data: Cell<bool>,
is_nonblock: bool,
}

Expand Down Expand Up @@ -91,6 +95,10 @@ impl FileDescription for AnonSocket {
// for read and write.
epoll_ready_events.epollin = true;
epoll_ready_events.epollout = true;
// If there is data lost in peer_fd, set EPOLLERR.
if self.peer_lost_data.get() {
epoll_ready_events.epollerr = true;
}
}
Ok(epoll_ready_events)
}
Expand All @@ -101,6 +109,13 @@ impl FileDescription for AnonSocket {
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
if let Some(peer_fd) = self.peer_fd().upgrade() {
// If the current readbuf is non-empty when the file description is closed,
// notify the peer that data lost has happened in current file description.
if let Some(readbuf) = &self.readbuf {
if !readbuf.borrow().buf.is_empty() {
peer_fd.downcast::<AnonSocket>().unwrap().peer_lost_data.set(true);
}
}
// Notify peer fd that close has happened, since that can unblock reads and writes.
ecx.check_and_update_readiness(&peer_fd)?;
}
Expand Down Expand Up @@ -290,11 +305,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let fd0 = fds.new_ref(AnonSocket {
readbuf: Some(RefCell::new(Buffer::new())),
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: is_sock_nonblock,
});
let fd1 = fds.new_ref(AnonSocket {
readbuf: Some(RefCell::new(Buffer::new())),
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: is_sock_nonblock,
});

Expand Down Expand Up @@ -340,10 +357,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let fd0 = fds.new_ref(AnonSocket {
readbuf: Some(RefCell::new(Buffer::new())),
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: false,
});
let fd1 = fds.new_ref(AnonSocket {
readbuf: None,
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: false,
});
let fd1 =
fds.new_ref(AnonSocket { readbuf: None, peer_fd: OnceCell::new(), is_nonblock: false });

// Make the file descriptions point to each other.
fd0.downcast::<AnonSocket>().unwrap().peer_fd.set(fd1.downgrade()).unwrap();
Expand Down
38 changes: 38 additions & 0 deletions src/tools/miri/tests/pass-dep/libc/libc-epoll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fn main() {
test_pointer();
test_two_same_fd_in_same_epoll_instance();
test_epoll_wait_maxevent_zero();
test_socketpair_epollerr();
test_epoll_lost_events();
test_ready_list_fetching_logic();
}
Expand Down Expand Up @@ -551,6 +552,43 @@ fn test_epoll_wait_maxevent_zero() {
assert_eq!(res, -1);
}

fn test_socketpair_epollerr() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);

// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);

// Write to fd[0]
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);

// Close fds[1].
// EPOLLERR will be triggered if we close peer fd that still has data in its read buffer.
let res = unsafe { libc::close(fds[1]) };
assert_eq!(res, 0);

// Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP
let mut ev = libc::epoll_event {
events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET | libc::EPOLLRDHUP) as _,
u64: u64::try_from(fds[1]).unwrap(),
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
assert_ne!(res, -1);

// Check result from epoll_wait.
let expected_event = u32::try_from(
libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLHUP | libc::EPOLLRDHUP | libc::EPOLLERR,
)
.unwrap();
let expected_value = u64::try_from(fds[1]).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}

// This is a test for https://github.com/rust-lang/miri/issues/3812,
// epoll can lose events if they don't fit in the output buffer.
fn test_epoll_lost_events() {
Expand Down

0 comments on commit c74ffd8

Please sign in to comment.