Auto merge of #3829 - tiif:edgecase, r=oli-obk
epoll: handle edge case for epoll_ctl There is a test case that revealed that our implementation differs from the real system: - Set up an epoll watching the FD - Call epoll_wait - Set up another epoll watching the same FD - Call epoll_wait on the first epoll. Nothing should be reported! This happened because, in ``epoll_ctl``, we used ``check_and_update_readiness``, which is a function that would return notification for all epoll file description that registered a particular file description. But we shouldn't do that because no notification should be returned if there is no I/O activity between two ``epoll_wait`` (every first ``epoll_wait`` that happens after ``epoll_ctl`` is an exception, we should return notification that reflects the readiness of file description). r? `@oli-obk`
This commit is contained in:
commit
dbfd066ed8
@ -269,10 +269,10 @@ fn epoll_ctl(
|
|||||||
let mut interest_list = epoll_file_description.interest_list.borrow_mut();
|
let mut interest_list = epoll_file_description.interest_list.borrow_mut();
|
||||||
let ready_list = &epoll_file_description.ready_list;
|
let ready_list = &epoll_file_description.ready_list;
|
||||||
|
|
||||||
let Some(file_descriptor) = this.machine.fds.get(fd) else {
|
let Some(fd_ref) = this.machine.fds.get(fd) else {
|
||||||
return Ok(Scalar::from_i32(this.fd_not_found()?));
|
return Ok(Scalar::from_i32(this.fd_not_found()?));
|
||||||
};
|
};
|
||||||
let id = file_descriptor.get_id();
|
let id = fd_ref.get_id();
|
||||||
|
|
||||||
if op == epoll_ctl_add || op == epoll_ctl_mod {
|
if op == epoll_ctl_add || op == epoll_ctl_mod {
|
||||||
// Read event bitmask and data from epoll_event passed by caller.
|
// Read event bitmask and data from epoll_event passed by caller.
|
||||||
@ -332,7 +332,6 @@ fn epoll_ctl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = file_descriptor.get_id();
|
|
||||||
// Create an epoll_interest.
|
// Create an epoll_interest.
|
||||||
let interest = Rc::new(RefCell::new(EpollEventInterest {
|
let interest = Rc::new(RefCell::new(EpollEventInterest {
|
||||||
file_descriptor: fd,
|
file_descriptor: fd,
|
||||||
@ -344,7 +343,7 @@ fn epoll_ctl(
|
|||||||
if op == epoll_ctl_add {
|
if op == epoll_ctl_add {
|
||||||
// Insert an epoll_interest to global epoll_interest list.
|
// Insert an epoll_interest to global epoll_interest list.
|
||||||
this.machine.epoll_interests.insert_epoll_interest(id, Rc::downgrade(&interest));
|
this.machine.epoll_interests.insert_epoll_interest(id, Rc::downgrade(&interest));
|
||||||
interest_list.insert(epoll_key, interest);
|
interest_list.insert(epoll_key, Rc::clone(&interest));
|
||||||
} else {
|
} else {
|
||||||
// Directly modify the epoll_interest so the global epoll_event_interest table
|
// Directly modify the epoll_interest so the global epoll_event_interest table
|
||||||
// will be updated too.
|
// will be updated too.
|
||||||
@ -353,9 +352,9 @@ fn epoll_ctl(
|
|||||||
epoll_interest.data = data;
|
epoll_interest.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Readiness will be updated immediately when the epoll_event_interest is added or modified.
|
// Notification will be returned for current epfd if there is event in the file
|
||||||
this.check_and_update_readiness(&file_descriptor)?;
|
// descriptor we registered.
|
||||||
|
check_and_update_one_event_interest(&fd_ref, interest, id, this)?;
|
||||||
return Ok(Scalar::from_i32(0));
|
return Ok(Scalar::from_i32(0));
|
||||||
} else if op == epoll_ctl_del {
|
} else if op == epoll_ctl_del {
|
||||||
let epoll_key = (id, fd);
|
let epoll_key = (id, fd);
|
||||||
@ -489,25 +488,9 @@ fn check_and_update_readiness(&self, fd_ref: &FileDescriptionRef) -> InterpResul
|
|||||||
let id = fd_ref.get_id();
|
let id = fd_ref.get_id();
|
||||||
// Get a list of EpollEventInterest that is associated to a specific file description.
|
// Get a list of EpollEventInterest that is associated to a specific file description.
|
||||||
if let Some(epoll_interests) = this.machine.epoll_interests.get_epoll_interest(id) {
|
if let Some(epoll_interests) = this.machine.epoll_interests.get_epoll_interest(id) {
|
||||||
let epoll_ready_events = fd_ref.get_epoll_ready_events()?;
|
|
||||||
// Get the bitmask of ready events.
|
|
||||||
let ready_events = epoll_ready_events.get_event_bitmask(this);
|
|
||||||
|
|
||||||
for weak_epoll_interest in epoll_interests {
|
for weak_epoll_interest in epoll_interests {
|
||||||
if let Some(epoll_interest) = weak_epoll_interest.upgrade() {
|
if let Some(epoll_interest) = weak_epoll_interest.upgrade() {
|
||||||
// This checks if any of the events specified in epoll_event_interest.events
|
check_and_update_one_event_interest(fd_ref, epoll_interest, id, this)?;
|
||||||
// match those in ready_events.
|
|
||||||
let epoll_event_interest = epoll_interest.borrow();
|
|
||||||
let flags = epoll_event_interest.events & ready_events;
|
|
||||||
// If there is any event that we are interested in being specified as ready,
|
|
||||||
// insert an epoll_return to the ready list.
|
|
||||||
if flags != 0 {
|
|
||||||
let epoll_key = (id, epoll_event_interest.file_descriptor);
|
|
||||||
let ready_list = &mut epoll_event_interest.ready_list.borrow_mut();
|
|
||||||
let event_instance =
|
|
||||||
EpollEventInstance::new(flags, epoll_event_interest.data);
|
|
||||||
ready_list.insert(epoll_key, event_instance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,3 +515,30 @@ fn ready_list_next(
|
|||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This helper function checks whether an epoll notification should be triggered for a specific
|
||||||
|
/// epoll_interest and, if necessary, triggers the notification. Unlike check_and_update_readiness,
|
||||||
|
/// this function sends a notification to only one epoll instance.
|
||||||
|
fn check_and_update_one_event_interest<'tcx>(
|
||||||
|
fd_ref: &FileDescriptionRef,
|
||||||
|
interest: Rc<RefCell<EpollEventInterest>>,
|
||||||
|
id: FdId,
|
||||||
|
ecx: &MiriInterpCx<'tcx>,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
|
// Get the bitmask of ready events for a file description.
|
||||||
|
let ready_events_bitmask = fd_ref.get_epoll_ready_events()?.get_event_bitmask(ecx);
|
||||||
|
let epoll_event_interest = interest.borrow();
|
||||||
|
// This checks if any of the events specified in epoll_event_interest.events
|
||||||
|
// match those in ready_events.
|
||||||
|
let flags = epoll_event_interest.events & ready_events_bitmask;
|
||||||
|
// If there is any event that we are interested in being specified as ready,
|
||||||
|
// insert an epoll_return to the ready list.
|
||||||
|
if flags != 0 {
|
||||||
|
let epoll_key = (id, epoll_event_interest.file_descriptor);
|
||||||
|
let ready_list = &mut epoll_event_interest.ready_list.borrow_mut();
|
||||||
|
let event_instance = EpollEventInstance::new(flags, epoll_event_interest.data);
|
||||||
|
// Triggers the notification by inserting it to the ready list.
|
||||||
|
ready_list.insert(epoll_key, event_instance);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
//@only-target-linux
|
||||||
|
|
||||||
|
// This is a test for registering unsupported fd with epoll.
|
||||||
|
// Register epoll fd with epoll is allowed in real system, but we do not support this.
|
||||||
|
fn main() {
|
||||||
|
// Create two epoll instance.
|
||||||
|
let epfd0 = unsafe { libc::epoll_create1(0) };
|
||||||
|
assert_ne!(epfd0, -1);
|
||||||
|
let epfd1 = unsafe { libc::epoll_create1(0) };
|
||||||
|
assert_ne!(epfd1, -1);
|
||||||
|
|
||||||
|
// Register epoll with epoll.
|
||||||
|
let mut ev =
|
||||||
|
libc::epoll_event { events: (libc::EPOLLIN | libc::EPOLLET) as _, u64: epfd1 as u64 };
|
||||||
|
let res = unsafe { libc::epoll_ctl(epfd0, libc::EPOLL_CTL_ADD, epfd1, &mut ev) };
|
||||||
|
//~^ERROR: epoll does not support this file description
|
||||||
|
assert_eq!(res, 0);
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
error: unsupported operation: epoll: epoll does not support this file description
|
||||||
|
--> $DIR/libc_epoll_unsupported_fd.rs:LL:CC
|
||||||
|
|
|
||||||
|
LL | let res = unsafe { libc::epoll_ctl(epfd0, libc::EPOLL_CTL_ADD, epfd1, &mut ev) };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ epoll: epoll does not support this file description
|
||||||
|
|
|
||||||
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
|
||||||
|
= note: BACKTRACE:
|
||||||
|
= note: inside `main` at $DIR/libc_epoll_unsupported_fd.rs:LL:CC
|
||||||
|
|
||||||
|
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
@ -22,6 +22,7 @@ fn main() {
|
|||||||
test_epoll_lost_events();
|
test_epoll_lost_events();
|
||||||
test_ready_list_fetching_logic();
|
test_ready_list_fetching_logic();
|
||||||
test_epoll_ctl_epfd_equal_fd();
|
test_epoll_ctl_epfd_equal_fd();
|
||||||
|
test_epoll_ctl_notification();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using `as` cast since `EPOLLET` wraps around
|
// Using `as` cast since `EPOLLET` wraps around
|
||||||
@ -644,3 +645,41 @@ fn test_epoll_ctl_epfd_equal_fd() {
|
|||||||
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
|
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
|
||||||
assert_eq!(res, -1);
|
assert_eq!(res, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We previously used check_and_update_readiness the moment a file description is registered in an
|
||||||
|
// epoll instance. But this has an unfortunate side effect of returning notification to another
|
||||||
|
// epfd that shouldn't receive notification.
|
||||||
|
fn test_epoll_ctl_notification() {
|
||||||
|
// Create an epoll instance.
|
||||||
|
let epfd0 = unsafe { libc::epoll_create1(0) };
|
||||||
|
assert_ne!(epfd0, -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);
|
||||||
|
|
||||||
|
// Register one side of the socketpair with epoll.
|
||||||
|
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
|
||||||
|
let res = unsafe { libc::epoll_ctl(epfd0, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
|
||||||
|
assert_eq!(res, 0);
|
||||||
|
|
||||||
|
// epoll_wait to clear notification for epfd0.
|
||||||
|
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
|
||||||
|
let expected_value = fds[0] as u64;
|
||||||
|
check_epoll_wait::<1>(epfd0, &[(expected_event, expected_value)]);
|
||||||
|
|
||||||
|
// Create another epoll instance.
|
||||||
|
let epfd1 = unsafe { libc::epoll_create1(0) };
|
||||||
|
assert_ne!(epfd1, -1);
|
||||||
|
|
||||||
|
// Register the same file description for epfd1.
|
||||||
|
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
|
||||||
|
let res = unsafe { libc::epoll_ctl(epfd1, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
|
||||||
|
assert_eq!(res, 0);
|
||||||
|
check_epoll_wait::<1>(epfd1, &[(expected_event, expected_value)]);
|
||||||
|
|
||||||
|
// Previously this epoll_wait will receive a notification, but we shouldn't return notification
|
||||||
|
// for this epfd, because there is no I/O event between the two epoll_wait.
|
||||||
|
check_epoll_wait::<1>(epfd0, &[]);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user