Skip to content

Commit 9370020

Browse files
committed
Auto merge of #3818 - tiif:loseevents, r=RalfJung
epoll: iterate through output buffer then fetch an event from ready list Fixes #3812
2 parents 8f2768b + 6702f15 commit 9370020

File tree

2 files changed

+94
-17
lines changed

2 files changed

+94
-17
lines changed

src/tools/miri/src/shims/unix/linux/epoll.rs

+30-17
Original file line numberDiff line numberDiff line change
@@ -436,23 +436,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
436436
let mut num_of_events: i32 = 0;
437437
let mut array_iter = this.project_array_fields(&events)?;
438438

439-
while let Some((epoll_key, epoll_return)) = ready_list.pop_first() {
440-
// If the file description is fully close, the entry for corresponding FdID in the
441-
// global epoll event interest table would be empty.
442-
if this.machine.epoll_interests.get_epoll_interest(epoll_key.0).is_some() {
443-
// Return notification to the caller if the file description is not fully closed.
444-
if let Some(des) = array_iter.next(this)? {
445-
this.write_int_fields_named(
446-
&[
447-
("events", epoll_return.events.into()),
448-
("u64", epoll_return.data.into()),
449-
],
450-
&des.1,
451-
)?;
452-
num_of_events = num_of_events.checked_add(1).unwrap();
453-
} else {
454-
break;
455-
}
439+
while let Some(des) = array_iter.next(this)? {
440+
if let Some(epoll_event_instance) = ready_list_next(this, &mut ready_list) {
441+
this.write_int_fields_named(
442+
&[
443+
("events", epoll_event_instance.events.into()),
444+
("u64", epoll_event_instance.data.into()),
445+
],
446+
&des.1,
447+
)?;
448+
num_of_events = num_of_events.strict_add(1);
449+
} else {
450+
break;
456451
}
457452
}
458453
Ok(Scalar::from_i32(num_of_events))
@@ -495,3 +490,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
495490
Ok(())
496491
}
497492
}
493+
494+
/// This function takes in ready list and returns EpollEventInstance with file description
495+
/// that is not closed.
496+
fn ready_list_next(
497+
ecx: &MiriInterpCx<'_>,
498+
ready_list: &mut BTreeMap<(FdId, i32), EpollEventInstance>,
499+
) -> Option<EpollEventInstance> {
500+
while let Some((epoll_key, epoll_event_instance)) = ready_list.pop_first() {
501+
// This ensures that we only return events that we are interested. The FD might have been closed since
502+
// the event was generated, in which case we are not interested anymore.
503+
// When a file description is fully closed, it gets removed from `machine.epoll_interests`,
504+
// so we skip events whose FD is not in that map anymore.
505+
if ecx.machine.epoll_interests.get_epoll_interest(epoll_key.0).is_some() {
506+
return Some(epoll_event_instance);
507+
}
508+
}
509+
return None;
510+
}

src/tools/miri/tests/pass-dep/libc/libc-epoll.rs

+64
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ fn main() {
2020
test_pointer();
2121
test_two_same_fd_in_same_epoll_instance();
2222
test_epoll_wait_maxevent_zero();
23+
test_epoll_lost_events();
24+
test_ready_list_fetching_logic();
2325
}
2426

2527
// Using `as` cast since `EPOLLET` wraps around
@@ -548,3 +550,65 @@ fn test_epoll_wait_maxevent_zero() {
548550
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
549551
assert_eq!(res, -1);
550552
}
553+
554+
// This is a test for https://github.com/rust-lang/miri/issues/3812,
555+
// epoll can lose events if they don't fit in the output buffer.
556+
fn test_epoll_lost_events() {
557+
// Create an epoll instance.
558+
let epfd = unsafe { libc::epoll_create1(0) };
559+
assert_ne!(epfd, -1);
560+
561+
// Create a socketpair instance.
562+
let mut fds = [-1, -1];
563+
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
564+
assert_eq!(res, 0);
565+
566+
// Register both fd to the same epoll instance.
567+
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
568+
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
569+
assert_eq!(res, 0);
570+
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[1] as u64 };
571+
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
572+
assert_eq!(res, 0);
573+
574+
//Two notification should be received. But we only provide buffer for one event.
575+
let expected_event0 = u32::try_from(libc::EPOLLOUT).unwrap();
576+
let expected_value0 = fds[0] as u64;
577+
check_epoll_wait::<1>(epfd, &[(expected_event0, expected_value0)]);
578+
579+
// Previous event should be returned for the second epoll_wait.
580+
let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
581+
let expected_value1 = fds[1] as u64;
582+
check_epoll_wait::<1>(epfd, &[(expected_event1, expected_value1)]);
583+
}
584+
585+
// This is testing if closing an fd that is already in ready list will cause an empty entry in
586+
// returned notification.
587+
// Related discussion in https://github.com/rust-lang/miri/pull/3818#discussion_r1720679440.
588+
fn test_ready_list_fetching_logic() {
589+
// Create an epoll instance.
590+
let epfd = unsafe { libc::epoll_create1(0) };
591+
assert_ne!(epfd, -1);
592+
593+
// Create two eventfd instances.
594+
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
595+
let fd0 = unsafe { libc::eventfd(0, flags) };
596+
let fd1 = unsafe { libc::eventfd(0, flags) };
597+
598+
// Register both fd to the same epoll instance. At this point, both of them are on the ready list.
599+
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd0 as u64 };
600+
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd0, &mut ev) };
601+
assert_eq!(res, 0);
602+
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd1 as u64 };
603+
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd1, &mut ev) };
604+
assert_eq!(res, 0);
605+
606+
// Close fd0 so the first entry in the ready list will be empty.
607+
let res = unsafe { libc::close(fd0) };
608+
assert_eq!(res, 0);
609+
610+
// Notification for fd1 should be returned.
611+
let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
612+
let expected_value1 = fd1 as u64;
613+
check_epoll_wait::<1>(epfd, &[(expected_event1, expected_value1)]);
614+
}

0 commit comments

Comments
 (0)