miri/shims/unix/linux_like/epoll.rs
1use std::cell::RefCell;
2use std::collections::{BTreeMap, BTreeSet, VecDeque};
3use std::io;
4use std::ops::Bound;
5use std::time::Duration;
6
7use rustc_abi::FieldIdx;
8
9use crate::concurrency::VClock;
10use crate::shims::files::{
11 DynFileDescriptionRef, FdId, FdNum, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
12};
13use crate::shims::unix::UnixFileDescription;
14use crate::*;
15
16type EpollEventKey = (FdId, FdNum);
17
18/// An `Epoll` file descriptor connects file handles and epoll events
19#[derive(Debug, Default)]
20struct Epoll {
21 /// A map of EpollEventInterests registered under this epoll instance. Each entry is
22 /// differentiated using FdId and file descriptor value.
23 interest_list: RefCell<BTreeMap<EpollEventKey, EpollEventInterest>>,
24 /// The subset of interests that is currently considered "ready". Stored separately so we
25 /// can access it more efficiently.
26 ready_set: RefCell<BTreeSet<EpollEventKey>>,
27 /// The queue of threads blocked on this epoll instance.
28 queue: RefCell<VecDeque<ThreadId>>,
29}
30
31impl VisitProvenance for Epoll {
32 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
33 // No provenance anywhere in this type.
34 }
35}
36
37/// Returns the range of all EpollEventKey for the given FD ID.
38fn range_for_id(id: FdId) -> std::ops::RangeInclusive<EpollEventKey> {
39 (id, 0)..=(id, i32::MAX)
40}
41
42/// Tracks the events that this epoll is interested in for a given file descriptor.
43#[derive(Debug)]
44pub struct EpollEventInterest {
45 /// The events bitmask the epoll is interested in.
46 relevant_events: u32,
47 /// The currently active events for this file descriptor.
48 active_events: u32,
49 /// The vector clock for wakeups.
50 clock: VClock,
51 /// User-defined data associated with this interest.
52 /// libc's data field in epoll_event can store integer or pointer,
53 /// but only u64 is supported for now.
54 /// <https://man7.org/linux/man-pages/man3/epoll_event.3type.html>
55 data: u64,
56}
57
58/// EpollReadyEvents reflects the readiness of a file description.
59#[derive(Debug)]
60pub struct EpollEvents {
61 /// The associated file is available for read(2) operations, in the sense that a read will not block.
62 /// (I.e., returning EOF is considered "ready".)
63 pub epollin: bool,
64 /// The associated file is available for write(2) operations, in the sense that a write will not block.
65 pub epollout: bool,
66 /// Stream socket peer closed connection, or shut down writing
67 /// half of connection.
68 pub epollrdhup: bool,
69 /// For stream socket, this event merely indicates that the peer
70 /// closed its end of the channel.
71 /// Unlike epollrdhup, this should only be set when the stream is fully closed.
72 /// epollrdhup also gets set when only the write half is closed, which is possible
73 /// via `shutdown(_, SHUT_WR)`.
74 pub epollhup: bool,
75 /// Error condition happened on the associated file descriptor.
76 pub epollerr: bool,
77}
78
79impl EpollEvents {
80 pub fn new() -> Self {
81 EpollEvents {
82 epollin: false,
83 epollout: false,
84 epollrdhup: false,
85 epollhup: false,
86 epollerr: false,
87 }
88 }
89
90 pub fn get_event_bitmask<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> u32 {
91 let epollin = ecx.eval_libc_u32("EPOLLIN");
92 let epollout = ecx.eval_libc_u32("EPOLLOUT");
93 let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
94 let epollhup = ecx.eval_libc_u32("EPOLLHUP");
95 let epollerr = ecx.eval_libc_u32("EPOLLERR");
96
97 let mut bitmask = 0;
98 if self.epollin {
99 bitmask |= epollin;
100 }
101 if self.epollout {
102 bitmask |= epollout;
103 }
104 if self.epollrdhup {
105 bitmask |= epollrdhup;
106 }
107 if self.epollhup {
108 bitmask |= epollhup;
109 }
110 if self.epollerr {
111 bitmask |= epollerr;
112 }
113 bitmask
114 }
115}
116
117impl FileDescription for Epoll {
118 fn name(&self) -> &'static str {
119 "epoll"
120 }
121
122 fn destroy<'tcx>(
123 mut self,
124 self_id: FdId,
125 _communicate_allowed: bool,
126 ecx: &mut MiriInterpCx<'tcx>,
127 ) -> InterpResult<'tcx, io::Result<()>> {
128 // If we were interested in some FDs, we can remove that now.
129 let mut ids = self.interest_list.get_mut().keys().map(|(id, _num)| *id).collect::<Vec<_>>();
130 ids.dedup(); // they come out of the map sorted
131 for id in ids {
132 ecx.machine.epoll_interests.remove(id, self_id);
133 }
134 interp_ok(Ok(()))
135 }
136
137 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
138 self
139 }
140}
141
142impl UnixFileDescription for Epoll {}
143
144/// The table of all EpollEventInterest.
145/// This tracks, for each file description, which epoll instances have an interest in events
146/// for this file description. The `FdId` is the ID of the epoll instance, so that we can recognize
147/// it later when it is slated for removal. The vector is sorted by that ID.
148pub struct EpollInterestTable(BTreeMap<FdId, Vec<(FdId, WeakFileDescriptionRef<Epoll>)>>);
149
150impl EpollInterestTable {
151 pub(crate) fn new() -> Self {
152 EpollInterestTable(BTreeMap::new())
153 }
154
155 fn insert(&mut self, id: FdId, epoll: &FileDescriptionRef<Epoll>) {
156 let epolls = self.0.entry(id).or_default();
157 let idx = epolls
158 .binary_search_by_key(&epoll.id(), |&(id, _)| id)
159 .expect_err("trying to add an epoll that's already in the list");
160 epolls.insert(idx, (epoll.id(), FileDescriptionRef::downgrade(epoll)));
161 }
162
163 fn remove(&mut self, id: FdId, epoll_id: FdId) {
164 let epolls = self.0.entry(id).or_default();
165 let idx = epolls
166 .binary_search_by_key(&epoll_id, |&(id, _)| id)
167 .expect("trying to remove an epoll that's not in the list");
168 epolls.remove(idx);
169 }
170
171 fn get_epolls(&self, id: FdId) -> Option<impl Iterator<Item = &WeakFileDescriptionRef<Epoll>>> {
172 self.0.get(&id).map(|epolls| epolls.iter().map(|(_id, epoll)| epoll))
173 }
174
175 pub fn remove_epolls(&mut self, id: FdId) {
176 if let Some(epolls) = self.0.remove(&id) {
177 for epoll in epolls.iter().filter_map(|(_id, epoll)| epoll.upgrade()) {
178 // This is a still-live epoll with interest in this FD. Remove all
179 // relevent interests (including from the ready set).
180 epoll
181 .interest_list
182 .borrow_mut()
183 .extract_if(range_for_id(id), |_, _| true)
184 // Consume the iterator.
185 .for_each(drop);
186 epoll
187 .ready_set
188 .borrow_mut()
189 .extract_if(range_for_id(id), |_| true)
190 // Consume the iterator.
191 .for_each(drop);
192 }
193 }
194 }
195}
196
197impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
198pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
199 /// This function returns a file descriptor referring to the new `Epoll` instance. This file
200 /// descriptor is used for all subsequent calls to the epoll interface. If the `flags` argument
201 /// is 0, then this function is the same as `epoll_create()`.
202 ///
203 /// <https://linux.die.net/man/2/epoll_create1>
204 fn epoll_create1(&mut self, flags: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
205 let this = self.eval_context_mut();
206
207 let flags = this.read_scalar(flags)?.to_i32()?;
208
209 let epoll_cloexec = this.eval_libc_i32("EPOLL_CLOEXEC");
210
211 // Miri does not support exec, so EPOLL_CLOEXEC flag has no effect.
212 if flags != epoll_cloexec && flags != 0 {
213 throw_unsup_format!(
214 "epoll_create1: flag {:#x} is unsupported, only 0 or EPOLL_CLOEXEC are allowed",
215 flags
216 );
217 }
218
219 let fd = this.machine.fds.insert_new(Epoll::default());
220 interp_ok(Scalar::from_i32(fd))
221 }
222
223 /// This function performs control operations on the `Epoll` instance referred to by the file
224 /// descriptor `epfd`. It requests that the operation `op` be performed for the target file
225 /// descriptor, `fd`.
226 ///
227 /// Valid values for the op argument are:
228 /// `EPOLL_CTL_ADD` - Register the target file descriptor `fd` on the `Epoll` instance referred
229 /// to by the file descriptor `epfd` and associate the event `event` with the internal file
230 /// linked to `fd`.
231 /// `EPOLL_CTL_MOD` - Change the event `event` associated with the target file descriptor `fd`.
232 /// `EPOLL_CTL_DEL` - Deregister the target file descriptor `fd` from the `Epoll` instance
233 /// referred to by `epfd`. The `event` is ignored and can be null.
234 ///
235 /// <https://linux.die.net/man/2/epoll_ctl>
236 fn epoll_ctl(
237 &mut self,
238 epfd: &OpTy<'tcx>,
239 op: &OpTy<'tcx>,
240 fd: &OpTy<'tcx>,
241 event: &OpTy<'tcx>,
242 ) -> InterpResult<'tcx, Scalar> {
243 let this = self.eval_context_mut();
244
245 let epfd_value = this.read_scalar(epfd)?.to_i32()?;
246 let op = this.read_scalar(op)?.to_i32()?;
247 let fd = this.read_scalar(fd)?.to_i32()?;
248 let event = this.deref_pointer_as(event, this.libc_ty_layout("epoll_event"))?;
249
250 let epoll_ctl_add = this.eval_libc_i32("EPOLL_CTL_ADD");
251 let epoll_ctl_mod = this.eval_libc_i32("EPOLL_CTL_MOD");
252 let epoll_ctl_del = this.eval_libc_i32("EPOLL_CTL_DEL");
253 let epollin = this.eval_libc_u32("EPOLLIN");
254 let epollout = this.eval_libc_u32("EPOLLOUT");
255 let epollrdhup = this.eval_libc_u32("EPOLLRDHUP");
256 let epollet = this.eval_libc_u32("EPOLLET");
257 let epollhup = this.eval_libc_u32("EPOLLHUP");
258 let epollerr = this.eval_libc_u32("EPOLLERR");
259
260 // Throw EFAULT if epfd and fd have the same value.
261 if epfd_value == fd {
262 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
263 }
264
265 // Check if epfd is a valid epoll file descriptor.
266 let Some(epfd) = this.machine.fds.get(epfd_value) else {
267 return this.set_last_error_and_return_i32(LibcError("EBADF"));
268 };
269 let epfd = epfd
270 .downcast::<Epoll>()
271 .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
272
273 let mut interest_list = epfd.interest_list.borrow_mut();
274
275 let Some(fd_ref) = this.machine.fds.get(fd) else {
276 return this.set_last_error_and_return_i32(LibcError("EBADF"));
277 };
278 let id = fd_ref.id();
279
280 if op == epoll_ctl_add || op == epoll_ctl_mod {
281 // Read event bitmask and data from epoll_event passed by caller.
282 let mut events =
283 this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?;
284 let data = this.read_scalar(&this.project_field(&event, FieldIdx::ONE)?)?.to_u64()?;
285
286 // Unset the flag we support to discover if any unsupported flags are used.
287 let mut flags = events;
288 // epoll_wait(2) will always wait for epollhup and epollerr; it is not
289 // necessary to set it in events when calling epoll_ctl().
290 // So we will always set these two event types.
291 events |= epollhup;
292 events |= epollerr;
293
294 if events & epollet != epollet {
295 // We only support edge-triggered notification for now.
296 throw_unsup_format!("epoll_ctl: epollet flag must be included.");
297 } else {
298 flags &= !epollet;
299 }
300 if flags & epollin == epollin {
301 flags &= !epollin;
302 }
303 if flags & epollout == epollout {
304 flags &= !epollout;
305 }
306 if flags & epollrdhup == epollrdhup {
307 flags &= !epollrdhup;
308 }
309 if flags & epollhup == epollhup {
310 flags &= !epollhup;
311 }
312 if flags & epollerr == epollerr {
313 flags &= !epollerr;
314 }
315 if flags != 0 {
316 throw_unsup_format!(
317 "epoll_ctl: encountered unknown unsupported flags {:#x}",
318 flags
319 );
320 }
321
322 // Add new interest to list. Experiments show that we need to reset all state
323 // on `EPOLL_CTL_MOD`, including the edge tracking.
324 let epoll_key = (id, fd);
325 if op == epoll_ctl_add {
326 if interest_list.range(range_for_id(id)).next().is_none() {
327 // This is the first time this FD got added to this epoll.
328 // Remember that in the global list so we get notified about FD events.
329 this.machine.epoll_interests.insert(id, &epfd);
330 }
331 let new_interest = EpollEventInterest {
332 relevant_events: events,
333 data,
334 active_events: 0,
335 clock: VClock::default(),
336 };
337 if interest_list.try_insert(epoll_key, new_interest).is_err() {
338 // We already had interest in this.
339 return this.set_last_error_and_return_i32(LibcError("EEXIST"));
340 }
341 } else {
342 // Modify the existing interest.
343 let Some(interest) = interest_list.get_mut(&epoll_key) else {
344 return this.set_last_error_and_return_i32(LibcError("ENOENT"));
345 };
346 interest.relevant_events = events;
347 interest.data = data;
348 }
349
350 // Deliver events for the new interest.
351 update_readiness(
352 this,
353 &epfd,
354 fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this),
355 /* force_edge */ true,
356 move |callback| {
357 // Need to release the RefCell when this closure returns, so we have to move
358 // it into the closure, so we have to do a re-lookup here.
359 callback(epoll_key, interest_list.get_mut(&epoll_key).unwrap())
360 },
361 )?;
362
363 interp_ok(Scalar::from_i32(0))
364 } else if op == epoll_ctl_del {
365 let epoll_key = (id, fd);
366
367 // Remove epoll_event_interest from interest_list and ready_set.
368 if interest_list.remove(&epoll_key).is_none() {
369 // We did not have interest in this.
370 return this.set_last_error_and_return_i32(LibcError("ENOENT"));
371 };
372 epfd.ready_set.borrow_mut().remove(&epoll_key);
373 // If this was the last interest in this FD, remove us from the global list
374 // of who is interested in this FD.
375 if interest_list.range(range_for_id(id)).next().is_none() {
376 this.machine.epoll_interests.remove(id, epfd.id());
377 }
378
379 interp_ok(Scalar::from_i32(0))
380 } else {
381 throw_unsup_format!("unsupported epoll_ctl operation: {op}");
382 }
383 }
384
385 /// The `epoll_wait()` system call waits for events on the `Epoll`
386 /// instance referred to by the file descriptor `epfd`. The buffer
387 /// pointed to by `events` is used to return information from the ready
388 /// list about file descriptors in the interest list that have some
389 /// events available. Up to `maxevents` are returned by `epoll_wait()`.
390 /// The `maxevents` argument must be greater than zero.
391 ///
392 /// The `timeout` argument specifies the number of milliseconds that
393 /// `epoll_wait()` will block. Time is measured against the
394 /// CLOCK_MONOTONIC clock. If the timeout is zero, the function will not block,
395 /// while if the timeout is -1, the function will block
396 /// until at least one event has been retrieved (or an error
397 /// occurred).
398 ///
399 /// A call to `epoll_wait()` will block until either:
400 /// • a file descriptor delivers an event;
401 /// • the call is interrupted by a signal handler; or
402 /// • the timeout expires.
403 ///
404 /// Note that the timeout interval will be rounded up to the system
405 /// clock granularity, and kernel scheduling delays mean that the
406 /// blocking interval may overrun by a small amount. Specifying a
407 /// timeout of -1 causes `epoll_wait()` to block indefinitely, while
408 /// specifying a timeout equal to zero cause `epoll_wait()` to return
409 /// immediately, even if no events are available.
410 ///
411 /// On success, `epoll_wait()` returns the number of file descriptors
412 /// ready for the requested I/O, or zero if no file descriptor became
413 /// ready during the requested timeout milliseconds. On failure,
414 /// `epoll_wait()` returns -1 and errno is set to indicate the error.
415 ///
416 /// <https://man7.org/linux/man-pages/man2/epoll_wait.2.html>
417 fn epoll_wait(
418 &mut self,
419 epfd: &OpTy<'tcx>,
420 events_op: &OpTy<'tcx>,
421 maxevents: &OpTy<'tcx>,
422 timeout: &OpTy<'tcx>,
423 dest: &MPlaceTy<'tcx>,
424 ) -> InterpResult<'tcx> {
425 let this = self.eval_context_mut();
426
427 let epfd_value = this.read_scalar(epfd)?.to_i32()?;
428 let events = this.read_immediate(events_op)?;
429 let maxevents = this.read_scalar(maxevents)?.to_i32()?;
430 let timeout = this.read_scalar(timeout)?.to_i32()?;
431
432 if epfd_value <= 0 || maxevents <= 0 {
433 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
434 }
435
436 // This needs to come after the maxevents value check, or else maxevents.try_into().unwrap()
437 // will fail.
438 let event = this.deref_pointer_as(
439 &events,
440 this.libc_array_ty_layout("epoll_event", maxevents.try_into().unwrap()),
441 )?;
442
443 let Some(epfd) = this.machine.fds.get(epfd_value) else {
444 return this.set_last_error_and_return(LibcError("EBADF"), dest);
445 };
446 let Some(epfd) = epfd.downcast::<Epoll>() else {
447 return this.set_last_error_and_return(LibcError("EBADF"), dest);
448 };
449
450 if timeout == 0 || !epfd.ready_set.borrow().is_empty() {
451 // If the timeout is 0 or there is a ready event, we can return immediately.
452 return_ready_list(&epfd, dest, &event, this)?;
453 } else {
454 // Blocking
455 let timeout = match timeout {
456 0.. => {
457 let duration = Duration::from_millis(timeout.try_into().unwrap());
458 Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration))
459 }
460 -1 => None,
461 ..-1 => {
462 throw_unsup_format!(
463 "epoll_wait: Only timeout values greater than or equal to -1 are supported."
464 );
465 }
466 };
467 // Record this thread as blocked.
468 epfd.queue.borrow_mut().push_back(this.active_thread());
469 // And block it.
470 let dest = dest.clone();
471 // We keep a strong ref to the underlying `Epoll` to make sure it sticks around.
472 // This means there'll be a leak if we never wake up, but that anyway would imply
473 // a thread is permanently blocked so this is fine.
474 this.block_thread(
475 BlockReason::Epoll,
476 timeout,
477 callback!(
478 @capture<'tcx> {
479 epfd: FileDescriptionRef<Epoll>,
480 dest: MPlaceTy<'tcx>,
481 event: MPlaceTy<'tcx>,
482 }
483 |this, unblock: UnblockKind| {
484 match unblock {
485 UnblockKind::Ready => {
486 let events = return_ready_list(&epfd, &dest, &event, this)?;
487 assert!(events > 0, "we got woken up with no events to deliver");
488 interp_ok(())
489 },
490 UnblockKind::TimedOut => {
491 // Remove the current active thread_id from the blocked thread_id list.
492 epfd
493 .queue.borrow_mut()
494 .retain(|&id| id != this.active_thread());
495 this.write_int(0, &dest)?;
496 interp_ok(())
497 },
498 }
499 }
500 ),
501 );
502 }
503 interp_ok(())
504 }
505
506 /// For a specific file description, get its currently active events and send it to everyone who
507 /// registered interest in this FD. This function must be called whenever the result of
508 /// `epoll_active_events` might change.
509 ///
510 /// If `force_edge` is set, edge-triggered interests will be triggered even if the set of
511 /// ready events did not change. This can lead to spurious wakeups. Use with caution!
512 fn update_epoll_active_events(
513 &mut self,
514 fd_ref: DynFileDescriptionRef,
515 force_edge: bool,
516 ) -> InterpResult<'tcx> {
517 let this = self.eval_context_mut();
518 let id = fd_ref.id();
519 // Figure out who is interested in this. We need to clone this list since we can't prove
520 // that `send_active_events_to_interest` won't mutate it.
521 let Some(epolls) = this.machine.epoll_interests.get_epolls(id) else {
522 return interp_ok(());
523 };
524 let epolls = epolls
525 .map(|weak| {
526 weak.upgrade()
527 .expect("someone forgot to remove the garbage from `machine.epoll_interests`")
528 })
529 .collect::<Vec<_>>();
530 let active_events = fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this);
531 for epoll in epolls {
532 update_readiness(this, &epoll, active_events, force_edge, |callback| {
533 for (&key, interest) in epoll.interest_list.borrow_mut().range_mut(range_for_id(id))
534 {
535 callback(key, interest)?;
536 }
537 interp_ok(())
538 })?;
539 }
540
541 interp_ok(())
542 }
543}
544
545/// Call this when the interests denoted by `for_each_interest` have their active event set changed
546/// to `active_events`. The list is provided indirectly via the `for_each_interest` closure, which
547/// will call its argument closure for each relevant interest.
548///
549/// Any `RefCell` should be released by the time `for_each_interest` returns since we will then
550/// be waking up threads which might require access to those `RefCell`.
551fn update_readiness<'tcx>(
552 ecx: &mut MiriInterpCx<'tcx>,
553 epoll: &Epoll,
554 active_events: u32,
555 force_edge: bool,
556 for_each_interest: impl FnOnce(
557 &mut dyn FnMut(EpollEventKey, &mut EpollEventInterest) -> InterpResult<'tcx>,
558 ) -> InterpResult<'tcx>,
559) -> InterpResult<'tcx> {
560 let mut ready_set = epoll.ready_set.borrow_mut();
561 for_each_interest(&mut |key, interest| {
562 // Update the ready events tracked in this interest.
563 let new_readiness = interest.relevant_events & active_events;
564 let prev_readiness = std::mem::replace(&mut interest.active_events, new_readiness);
565 if new_readiness == 0 {
566 // Un-trigger this, there's nothing left to report here.
567 ready_set.remove(&key);
568 } else if force_edge || new_readiness != prev_readiness & new_readiness {
569 // Either we force an "edge" to be detected, or there's a bit set in `new`
570 // that was not set in `prev`. In both cases, this is ready now.
571 ready_set.insert(key);
572 ecx.release_clock(|clock| {
573 interest.clock.join(clock);
574 })?;
575 }
576 interp_ok(())
577 })?;
578 // While there are events ready to be delivered, wake up a thread to receive them.
579 while !ready_set.is_empty()
580 && let Some(thread_id) = epoll.queue.borrow_mut().pop_front()
581 {
582 drop(ready_set); // release the "lock" so the unblocked thread can have it
583 ecx.unblock_thread(thread_id, BlockReason::Epoll)?;
584 ready_set = epoll.ready_set.borrow_mut();
585 }
586
587 interp_ok(())
588}
589
590/// Stores the ready list of the `epfd` epoll instance into `events` (which must be an array),
591/// and the number of returned events into `dest`.
592fn return_ready_list<'tcx>(
593 epfd: &FileDescriptionRef<Epoll>,
594 dest: &MPlaceTy<'tcx>,
595 events: &MPlaceTy<'tcx>,
596 ecx: &mut MiriInterpCx<'tcx>,
597) -> InterpResult<'tcx, i32> {
598 let mut interest_list = epfd.interest_list.borrow_mut();
599 let mut ready_set = epfd.ready_set.borrow_mut();
600 let mut num_of_events: i32 = 0;
601 let mut array_iter = ecx.project_array_fields(events)?;
602
603 // Sanity-check to ensure that all event info is up-to-date.
604 if cfg!(debug_assertions) {
605 for (key, interest) in interest_list.iter() {
606 // Ensure this matches the latest readiness of this FD.
607 // We have to do an FD lookup by ID for this. The FdNum might be already closed.
608 let fd = &ecx.machine.fds.fds.values().find(|fd| fd.id() == key.0).unwrap();
609 let current_active = fd.as_unix(ecx).epoll_active_events()?.get_event_bitmask(ecx);
610 assert_eq!(interest.active_events, current_active & interest.relevant_events);
611 }
612 }
613
614 // While there is a slot to store another event, and an event to store, deliver that event.
615 // We can't use an iterator over `ready_set` as we want to remove elements as we go,
616 // so we track the most recently delivered event to find the next one. We track it as a lower
617 // bound that we can pass to `BTreeSet::range`.
618 let mut event_lower_bound = Bound::Unbounded;
619 while let Some(slot) = array_iter.next(ecx)?
620 && let Some(&key) = ready_set.range((event_lower_bound, Bound::Unbounded)).next()
621 {
622 let interest = interest_list.get_mut(&key).expect("non-existent event in ready set");
623 // Deliver event to caller.
624 ecx.write_int_fields_named(
625 &[("events", interest.active_events.into()), ("u64", interest.data.into())],
626 &slot.1,
627 )?;
628 num_of_events = num_of_events.strict_add(1);
629 // Synchronize receiving thread with the event of interest.
630 ecx.acquire_clock(&interest.clock)?;
631 // This was an edge-triggered event, so remove it from the ready set.
632 ready_set.remove(&key);
633 // Go find the next event.
634 event_lower_bound = Bound::Excluded(key);
635 }
636 ecx.write_int(num_of_events, dest)?;
637 interp_ok(num_of_events)
638}