miri/shims/unix/linux_like/
epoll.rs

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