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