Skip to content

Commit dbfd066

Browse files
committed
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`
2 parents 3a9e63c + 23f308b commit dbfd066

File tree

4 files changed

+105
-24
lines changed

4 files changed

+105
-24
lines changed

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

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
269269
let mut interest_list = epoll_file_description.interest_list.borrow_mut();
270270
let ready_list = &epoll_file_description.ready_list;
271271

272-
let Some(file_descriptor) = this.machine.fds.get(fd) else {
272+
let Some(fd_ref) = this.machine.fds.get(fd) else {
273273
return Ok(Scalar::from_i32(this.fd_not_found()?));
274274
};
275-
let id = file_descriptor.get_id();
275+
let id = fd_ref.get_id();
276276

277277
if op == epoll_ctl_add || op == epoll_ctl_mod {
278278
// Read event bitmask and data from epoll_event passed by caller.
@@ -332,7 +332,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
332332
}
333333
}
334334

335-
let id = file_descriptor.get_id();
336335
// Create an epoll_interest.
337336
let interest = Rc::new(RefCell::new(EpollEventInterest {
338337
file_descriptor: fd,
@@ -344,7 +343,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
344343
if op == epoll_ctl_add {
345344
// Insert an epoll_interest to global epoll_interest list.
346345
this.machine.epoll_interests.insert_epoll_interest(id, Rc::downgrade(&interest));
347-
interest_list.insert(epoll_key, interest);
346+
interest_list.insert(epoll_key, Rc::clone(&interest));
348347
} else {
349348
// Directly modify the epoll_interest so the global epoll_event_interest table
350349
// will be updated too.
@@ -353,9 +352,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
353352
epoll_interest.data = data;
354353
}
355354

356-
// Readiness will be updated immediately when the epoll_event_interest is added or modified.
357-
this.check_and_update_readiness(&file_descriptor)?;
358-
355+
// Notification will be returned for current epfd if there is event in the file
356+
// descriptor we registered.
357+
check_and_update_one_event_interest(&fd_ref, interest, id, this)?;
359358
return Ok(Scalar::from_i32(0));
360359
} else if op == epoll_ctl_del {
361360
let epoll_key = (id, fd);
@@ -489,25 +488,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
489488
let id = fd_ref.get_id();
490489
// Get a list of EpollEventInterest that is associated to a specific file description.
491490
if let Some(epoll_interests) = this.machine.epoll_interests.get_epoll_interest(id) {
492-
let epoll_ready_events = fd_ref.get_epoll_ready_events()?;
493-
// Get the bitmask of ready events.
494-
let ready_events = epoll_ready_events.get_event_bitmask(this);
495-
496491
for weak_epoll_interest in epoll_interests {
497492
if let Some(epoll_interest) = weak_epoll_interest.upgrade() {
498-
// This checks if any of the events specified in epoll_event_interest.events
499-
// match those in ready_events.
500-
let epoll_event_interest = epoll_interest.borrow();
501-
let flags = epoll_event_interest.events & ready_events;
502-
// If there is any event that we are interested in being specified as ready,
503-
// insert an epoll_return to the ready list.
504-
if flags != 0 {
505-
let epoll_key = (id, epoll_event_interest.file_descriptor);
506-
let ready_list = &mut epoll_event_interest.ready_list.borrow_mut();
507-
let event_instance =
508-
EpollEventInstance::new(flags, epoll_event_interest.data);
509-
ready_list.insert(epoll_key, event_instance);
510-
}
493+
check_and_update_one_event_interest(fd_ref, epoll_interest, id, this)?;
511494
}
512495
}
513496
}
@@ -532,3 +515,30 @@ fn ready_list_next(
532515
}
533516
return None;
534517
}
518+
519+
/// This helper function checks whether an epoll notification should be triggered for a specific
520+
/// epoll_interest and, if necessary, triggers the notification. Unlike check_and_update_readiness,
521+
/// this function sends a notification to only one epoll instance.
522+
fn check_and_update_one_event_interest<'tcx>(
523+
fd_ref: &FileDescriptionRef,
524+
interest: Rc<RefCell<EpollEventInterest>>,
525+
id: FdId,
526+
ecx: &MiriInterpCx<'tcx>,
527+
) -> InterpResult<'tcx> {
528+
// Get the bitmask of ready events for a file description.
529+
let ready_events_bitmask = fd_ref.get_epoll_ready_events()?.get_event_bitmask(ecx);
530+
let epoll_event_interest = interest.borrow();
531+
// This checks if any of the events specified in epoll_event_interest.events
532+
// match those in ready_events.
533+
let flags = epoll_event_interest.events & ready_events_bitmask;
534+
// If there is any event that we are interested in being specified as ready,
535+
// insert an epoll_return to the ready list.
536+
if flags != 0 {
537+
let epoll_key = (id, epoll_event_interest.file_descriptor);
538+
let ready_list = &mut epoll_event_interest.ready_list.borrow_mut();
539+
let event_instance = EpollEventInstance::new(flags, epoll_event_interest.data);
540+
// Triggers the notification by inserting it to the ready list.
541+
ready_list.insert(epoll_key, event_instance);
542+
}
543+
Ok(())
544+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//@only-target-linux
2+
3+
// This is a test for registering unsupported fd with epoll.
4+
// Register epoll fd with epoll is allowed in real system, but we do not support this.
5+
fn main() {
6+
// Create two epoll instance.
7+
let epfd0 = unsafe { libc::epoll_create1(0) };
8+
assert_ne!(epfd0, -1);
9+
let epfd1 = unsafe { libc::epoll_create1(0) };
10+
assert_ne!(epfd1, -1);
11+
12+
// Register epoll with epoll.
13+
let mut ev =
14+
libc::epoll_event { events: (libc::EPOLLIN | libc::EPOLLET) as _, u64: epfd1 as u64 };
15+
let res = unsafe { libc::epoll_ctl(epfd0, libc::EPOLL_CTL_ADD, epfd1, &mut ev) };
16+
//~^ERROR: epoll does not support this file description
17+
assert_eq!(res, 0);
18+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: unsupported operation: epoll: epoll does not support this file description
2+
--> $DIR/libc_epoll_unsupported_fd.rs:LL:CC
3+
|
4+
LL | let res = unsafe { libc::epoll_ctl(epfd0, libc::EPOLL_CTL_ADD, epfd1, &mut ev) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ epoll: epoll does not support this file description
6+
|
7+
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
8+
= note: BACKTRACE:
9+
= note: inside `main` at $DIR/libc_epoll_unsupported_fd.rs:LL:CC
10+
11+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
12+
13+
error: aborting due to 1 previous error
14+

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ fn main() {
2222
test_epoll_lost_events();
2323
test_ready_list_fetching_logic();
2424
test_epoll_ctl_epfd_equal_fd();
25+
test_epoll_ctl_notification();
2526
}
2627

2728
// Using `as` cast since `EPOLLET` wraps around
@@ -644,3 +645,41 @@ fn test_epoll_ctl_epfd_equal_fd() {
644645
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
645646
assert_eq!(res, -1);
646647
}
648+
649+
// We previously used check_and_update_readiness the moment a file description is registered in an
650+
// epoll instance. But this has an unfortunate side effect of returning notification to another
651+
// epfd that shouldn't receive notification.
652+
fn test_epoll_ctl_notification() {
653+
// Create an epoll instance.
654+
let epfd0 = unsafe { libc::epoll_create1(0) };
655+
assert_ne!(epfd0, -1);
656+
657+
// Create a socketpair instance.
658+
let mut fds = [-1, -1];
659+
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
660+
assert_eq!(res, 0);
661+
662+
// Register one side of the socketpair with epoll.
663+
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
664+
let res = unsafe { libc::epoll_ctl(epfd0, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
665+
assert_eq!(res, 0);
666+
667+
// epoll_wait to clear notification for epfd0.
668+
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
669+
let expected_value = fds[0] as u64;
670+
check_epoll_wait::<1>(epfd0, &[(expected_event, expected_value)]);
671+
672+
// Create another epoll instance.
673+
let epfd1 = unsafe { libc::epoll_create1(0) };
674+
assert_ne!(epfd1, -1);
675+
676+
// Register the same file description for epfd1.
677+
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
678+
let res = unsafe { libc::epoll_ctl(epfd1, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
679+
assert_eq!(res, 0);
680+
check_epoll_wait::<1>(epfd1, &[(expected_event, expected_value)]);
681+
682+
// Previously this epoll_wait will receive a notification, but we shouldn't return notification
683+
// for this epfd, because there is no I/O event between the two epoll_wait.
684+
check_epoll_wait::<1>(epfd0, &[]);
685+
}

0 commit comments

Comments
 (0)