Skip to content

Commit 3504096

Browse files
authored
Merge pull request rust-lang#5071 from WhySoBad/level-triggered-epoll
Add support for level-triggered epoll
2 parents 8bacbc1 + b465d66 commit 3504096

3 files changed

Lines changed: 350 additions & 256 deletions

File tree

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

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::cell::RefCell;
2-
use std::collections::{BTreeMap, BTreeSet, VecDeque};
2+
use std::collections::{BTreeMap, VecDeque};
33
use std::io;
4-
use std::ops::Bound;
54
use std::time::Duration;
65

76
use rustc_abi::FieldIdx;
@@ -23,7 +22,9 @@ pub struct Epoll {
2322
interest_list: RefCell<BTreeMap<EpollEventKey, EpollEventInterest>>,
2423
/// The subset of interests that is currently considered "ready". Stored separately so we
2524
/// can access it more efficiently.
26-
ready_set: RefCell<BTreeSet<EpollEventKey>>,
25+
/// This is implemented as a queue so that for level-triggered epoll, all events eventually
26+
/// get returned from `epoll_wait`. The queue does not contain any duplicates.
27+
ready_events: RefCell<VecDeque<EpollEventKey>>,
2728
/// The queue of threads blocked on this epoll instance.
2829
queue: RefCell<VecDeque<ThreadId>>,
2930
}
@@ -46,6 +47,9 @@ pub struct EpollEventInterest {
4647
relevant_events: u32,
4748
/// The currently active events for this file descriptor.
4849
active_events: u32,
50+
/// Boolean whether this is an edge-triggered interest.
51+
/// When [`false`] it's a level-triggered interest instead.
52+
is_edge_triggered: bool,
4953
/// The vector clock for wakeups.
5054
clock: VClock,
5155
/// User-defined data associated with this interest.
@@ -203,12 +207,8 @@ impl EpollInterestTable {
203207
.extract_if(range_for_id(id), |_, _| true)
204208
// Consume the iterator.
205209
.for_each(drop);
206-
epoll
207-
.ready_set
208-
.borrow_mut()
209-
.extract_if(range_for_id(id), |_| true)
210-
// Consume the iterator.
211-
.for_each(drop);
210+
// Remove the ready events for this file description.
211+
epoll.ready_events.borrow_mut().retain(|(fd_id, _)| fd_id != &id);
212212
}
213213
}
214214
}
@@ -303,6 +303,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
303303
this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?;
304304
let data = this.read_scalar(&this.project_field(&event, FieldIdx::ONE)?)?.to_u64()?;
305305

306+
let is_edge_triggered = if events & epollet == epollet {
307+
events &= !epollet;
308+
true
309+
} else {
310+
false
311+
};
312+
306313
// Unset the flag we support to discover if any unsupported flags are used.
307314
let mut flags = events;
308315
// epoll_wait(2) will always wait for epollhup and epollerr; it is not
@@ -311,12 +318,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
311318
events |= epollhup;
312319
events |= epollerr;
313320

314-
if events & epollet != epollet {
315-
// We only support edge-triggered notification for now.
316-
throw_unsup_format!("epoll_ctl: epollet flag must be included.");
317-
} else {
318-
flags &= !epollet;
319-
}
320321
if flags & epollin == epollin {
321322
flags &= !epollin;
322323
}
@@ -350,6 +351,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
350351
}
351352
let new_interest = EpollEventInterest {
352353
relevant_events: events,
354+
is_edge_triggered,
353355
data,
354356
active_events: 0,
355357
clock: VClock::default(),
@@ -364,6 +366,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
364366
return this.set_errno_and_return_neg1_i32(LibcError("ENOENT"));
365367
};
366368
interest.relevant_events = events;
369+
interest.is_edge_triggered = is_edge_triggered;
367370
interest.data = data;
368371
}
369372

@@ -391,7 +394,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
391394
// We did not have interest in this.
392395
return this.set_errno_and_return_neg1_i32(LibcError("ENOENT"));
393396
};
394-
epfd.ready_set.borrow_mut().remove(&epoll_key);
397+
// Remove the ready event for this key, should one exist.
398+
let mut ready_events = epfd.ready_events.borrow_mut();
399+
if let Some(idx) = ready_events.iter().position(|k| k == &epoll_key) {
400+
ready_events.remove(idx);
401+
}
395402
// If this was the last interest in this FD, remove us from the global list
396403
// of who is interested in this FD.
397404
if interest_list.range(range_for_id(id)).next().is_none() {
@@ -469,7 +476,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
469476
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
470477
};
471478

