miri/shims/unix/linux_like/
epoll.rs

1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::io;
4use std::rc::{Rc, Weak};
5use std::time::Duration;
6
7use rustc_abi::FieldIdx;
8
9use crate::concurrency::VClock;
10use crate::shims::files::{
11    DynFileDescriptionRef, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
12};
13use crate::shims::unix::UnixFileDescription;
14use crate::*;
15
16/// An `Epoll` file descriptor connects file handles and epoll events
17#[derive(Debug, Default)]
18struct Epoll {
19    /// A map of EpollEventInterests registered under this epoll instance.
20    /// Each entry is differentiated using FdId and file descriptor value.
21    interest_list: RefCell<BTreeMap<(FdId, i32), Rc<RefCell<EpollEventInterest>>>>,
22    /// A map of EpollEventInstance that will be returned when `epoll_wait` is called.
23    /// Similar to interest_list, the entry is also differentiated using FdId
24    /// and file descriptor value.
25    ready_list: ReadyList,
26    /// A list of thread ids blocked on this epoll instance.
27    blocked_tid: RefCell<Vec<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/// EpollEventInstance contains information that will be returned by epoll_wait.
37#[derive(Debug)]
38pub struct EpollEventInstance {
39    /// Xor-ed event types that happened to the file description.
40    events: u32,
41    /// Original data retrieved from `epoll_event` during `epoll_ctl`.
42    data: u64,
43    /// The release clock associated with this event.
44    clock: VClock,
45}
46
47impl EpollEventInstance {
48    pub fn new(events: u32, data: u64) -> EpollEventInstance {
49        EpollEventInstance { events, data, clock: Default::default() }
50    }
51}
52
53/// EpollEventInterest registers the file description information to an epoll
54/// instance during a successful `epoll_ctl` call. It also stores additional
55/// information needed to check and update readiness state for `epoll_wait`.
56///
57/// `events` and `data` field matches the `epoll_event` struct defined
58/// by the epoll_ctl man page. For more information
59/// see the man page:
60///
61/// <https://man7.org/linux/man-pages/man2/epoll_ctl.2.html>
62#[derive(Debug)]
63pub struct EpollEventInterest {
64    /// The file descriptor value of the file description registered.
65    /// This is only used for ready_list, to inform userspace which FD triggered an event.
66    /// For that, it is crucial to preserve the original FD number.
67    /// This FD number must never be "dereferenced" to a file description inside Miri.
68    fd_num: i32,
69    /// The events bitmask retrieved from `epoll_event`.
70    events: u32,
71    /// The data retrieved from `epoll_event`.
72    /// libc's data field in epoll_event can store integer or pointer,
73    /// but only u64 is supported for now.
74    /// <https://man7.org/linux/man-pages/man3/epoll_event.3type.html>
75    data: u64,
76    /// The epoll file description that this EpollEventInterest is registered under.
77    /// This is weak to avoid cycles, but an upgrade is always guaranteed to succeed
78    /// because only the `Epoll` holds a strong ref to a `EpollEventInterest`.
79    weak_epfd: WeakFileDescriptionRef<Epoll>,
80}
81
82/// EpollReadyEvents reflects the readiness of a file description.
83pub struct EpollReadyEvents {
84    /// The associated file is available for read(2) operations, in the sense that a read will not block.
85    /// (I.e., returning EOF is considered "ready".)
86    pub epollin: bool,
87    /// The associated file is available for write(2) operations, in the sense that a write will not block.
88    pub epollout: bool,
89    /// Stream socket peer closed connection, or shut down writing
90    /// half of connection.
91    pub epollrdhup: bool,
92    /// For stream socket, this event merely indicates that the peer
93    /// closed its end of the channel.
94    /// Unlike epollrdhup, this should only be set when the stream is fully closed.
95    /// epollrdhup also gets set when only the write half is closed, which is possible
96    /// via `shutdown(_, SHUT_WR)`.
97    pub epollhup: bool,
98    /// Error condition happened on the associated file descriptor.
99    pub epollerr: bool,
100}
101
102#[derive(Debug, Default)]
103struct ReadyList {
104    mapping: RefCell<BTreeMap<(FdId, i32), EpollEventInstance>>,
105}
106
107impl EpollReadyEvents {
108    pub fn new() -> Self {
109        EpollReadyEvents {
110            epollin: false,
111            epollout: false,
112            epollrdhup: false,
113            epollhup: false,
114            epollerr: false,
115        }
116    }
117
118    pub fn get_event_bitmask<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> u32 {
119        let epollin = ecx.eval_libc_u32("EPOLLIN");
120        let epollout = ecx.eval_libc_u32("EPOLLOUT");
121        let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
122        let epollhup = ecx.eval_libc_u32("EPOLLHUP");
123        let epollerr = ecx.eval_libc_u32("EPOLLERR");
124
125        let mut bitmask = 0;
126        if self.epollin {
127            bitmask |= epollin;
128        }
129        if self.epollout {
130            bitmask |= epollout;
131        }
132        if self.epollrdhup {
133            bitmask |= epollrdhup;
134        }
135        if self.epollhup {
136            bitmask |= epollhup;
137        }
138        if self.epollerr {
139            bitmask |= epollerr;
140        }
141        bitmask
142    }
143}
144
145impl FileDescription for Epoll {
146    fn name(&self) -> &'static str {
147        "epoll"
148    }
149
150    fn close<'tcx>(
151        self,
152        _communicate_allowed: bool,
153        _ecx: &mut MiriInterpCx<'tcx>,
154    ) -> InterpResult<'tcx, io::Result<()>> {
155        interp_ok(Ok(()))
156    }
157
158    fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
159        self
160    }
161}
162
163impl UnixFileDescription for Epoll {}
164
165/// The table of all EpollEventInterest.
166/// The BTreeMap key is the FdId of an active file description registered with
167/// any epoll instance. The value is a list of EpollEventInterest associated
168/// with that file description.
169pub struct EpollInterestTable(BTreeMap<FdId, Vec<Weak<RefCell<EpollEventInterest>>>>);
170
171impl EpollInterestTable {
172    pub(crate) fn new() -> Self {
173        EpollInterestTable(BTreeMap::new())
174    }
175
176    pub fn insert_epoll_interest(&mut self, id: FdId, fd: Weak<RefCell<EpollEventInterest>>) {
177        match self.0.get_mut(&id) {
178            Some(fds) => {
179                fds.push(fd);
180            }
181            None => {
182                let vec = vec![fd];
183                self.0.insert(id, vec);
184            }
185        }
186    }
187
188    pub fn get_epoll_interest(&self, id: FdId) -> Option<&Vec<Weak<RefCell<EpollEventInterest>>>> {
189        self.0.get(&id)
190    }
191
192    pub fn get_epoll_interest_mut(
193        &mut self,
194        id: FdId,
195    ) -> Option<&mut Vec<Weak<RefCell<EpollEventInterest>>>> {
196        self.0.get_mut(&id)
197    }
198
199    pub fn remove(&mut self, id: FdId) {
200        self.0.remove(&id);
201    }
202}
203
204impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
205pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
206    /// This function returns a file descriptor referring to the new `Epoll` instance. This file
207    /// descriptor is used for all subsequent calls to the epoll interface. If the `flags` argument
208    /// is 0, then this function is the same as `epoll_create()`.
209    ///
210    /// <https://linux.die.net/man/2/epoll_create1>
211    fn epoll_create1(&mut self, flags: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
212        let this = self.eval_context_mut();
213
214        let flags = this.read_scalar(flags)?.to_i32()?;
215
216        let epoll_cloexec = this.eval_libc_i32("EPOLL_CLOEXEC");
217
218        // Miri does not support exec, so EPOLL_CLOEXEC flag has no effect.
219        if flags != epoll_cloexec && flags != 0 {
220            throw_unsup_format!(
221                "epoll_create1: flag {:#x} is unsupported, only 0 or EPOLL_CLOEXEC are allowed",
222                flags
223            );
224        }
225
226        let fd = this.machine.fds.insert_new(Epoll::default());
227        interp_ok(Scalar::from_i32(fd))
228    }
229
230    /// This function performs control operations on the `Epoll` instance referred to by the file
231    /// descriptor `epfd`. It requests that the operation `op` be performed for the target file
232    /// descriptor, `fd`.
233    ///
234    /// Valid values for the op argument are:
235    /// `EPOLL_CTL_ADD` - Register the target file descriptor `fd` on the `Epoll` instance referred
236    /// to by the file descriptor `epfd` and associate the event `event` with the internal file
237    /// linked to `fd`.
238    /// `EPOLL_CTL_MOD` - Change the event `event` associated with the target file descriptor `fd`.
239    /// `EPOLL_CTL_DEL` - Deregister the target file descriptor `fd` from the `Epoll` instance
240    /// referred to by `epfd`. The `event` is ignored and can be null.
241    ///
242    /// <https://linux.die.net/man/2/epoll_ctl>
243    fn epoll_ctl(
244        &mut self,
245        epfd: &OpTy<'tcx>,
246        op: &OpTy<'tcx>,
247        fd: &OpTy<'tcx>,
248        event: &OpTy<'tcx>,
249    ) -> InterpResult<'tcx, Scalar> {
250        let this = self.eval_context_mut();
251
252        let epfd_value = this.read_scalar(epfd)?.to_i32()?;
253        let op = this.read_scalar(op)?.to_i32()?;
254        let fd = this.read_scalar(fd)?.to_i32()?;
255        let event = this.deref_pointer_as(event, this.libc_ty_layout("epoll_event"))?;
256
257        let epoll_ctl_add = this.eval_libc_i32("EPOLL_CTL_ADD");
258        let epoll_ctl_mod = this.eval_libc_i32("EPOLL_CTL_MOD");
259        let epoll_ctl_del = this.eval_libc_i32("EPOLL_CTL_DEL");
260        let epollin = this.eval_libc_u32("EPOLLIN");
261        let epollout = this.eval_libc_u32("EPOLLOUT");
262        let epollrdhup = this.eval_libc_u32("EPOLLRDHUP");
263        let epollet = this.eval_libc_u32("EPOLLET");
264        let epollhup = this.eval_libc_u32("EPOLLHUP");
265        let epollerr = this.eval_libc_u32("EPOLLERR");
266
267        // Throw EINVAL if epfd and fd have the same value.
268        if epfd_value == fd {
269            return this.set_last_error_and_return_i32(LibcError("EINVAL"));
270        }
271
272        // Check if epfd is a valid epoll file descriptor.
273        let Some(epfd) = this.machine.fds.get(epfd_value) else {
274            return this.set_last_error_and_return_i32(LibcError("EBADF"));
275        };
276        let epfd = epfd
277            .downcast::<Epoll>()
278            .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
279
280        let mut interest_list = epfd.interest_list.borrow_mut();
281
282        let Some(fd_ref) = this.machine.fds.get(fd) else {
283            return this.set_last_error_and_return_i32(LibcError("EBADF"));
284        };
285        let id = fd_ref.id();
286
287        if op == epoll_ctl_add || op == epoll_ctl_mod {
288            // Read event bitmask and data from epoll_event passed by caller.
289            let mut events =
290                this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?;
291            let data = this.read_scalar(&this.project_field(&event, FieldIdx::ONE)?)?.to_u64()?;
292
293            // Unset the flag we support to discover if any unsupported flags are used.
294            let mut flags = events;
295            // epoll_wait(2) will always wait for epollhup and epollerr; it is not
296            // necessary to set it in events when calling epoll_ctl().
297            // So we will always set these two event types.
298            events |= epollhup;
299            events |= epollerr;
300
301            if events & epollet != epollet {
302                // We only support edge-triggered notification for now.
303                throw_unsup_format!("epoll_ctl: epollet flag must be included.");
304            } else {
305                flags &= !epollet;
306            }
307            if flags & epollin == epollin {
308                flags &= !epollin;
309            }
310            if flags & epollout == epollout {
311                flags &= !epollout;
312            }
313            if flags & epollrdhup == epollrdhup {
314                flags &= !epollrdhup;
315            }
316            if flags & epollhup == epollhup {
317                flags &= !epollhup;
318            }
319            if flags & epollerr == epollerr {
320                flags &= !epollerr;
321            }
322            if flags != 0 {
323                throw_unsup_format!(
324                    "epoll_ctl: encountered unknown unsupported flags {:#x}",
325                    flags
326                );
327            }
328
329            let epoll_key = (id, fd);
330
331            // Check the existence of fd in the interest list.
332            if op == epoll_ctl_add {
333                if interest_list.contains_key(&epoll_key) {
334                    return this.set_last_error_and_return_i32(LibcError("EEXIST"));
335                }
336            } else {
337                if !interest_list.contains_key(&epoll_key) {
338                    return this.set_last_error_and_return_i32(LibcError("ENOENT"));
339                }
340            }
341
342            if op == epoll_ctl_add {
343                // Create an epoll_interest.
344                let interest = Rc::new(RefCell::new(EpollEventInterest {
345                    fd_num: fd,
346                    events,
347                    data,
348                    weak_epfd: FileDescriptionRef::downgrade(&epfd),
349                }));
350                // Notification will be returned for current epfd if there is event in the file
351                // descriptor we registered.
352                check_and_update_one_event_interest(&fd_ref, &interest, id, this)?;
353
354                // Insert an epoll_interest to global epoll_interest list.
355                this.machine.epoll_interests.insert_epoll_interest(id, Rc::downgrade(&interest));
356                interest_list.insert(epoll_key, interest);
357            } else {
358                // Modify the existing interest.
359                let epoll_interest = interest_list.get_mut(&epoll_key).unwrap();
360                {
361                    let mut epoll_interest = epoll_interest.borrow_mut();
362                    epoll_interest.events = events;
363                    epoll_interest.data = data;
364                }
365                // Updating an FD interest triggers events.
366                check_and_update_one_event_interest(&fd_ref, epoll_interest, id, this)?;
367            }
368
369            interp_ok(Scalar::from_i32(0))
370        } else if op == epoll_ctl_del {
371            let epoll_key = (id, fd);
372
373            // Remove epoll_event_interest from interest_list.
374            let Some(epoll_interest) = interest_list.remove(&epoll_key) else {
375                return this.set_last_error_and_return_i32(LibcError("ENOENT"));
376            };
377            // All related Weak<EpollEventInterest> will fail to upgrade after the drop.
378            drop(epoll_interest);
379
380            // Remove related epoll_interest from ready list.
381            epfd.ready_list.mapping.borrow_mut().remove(&epoll_key);
382
383            // Remove dangling EpollEventInterest from its global table.
384            // .unwrap() below should succeed because the file description id must have registered
385            // at least one epoll_interest, if not, it will fail when removing epoll_interest from
386            // interest list.
387            this.machine
388                .epoll_interests
389                .get_epoll_interest_mut(id)
390                .unwrap()
391                .retain(|event| event.upgrade().is_some());
392
393            interp_ok(Scalar::from_i32(0))
394        } else {
395            throw_unsup_format!("unsupported epoll_ctl operation: {op}");
396        }
397    }
398
399    /// The `epoll_wait()` system call waits for events on the `Epoll`
400    /// instance referred to by the file descriptor `epfd`. The buffer
401    /// pointed to by `events` is used to return information from the ready
402    /// list about file descriptors in the interest list that have some
403    /// events available. Up to `maxevents` are returned by `epoll_wait()`.
404    /// The `maxevents` argument must be greater than zero.
405    ///
406    /// The `timeout` argument specifies the number of milliseconds that
407    /// `epoll_wait()` will block. Time is measured against the
408    /// CLOCK_MONOTONIC clock. If the timeout is zero, the function will not block,
409    /// while if the timeout is -1, the function will block
410    /// until at least one event has been retrieved (or an error
411    /// occurred).
412    ///
413    /// A call to `epoll_wait()` will block until either:
414    /// • a file descriptor delivers an event;
415    /// • the call is interrupted by a signal handler; or
416    /// • the timeout expires.
417    ///
418    /// Note that the timeout interval will be rounded up to the system
419    /// clock granularity, and kernel scheduling delays mean that the
420    /// blocking interval may overrun by a small amount. Specifying a
421    /// timeout of -1 causes `epoll_wait()` to block indefinitely, while
422    /// specifying a timeout equal to zero cause `epoll_wait()` to return
423    /// immediately, even if no events are available.
424    ///
425    /// On success, `epoll_wait()` returns the number of file descriptors
426    /// ready for the requested I/O, or zero if no file descriptor became
427    /// ready during the requested timeout milliseconds. On failure,
428    /// `epoll_wait()` returns -1 and errno is set to indicate the error.
429    ///
430    /// <https://man7.org/linux/man-pages/man2/epoll_wait.2.html>
431    fn epoll_wait(
432        &mut self,
433        epfd: &OpTy<'tcx>,
434        events_op: &OpTy<'tcx>,
435        maxevents: &OpTy<'tcx>,
436        timeout: &OpTy<'tcx>,
437        dest: &MPlaceTy<'tcx>,
438    ) -> InterpResult<'tcx> {
439        let this = self.eval_context_mut();
440
441        let epfd_value = this.read_scalar(epfd)?.to_i32()?;
442        let events = this.read_immediate(events_op)?;
443        let maxevents = this.read_scalar(maxevents)?.to_i32()?;
444        let timeout = this.read_scalar(timeout)?.to_i32()?;
445
446        if epfd_value <= 0 || maxevents <= 0 {
447            return this.set_last_error_and_return(LibcError("EINVAL"), dest);
448        }
449
450        // This needs to come after the maxevents value check, or else maxevents.try_into().unwrap()
451        // will fail.
452        let event = this.deref_pointer_as(
453            &events,
454            this.libc_array_ty_layout("epoll_event", maxevents.try_into().unwrap()),
455        )?;
456
457        let Some(epfd) = this.machine.fds.get(epfd_value) else {
458            return this.set_last_error_and_return(LibcError("EBADF"), dest);
459        };
460        let Some(epfd) = epfd.downcast::<Epoll>() else {
461            return this.set_last_error_and_return(LibcError("EBADF"), dest);
462        };
463
464        // We just need to know if the ready list is empty and borrow the thread_ids out.
465        let ready_list_empty = epfd.ready_list.mapping.borrow().is_empty();
466        if timeout == 0 || !ready_list_empty {
467            // If the ready list is not empty, or the timeout is 0, we can return immediately.
468            return_ready_list(&epfd, dest, &event, this)?;
469        } else {
470            // Blocking
471            let timeout = match timeout {
472                0.. => {
473                    let duration = Duration::from_millis(timeout.try_into().unwrap());
474                    Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration))
475                }
476                -1 => None,
477                ..-1 => {
478                    throw_unsup_format!(
479                        "epoll_wait: Only timeout values greater than or equal to -1 are supported."
480                    );
481                }
482            };
483            // Record this thread as blocked.
484            epfd.blocked_tid.borrow_mut().push(this.active_thread());
485            // And block it.
486            let dest = dest.clone();
487            // We keep a strong ref to the underlying `Epoll` to make sure it sticks around.
488            // This means there'll be a leak if we never wake up, but that anyway would imply
489            // a thread is permanently blocked so this is fine.
490            this.block_thread(
491                BlockReason::Epoll,
492                timeout,
493                callback!(
494                    @capture<'tcx> {
495                        epfd: FileDescriptionRef<Epoll>,
496                        dest: MPlaceTy<'tcx>,
497                        event: MPlaceTy<'tcx>,
498                    }
499                    |this, unblock: UnblockKind| {
500                        match unblock {
501                            UnblockKind::Ready => {
502                                return_ready_list(&epfd, &dest, &event, this)?;
503                                interp_ok(())
504                            },
505                            UnblockKind::TimedOut => {
506                                // Remove the current active thread_id from the blocked thread_id list.
507                                epfd
508                                    .blocked_tid.borrow_mut()
509                                    .retain(|&id| id != this.active_thread());
510                                this.write_int(0, &dest)?;
511                                interp_ok(())
512                            },
513                        }
514                    }
515                ),
516            );
517        }
518        interp_ok(())
519    }
520
521    /// For a specific file description, get its ready events and update the corresponding ready
522    /// list. This function should be called whenever an event causes more bytes or an EOF to become
523    /// newly readable from an FD, and whenever more bytes can be written to an FD or no more future
524    /// writes are possible.
525    ///
526    /// This *will* report an event if anyone is subscribed to it, without any further filtering, so
527    /// do not call this function when an FD didn't have anything happen to it!
528    fn check_and_update_readiness(
529        &mut self,
530        fd_ref: DynFileDescriptionRef,
531    ) -> InterpResult<'tcx, ()> {
532        let this = self.eval_context_mut();
533        let id = fd_ref.id();
534        let mut waiter = Vec::new();
535        // Get a list of EpollEventInterest that is associated to a specific file description.
536        if let Some(epoll_interests) = this.machine.epoll_interests.get_epoll_interest(id) {
537            for weak_epoll_interest in epoll_interests {
538                if let Some(epoll_interest) = weak_epoll_interest.upgrade() {
539                    let is_updated =
540                        check_and_update_one_event_interest(&fd_ref, &epoll_interest, id, this)?;
541                    if is_updated {
542                        // Edge-triggered notification only notify one thread even if there are
543                        // multiple threads blocked on the same epfd.
544
545                        // This unwrap can never fail because if the current epoll instance were
546                        // closed, the upgrade of weak_epoll_interest
547                        // above would fail. This guarantee holds because only the epoll instance
548                        // holds a strong ref to epoll_interest.
549                        let epfd = epoll_interest.borrow().weak_epfd.upgrade().unwrap();
550                        // FIXME: We can randomly pick a thread to unblock.
551                        if let Some(thread_id) = epfd.blocked_tid.borrow_mut().pop() {
552                            waiter.push(thread_id);
553                        };
554                    }
555                }
556            }
557        }
558        waiter.sort();
559        waiter.dedup();
560        for thread_id in waiter {
561            this.unblock_thread(thread_id, BlockReason::Epoll)?;
562        }
563        interp_ok(())
564    }
565}
566
567/// This function takes in ready list and returns EpollEventInstance with file description
568/// that is not closed.
569fn ready_list_next(
570    ecx: &MiriInterpCx<'_>,
571    ready_list: &mut BTreeMap<(FdId, i32), EpollEventInstance>,
572) -> Option<EpollEventInstance> {
573    while let Some((epoll_key, epoll_event_instance)) = ready_list.pop_first() {
574        // This ensures that we only return events that we are interested. The FD might have been closed since
575        // the event was generated, in which case we are not interested anymore.
576        // When a file description is fully closed, it gets removed from `machine.epoll_interests`,
577        // so we skip events whose FD is not in that map anymore.
578        if ecx.machine.epoll_interests.get_epoll_interest(epoll_key.0).is_some() {
579            return Some(epoll_event_instance);
580        }
581    }
582    None
583}
584
585/// This helper function checks whether an epoll notification should be triggered for a specific
586/// epoll_interest and, if necessary, triggers the notification, and returns whether the
587/// notification was added/updated. Unlike check_and_update_readiness, this function sends a
588/// notification to only one epoll instance.
589fn check_and_update_one_event_interest<'tcx>(
590    fd_ref: &DynFileDescriptionRef,
591    interest: &RefCell<EpollEventInterest>,
592    id: FdId,
593    ecx: &MiriInterpCx<'tcx>,
594) -> InterpResult<'tcx, bool> {
595    // Get the bitmask of ready events for a file description.
596    let ready_events_bitmask = fd_ref.as_unix(ecx).get_epoll_ready_events()?.get_event_bitmask(ecx);
597    let epoll_event_interest = interest.borrow();
598    let epfd = epoll_event_interest.weak_epfd.upgrade().unwrap();
599    // This checks if any of the events specified in epoll_event_interest.events
600    // match those in ready_events.
601    let flags = epoll_event_interest.events & ready_events_bitmask;
602    // If there is any event that we are interested in being specified as ready,
603    // insert an epoll_return to the ready list.
604    if flags != 0 {
605        let epoll_key = (id, epoll_event_interest.fd_num);
606        let mut ready_list = epfd.ready_list.mapping.borrow_mut();
607        let mut event_instance = EpollEventInstance::new(flags, epoll_event_interest.data);
608        // If we are tracking data races, remember the current clock so we can sync with it later.
609        ecx.release_clock(|clock| {
610            event_instance.clock.clone_from(clock);
611        });
612        // Triggers the notification by inserting it to the ready list.
613        ready_list.insert(epoll_key, event_instance);
614        interp_ok(true)
615    } else {
616        interp_ok(false)
617    }
618}
619
620/// Stores the ready list of the `epfd` epoll instance into `events` (which must be an array),
621/// and the number of returned events into `dest`.
622fn return_ready_list<'tcx>(
623    epfd: &FileDescriptionRef<Epoll>,
624    dest: &MPlaceTy<'tcx>,
625    events: &MPlaceTy<'tcx>,
626    ecx: &mut MiriInterpCx<'tcx>,
627) -> InterpResult<'tcx> {
628    let mut ready_list = epfd.ready_list.mapping.borrow_mut();
629    let mut num_of_events: i32 = 0;
630    let mut array_iter = ecx.project_array_fields(events)?;
631
632    while let Some(des) = array_iter.next(ecx)? {
633        if let Some(epoll_event_instance) = ready_list_next(ecx, &mut ready_list) {
634            ecx.write_int_fields_named(
635                &[
636                    ("events", epoll_event_instance.events.into()),
637                    ("u64", epoll_event_instance.data.into()),
638                ],
639                &des.1,
640            )?;
641            // Synchronize waking thread with the event of interest.
642            ecx.acquire_clock(&epoll_event_instance.clock);
643
644            num_of_events = num_of_events.strict_add(1);
645        } else {
646            break;
647        }
648    }
649    ecx.write_int(num_of_events, dest)?;
650    interp_ok(())
651}