472-
if timeout == 0 || !epfd.ready_set.borrow().is_empty() {
479+
if timeout == 0 || !epfd.ready_events.borrow().is_empty() {
473480
// If the timeout is 0 or there is a ready event, we can return immediately.
474481
return_ready_list(&epfd, dest, &event, this)?;
475482
} else {
@@ -590,31 +597,42 @@ fn update_readiness<'tcx>(
590597
&mut dyn FnMut(EpollEventKey, &mut EpollEventInterest) -> InterpResult<'tcx>,
591598
) -> InterpResult<'tcx>,
592599
) -> InterpResult<'tcx> {
593-
let mut ready_set = epoll.ready_set.borrow_mut();
600+
let mut ready_events = epoll.ready_events.borrow_mut();
594601
for_each_interest(&mut |key, interest| {
595602
// Update the ready events tracked in this interest.
596603
let new_readiness = interest.relevant_events & active_events;
597604
let prev_readiness = std::mem::replace(&mut interest.active_events, new_readiness);
598605
if new_readiness == 0 {
599606
// Un-trigger this, there's nothing left to report here.
600-
ready_set.remove(&key);
607+
if let Some(idx) = ready_events.iter().position(|k| k == &key) {
608+
ready_events.remove(idx);
609+
}
601610
} else if force_edge || new_readiness != prev_readiness & new_readiness {
602-
// Either we force an "edge" to be detected, or there's a bit set in `new`
603-
// that was not set in `prev`. In both cases, this is ready now.
604-
ready_set.insert(key);
611+
// Either we force an "edge" to be detected or there's a bit set in `new_readiness`
612+
// that was not set in `prev_readiness`. In both cases, this is ready now.
613+
614+
// We need to ensure that this event is not already part of the
615+
// `ready_events` queue before enqueueing:
616+
// <https://github.com/torvalds/linux/blob/HEAD/fs/eventpoll.c#L1292-L1296>
617+
if !ready_events.contains(&key) {
618+
ready_events.push_back(key);
619+
}
620+
621+
// No matter whether this is newly ready or just re-triggered,
622+
// the `epoll_wait` fetching this event should sync with the current thread.
605623
ecx.release_clock(|clock| {
606624
interest.clock.join(clock);
607625
})?;
608626
}
609627
interp_ok(())
610628
})?;
611629
// While there are events ready to be delivered, wake up a thread to receive them.
612-
while !ready_set.is_empty()
630+
while !ready_events.is_empty()
613631
&& let Some(thread_id) = epoll.queue.borrow_mut().pop_front()
614632
{
615-
drop(ready_set); // release the "lock" so the unblocked thread can have it
633+
drop(ready_events); // release the "lock" so the unblocked thread can have it
616634
ecx.unblock_thread(thread_id, BlockReason::Epoll { epfd: epoll.clone() })?;
617-
ready_set = epoll.ready_set.borrow_mut();
635+
ready_events = epoll.ready_events.borrow_mut();
618636
}
619637

620638
interp_ok(())
@@ -629,7 +647,7 @@ fn return_ready_list<'tcx>(
629647
ecx: &mut MiriInterpCx<'tcx>,
630648
) -> InterpResult<'tcx, i32> {
631649
let mut interest_list = epfd.interest_list.borrow_mut();
632-
let mut ready_set = epfd.ready_set.borrow_mut();
650+
let mut ready_events = epfd.ready_events.borrow_mut();
633651
let mut num_of_events: i32 = 0;
634652
let mut array_iter = ecx.project_array_fields(events)?;
635653

@@ -644,27 +662,29 @@ fn return_ready_list<'tcx>(
644662
}
645663
}
646664

647-
// While there is a slot to store another event, and an event to store, deliver that event.
648-
// We can't use an iterator over `ready_set` as we want to remove elements as we go,
649-
// so we track the most recently delivered event to find the next one. We track it as a lower
650-
// bound that we can pass to `BTreeSet::range`.
651-
let mut event_lower_bound = Bound::Unbounded;
652-
while let Some(slot) = array_iter.next(ecx)?
653-
&& let Some(&key) = ready_set.range((event_lower_bound, Bound::Unbounded)).next()
665+
// We will fill at most the first `ready_events_len` slots of the array.
666+
// Bounding the iterator this way ensures that we can re-add events
667+
// to the end of the queue during the loop without having them show up in the array.
668+
let ready_events_len = u64::try_from(ready_events.len()).unwrap();
669+
while let Some((idx, slot)) = array_iter.next(ecx)?
670+
&& idx < ready_events_len
671+
&& let Some(key) = ready_events.pop_front()
654672
{
655673
let interest = interest_list.get_mut(&key).expect("non-existent event in ready set");
656674
// Deliver event to caller.
657675
ecx.write_int_fields_named(
658676
&[("events", interest.active_events.into()), ("u64", interest.data.into())],
659-
&slot.1,
677+
&slot,
660678
)?;
661679
num_of_events = num_of_events.strict_add(1);
662680
// Synchronize receiving thread with the event of interest.
663681
ecx.acquire_clock(&interest.clock)?;
664-
// This was an edge-triggered event, so remove it from the ready set.
665-
ready_set.remove(&key);
666-
// Go find the next event.
667-
event_lower_bound = Bound::Excluded(key);
682+
if !interest.is_edge_triggered {
683+
// This is a level-triggered interest, so we need to re-add the event
684+
// at the end of the ready queue:
685+
// <https://github.com/torvalds/linux/blob/HEAD/fs/eventpoll.c#L1835-L1847>
686+
ready_events.push_back(key);
687+
}
668688
}
669689
ecx.write_int(num_of_events, dest)?;
670690
interp_ok(num_of_events)

0 commit comments

Comments
 (0)