Skip to main content

miri/shims/
readiness.rs

1use std::assert_matches;
2use std::cell::RefCell;
3use std::collections::{BTreeMap, VecDeque};
4use std::rc::{Rc, Weak};
5
6use crate::concurrency::VClock;
7use crate::shims::files::{DynFileDescriptionRef, FdNum, WeakDynFileDescriptionRef};
8use crate::shims::*;
9use crate::*;
10
11/// Struct reflecting the readiness of a file description.
12#[derive(Debug, Copy, Clone, PartialEq)]
13pub struct Readiness {
14    /// Boolean whether the file description is readable.
15    pub readable: bool,
16    /// Boolean whether the file description is writable.
17    pub writable: bool,
18    /// Boolean whether the read end of the file description
19    /// is closed.
20    pub read_closed: bool,
21    /// Boolean whether the write end of the file description
22    /// is closed.
23    pub write_closed: bool,
24    /// Boolean whether the file description has an error.
25    pub error: bool,
26}
27
28impl std::ops::BitAnd for Readiness {
29    type Output = Readiness;
30
31    fn bitand(self, rhs: Readiness) -> Self::Output {
32        Readiness {
33            readable: self.readable && rhs.readable,
34            writable: self.writable && rhs.writable,
35            read_closed: self.read_closed && rhs.read_closed,
36            write_closed: self.write_closed && rhs.write_closed,
37            error: self.error && rhs.error,
38        }
39    }
40}
41
42impl std::ops::BitOr for Readiness {
43    type Output = Readiness;
44
45    fn bitor(self, rhs: Readiness) -> Self::Output {
46        Readiness {
47            readable: self.readable | rhs.readable,
48            writable: self.writable | rhs.writable,
49            read_closed: self.read_closed | rhs.read_closed,
50            write_closed: self.write_closed | rhs.write_closed,
51            error: self.error | rhs.error,
52        }
53    }
54}
55
56impl std::ops::BitOrAssign for Readiness {
57    fn bitor_assign(&mut self, rhs: Self) {
58        self.readable |= rhs.readable;
59        self.writable |= rhs.writable;
60        self.read_closed |= rhs.read_closed;
61        self.write_closed |= rhs.write_closed;
62        self.error |= rhs.error;
63    }
64}
65
66impl Readiness {
67    pub const EMPTY: Readiness = Readiness {
68        readable: false,
69        writable: false,
70        read_closed: false,
71        write_closed: false,
72        error: false,
73    };
74}
75
76bitflags::bitflags! {
77    #[derive(Debug, Copy, Clone, PartialEq)]
78    pub struct ReadinessUpdateFlags: u32 {
79        const DEFAULT = 0;
80        /// Trigger edge-triggered even if the set of ready events did not change. This can lead to
81        /// spurious wakeups. Use with caution!
82        const FORCE_EDGE = 1 << 0;
83        /// Do not treat this as a release event from the current thread. This can lead to incorrect
84        /// data race reports. Use with caution!
85        const NO_RELEASE_CLOCK = 1 << 1;
86    }
87}
88
89type ReadinessInterestKey = (FdId, FdNum);
90
91/// Returns the range of all [`ReadinessInterestKey`] for the given FD ID.
92fn range_for_id(id: FdId) -> std::ops::RangeInclusive<ReadinessInterestKey> {
93    (id, 0)..=(id, FdNum::MAX)
94}
95
96#[derive(Debug, Clone)]
97pub struct ReadinessInterest {
98    /// The FD we are interested in.
99    watched_fd: WeakDynFileDescriptionRef,
100    /// The mask of events the interest is interested in
101    /// for this file descriptor.
102    pub relevant: Readiness,
103    /// Boolean whether this is an edge-triggered interest.
104    /// When [`false`] it's a level-triggered interest instead.
105    pub is_edge_triggered: bool,
106    /// Data attached to the interest.
107    // FIXME: In the future we might want to support more data types,
108    // then this should no longer be a `u64` but a `dyn Any` instead.
109    pub data: u64,
110    /// The currently active readiness for this file descriptor.
111    active: Readiness,
112}
113
114impl ReadinessInterest {
115    pub fn active(&self) -> &Readiness {
116        &self.active
117    }
118}
119
120/// A struct which stores [`ReadinessInterest`]s for a set of file descriptions
121/// together with which interests are currently satisfied, and a list of
122/// threads which should be unblocked once a [`ReadinessInterest`] of the
123/// watcher is fulfilled.
124#[derive(Debug, Default)]
125pub struct ReadinessWatcher {
126    /// A map of [`ReadinessInterest`]s registered for this watcher. Each entry is
127    /// identified using a [`FdId`] [`FdNum`] tuple.
128    interests: RefCell<BTreeMap<ReadinessInterestKey, ReadinessInterest>>,
129    /// The subset of interests that is currently considered "ready". Stored separately so we
130    /// can access it more efficiently.
131    /// This is implemented as a queue so that with level-triggered interests, all events eventually
132    /// get returned from [`ReadinessWatcher::get_ready_interests`]. The queue does not contain any
133    /// duplicates.
134    ready: RefCell<VecDeque<ReadinessInterestKey>>,
135    /// The queue of threads blocked on this watcher.
136    queue: RefCell<VecDeque<ThreadId>>,
137}
138
139impl Drop for ReadinessWatcher {
140    fn drop(&mut self) {
141        // Remove ourselves from the FDs we were interested in, at most once per description.
142        let mut last_id = None;
143        for ((id, _fd_num), interest) in self.interests.borrow().iter() {
144            // We'll see interested sorted by ID. Only do something once for each ID.
145            if Some(id) == last_id {
146                continue;
147            }
148            last_id = Some(id);
149            // If the FD still exists, remove ourselves from it.
150            if let Some(fd) = interest.watched_fd.upgrade() {
151                // We can't easily figure out who in that list is us, but we can just remove
152                // everything that has no more strong refs.
153                let mut watchers = fd.readiness_watched().unwrap().watchers.borrow_mut();
154                watchers.retain(|w| w.strong_count() > 0);
155            }
156        }
157    }
158}
159
160impl ReadinessWatcher {
161    /// Add an interest for the file description to which the file descriptor
162    /// `fd_num` belongs.
163    /// `relevant` contains the readiness mask of relevant events.
164    /// `is_edge_triggered` specifies whether the interest is edge-triggered
165    /// ([`true`]) or level-triggered ([`false`]).
166    /// `data` is the user-data which is associated with the interest.
167    ///
168    /// The function returns `Ok(())` when the interest was successfully
169    /// added, and `Err(())` when an interest with this key was already registered.
170    pub fn add_interest<'tcx>(
171        self: &Rc<Self>,
172        fd_num: FdNum,
173        relevant: Readiness,
174        is_edge_triggered: bool,
175        data: u64,
176        ecx: &mut MiriInterpCx<'tcx>,
177    ) -> InterpResult<'tcx, Result<(), ()>> {
178        let fd_ref = ecx.machine.fds.get(fd_num).expect("File description should exist");
179        let fd_id = fd_ref.id();
180        let key = (fd_id, fd_num);
181
182        let interest = ReadinessInterest {
183            watched_fd: FileDescriptionRef::downgrade(&fd_ref),
184            active: Readiness::EMPTY,
185            relevant,
186            is_edge_triggered,
187            data,
188        };
189        let mut interests = self.interests.borrow_mut();
190        if interests.range(range_for_id(fd_id)).next().is_none() {
191            // This is the first time this FD got added to the watcher.
192            // Let's make sure it can be watched, and add ourselves to its list.
193            let watched = fd_ref.readiness_watched().ok_or_else(|| {
194                err_unsup_format!("I/O readiness watching not supported for {}", fd_ref.name())
195            })?;
196            watched.insert(self);
197        }
198        if interests.try_insert(key, interest).is_err() {
199            return interp_ok(Err(()));
200        }
201
202        // After adding a new interest for a fd, we need to forcefully update
203        // the readiness of this fd.
204
205        ecx.update_readiness(
206            self,
207            fd_ref.readiness(),
208            /* force_edge */ true,
209            move |callback| {
210                // Need to release the RefCell when this closure returns, so we have to move
211                // it into the closure, so we have to do a re-lookup here.
212                callback(key, interests.get_mut(&key).unwrap())
213            },
214        )?;
215
216        interp_ok(Ok(()))
217    }
218
219    /// Update the interest which is registered for `key`.
220    /// `cb` gets invoked with a mutable reference to the registered
221    /// [`ReadinessInterest`].
222    ///
223    /// This function returns [`None`] when no interest is registered
224    /// for the specified `key`.
225    pub fn update_interest<'tcx>(
226        self: &Rc<Self>,
227        key: ReadinessInterestKey,
228        ecx: &mut MiriInterpCx<'tcx>,
229        cb: impl FnOnce(&mut ReadinessInterest),
230    ) -> InterpResult<'tcx, Option<()>> {
231        let mut interests = self.interests.borrow_mut();
232        let Some(interest) = interests.get_mut(&key) else { return interp_ok(None) };
233        cb(interest);
234
235        // After updating an interest for a fd, we need to forcefully update
236        // the readiness of this fd.
237
238        let fd_ref = ecx.machine.fds.get(key.1).expect("File description should exist");
239        ecx.update_readiness(
240            self,
241            fd_ref.readiness(),
242            /* force_edge */ true,
243            move |callback| {
244                // Need to release the RefCell when this closure returns, so we have to move
245                // it into the closure, so we have to do a re-lookup here.
246                callback(key, interests.get_mut(&key).unwrap())
247            },
248        )?;
249
250        interp_ok(Some(()))
251    }
252
253    /// Remove the interest registered for `key`.
254    ///
255    /// This function returns [`None`] when no interest is registered
256    /// for the specified `key`.
257    pub fn remove_interest(self: &Rc<ReadinessWatcher>, key: ReadinessInterestKey) -> Option<()> {
258        let mut interests = self.interests.borrow_mut();
259
260        let Some(interest) = interests.remove(&key) else {
261            // We did not have interest in this.
262            return None;
263        };
264        let fd_ref = interest.watched_fd.upgrade().expect("dead watched");
265
266        // Remove the ready event for this key, should one exist.
267        let mut ready_events = self.ready.borrow_mut();
268        if let Some(idx) = ready_events.iter().position(|k| k == &key) {
269            ready_events.remove(idx);
270        }
271        // If this was the last interest in this FD, remove us from the global list
272        // of who is interested in this FD.
273        if interests.range(range_for_id(key.0)).next().is_none() {
274            fd_ref.readiness_watched()?.remove(self);
275        }
276
277        Some(())
278    }
279
280    /// Add the thread with id `thread_id` to the queue of
281    /// blocked threads which will be unblocked when the
282    /// watcher becomes ready.
283    pub fn add_blocked_thread(&self, thread_id: ThreadId) {
284        self.queue.borrow_mut().push_back(thread_id);
285    }
286
287    /// Remove all threads with id `thread_id` from the queue
288    /// of blocked threads which will be unblocked when the
289    /// watcher becomes ready.
290    pub fn remove_blocked_thread(&self, thread_id: ThreadId) {
291        self.queue.borrow_mut().retain(|id| id != &thread_id);
292    }
293
294    /// Get the amount of interests which are registered to this
295    /// watcher and which are currently ready.
296    pub fn ready_count(&self) -> usize {
297        self.ready.borrow().len()
298    }
299
300    /// Get at most the first `max` ready interests from the ready queue.
301    ///
302    /// If the interest is a level-triggered interest, it's automatically
303    /// added to the end of the queue again such that it will only be reported
304    /// after all other ready interest have been returned.
305    ///
306    /// This method returns at most every event from the ready queue once.
307    /// This ensures that every returned interest is unique, even when there
308    /// are level-triggered interests.
309    pub fn get_ready_interests<'tcx>(
310        &self,
311        max: usize,
312        ecx: &mut MiriInterpCx<'tcx>,
313    ) -> InterpResult<'tcx, Vec<ReadinessInterest>> {
314        // The goal below is to do work proportyional to how much we're actually reporting to the
315        // client, i.e., how many interests of this watcher are ready. In particular, we do *not*
316        // iterate over all interests of this watcher, or all watchers, or all FDs being watched, or
317        // anything like that.
318
319        // Process delayed readiness updates, to ensure we have fully up-to-date information.
320        // This is almost always a NOP.
321        DelayedReadinessUpdates::process(ecx)?;
322
323        let interests = self.interests.borrow();
324        let mut ready = self.ready.borrow_mut();
325
326        // Sanity-check to ensure that all event info is up-to-date.
327        if cfg!(debug_assertions) {
328            for interest in interests.values() {
329                // Ensure this matches the latest readiness of this FD.
330                let fd = interest.watched_fd.upgrade().expect("dead watched");
331                let current_active = fd.readiness();
332                assert_eq!(interest.active(), &(current_active & interest.relevant));
333            }
334        }
335
336        let mut ready_interests = Vec::with_capacity(max.min(ready.len()));
337        let mut re_add_interests = Vec::new();
338
339        // Continue while we haven't yet gotten `max` events, and while there are more to add.
340        while ready_interests.len() < max
341            && let Some(key) = ready.pop_front()
342        {
343            let interest = interests.get(&key).expect("non-existing interest in ready set");
344            let fd = interest.watched_fd.upgrade().expect("dead watched");
345
346            if !interest.is_edge_triggered {
347                // This is a level-triggered interest, so we need to re-add the event:
348                // <https://github.com/torvalds/linux/blob/HEAD/fs/eventpoll.c#L1835-L1847>.
349                // We delay adding them back so they do not get picked up by later loop iterations.
350                re_add_interests.push(key);
351            }
352
353            ready_interests.push(interest.clone());
354            // We now "see" the readiness of this FD, so make the data race system aware of that.
355            ecx.acquire_clock(&fd.readiness_watched().unwrap().ready_clock.borrow())?;
356        }
357        // Add back the level-triggered ones.
358        ready.extend(re_add_interests);
359
360        interp_ok(ready_interests)
361    }
362}
363
364impl VisitProvenance for ReadinessWatcher {
365    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
366}
367
368/// Data about a file description that can be watched for readiness
369/// (meant to be stored inside said file description).
370#[derive(Debug, Default)]
371pub struct ReadinessWatched {
372    /// List of readiness watchers that are interested in us.
373    watchers: RefCell<Vec<Weak<ReadinessWatcher>>>,
374    /// Vector clock for the most recent change to our readiness.
375    /// (Ideally this would be one clock per readiness flag, but we're not bothering with that.)
376    ready_clock: RefCell<VClock>,
377}
378
379impl Drop for ReadinessWatched {
380    fn drop(&mut self) {
381        // "A file descriptor is removed from an interest list only after all the file
382        // descriptors referring to the underlying open file description have been closed."
383        // So, remove ourselves from all interest lists.
384        for watcher in self.watchers.borrow().iter() {
385            // If the watcher still exists, remove ourselves from it.
386            let Some(watcher) = watcher.upgrade() else { continue };
387            // We can't easily figure out who in that list is us, but we can just remove everything
388            // that has no more strong refs.
389            let mut ready_events = watcher.ready.borrow_mut();
390            watcher.interests.borrow_mut().retain(|key, interest| {
391                if interest.watched_fd.is_closed() {
392                    // We'll remove this one. Also remove it from the ready queue if applicable!
393                    if let Some(idx) = ready_events.iter().position(|k| k == key) {
394                        ready_events.remove(idx);
395                    }
396                    false // do not retain
397                } else {
398                    true // do retain
399                }
400            });
401        }
402    }
403}
404
405impl ReadinessWatched {
406    fn insert(&self, watcher: &Rc<ReadinessWatcher>) {
407        let mut watchers = self.watchers.borrow_mut();
408        if cfg!(debug_assertions) {
409            // Ensure uniqueness.
410            assert_matches!(
411                watchers.iter().find(|elem| elem.as_ptr() == Rc::as_ptr(watcher)),
412                None,
413                "watcher has already been added to this watched fd",
414            );
415        }
416        watchers.push(Rc::downgrade(watcher));
417    }
418
419    fn remove(&self, watcher: &Rc<ReadinessWatcher>) {
420        let mut watchers = self.watchers.borrow_mut();
421        // We need to do a linear scan to find the watcher to remove. That's not ideal, but removing
422        // a watched FD from an epoll is rare so it's not worth the non-trivial effort it would take
423        // to make this more efficient.
424        let idx = watchers
425            .iter()
426            .position(|elem| elem.as_ptr() == Rc::as_ptr(watcher))
427            .expect("watcher has no registered interest in the provided watched fd");
428        watchers.remove(idx);
429    }
430
431    /// Returns whether the watched FD has any readiness watcher with a blocked thread watching it.
432    pub fn has_watcher_with_blocked_thread(&self) -> bool {
433        let watchers = self.watchers.borrow();
434        // See if any of those watchers has a blocked thread.
435        watchers.iter().any(|w| w.upgrade().expect("dead watcher").queue.borrow().len() > 0)
436    }
437
438    /// Remove all interes in the given file descriptor, which must refer to the file description
439    /// that contains `self`.
440    pub fn remove_file_num_interests(&self, fd_id: FdId, fd_num: FdNum) {
441        // We make a copy since `remove_interest` may want to mutate `watchers`. This is not
442        // very efficient, but this code anyway only runs on `close` on Illumos.
443        let watchers = self.watchers.borrow().clone();
444        for watcher in watchers.iter() {
445            let watcher = watcher.upgrade().expect("dead watcher");
446            // Remove this interest, if it exists.
447            watcher.remove_interest((fd_id, fd_num));
448        }
449    }
450}
451
452/// If a file description's readiness is known to change but we don't have an `ecx` around to update
453/// it immediately, we arange for a referene to the delayed readiness updates queue to be available
454/// and perform the update on the next scheduler call.
455#[derive(Default, Debug)]
456pub struct DelayedReadinessUpdates {
457    to_update: RefCell<Vec<DynFileDescriptionRef>>,
458}
459
460impl DelayedReadinessUpdates {
461    pub fn add(&self, fd: DynFileDescriptionRef) {
462        self.to_update.borrow_mut().push(fd);
463    }
464
465    pub fn process<'tcx>(ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx> {
466        loop {
467            // Avoid keeping the RefCell open over the `update_fd_readiness` as that can invoke
468            // arbitrary code via the unblock callback.
469            let Some(fd) = ecx.machine.delayed_readiness_updates.to_update.borrow_mut().pop()
470            else {
471                return interp_ok(());
472            };
473            ecx.update_fd_readiness(fd, ReadinessUpdateFlags::DEFAULT)?;
474        }
475    }
476}
477
478impl<'tcx> EvalContextExt<'tcx> for MiriInterpCx<'tcx> {}
479pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
480    /// For a specific file description, get its current readiness and send it to everyone who
481    /// registered interest in this FD. This function must be called whenever the result of
482    /// `FileDescription::readiness` might change.
483    fn update_fd_readiness(
484        &mut self,
485        fd: DynFileDescriptionRef,
486        flags: ReadinessUpdateFlags,
487    ) -> InterpResult<'tcx> {
488        let this = self.eval_context_mut();
489        let fd_id = fd.id();
490        let watched = fd.readiness_watched().unwrap();
491
492        if !flags.contains(ReadinessUpdateFlags::NO_RELEASE_CLOCK) {
493            // We capture a vector clock even if there is nobody watching, since someone might get
494            // interested in the future and then synchronize with this event.
495            // FIXME: currently we do this even if the readiness did not change!
496            this.release_clock(|clock| {
497                watched.ready_clock.borrow_mut().join(clock);
498            })?;
499        }
500
501        let watchers_ref = watched.watchers.borrow();
502        // Need to make a copy so below we can unblock threads which may need the same `RefCell`.
503        let watchers =
504            watchers_ref.iter().map(|w| w.upgrade().expect("dead watcher")).collect::<Vec<_>>();
505        if watchers.is_empty() {
506            return interp_ok(());
507        };
508        drop(watchers_ref);
509
510        let active_readiness = fd.readiness();
511        for watcher in watchers {
512            this.update_readiness(
513                &watcher,
514                active_readiness,
515                flags.contains(ReadinessUpdateFlags::FORCE_EDGE),
516                |callback| {
517                    for (&key, interest) in
518                        watcher.interests.borrow_mut().range_mut(range_for_id(fd_id))
519                    {
520                        callback(key, interest)?;
521                    }
522                    interp_ok(())
523                },
524            )?;
525        }
526
527        interp_ok(())
528    }
529}
530
531impl<'tcx> EvalContextPrivExt<'tcx> for MiriInterpCx<'tcx> {}
532pub trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
533    /// Call this when the interests denoted by `for_each_interest` have their active readiness changed
534    /// to `active`. The list is provided indirectly via the `for_each_interest` closure, which
535    /// will call its argument closure for each relevant interest.
536    ///
537    /// Any [`RefCell`]s should be released by the time `for_each_interest` returns since we will then
538    /// be waking up threads which might require access to those [`RefCell`]s.
539    fn update_readiness(
540        &mut self,
541        watcher: &Rc<ReadinessWatcher>,
542        active: Readiness,
543        force_edge: bool,
544        for_each_interest: impl FnOnce(
545            &mut dyn FnMut(ReadinessInterestKey, &mut ReadinessInterest) -> InterpResult<'tcx>,
546        ) -> InterpResult<'tcx>,
547    ) -> InterpResult<'tcx> {
548        let this = self.eval_context_mut();
549        let mut ready = watcher.ready.borrow_mut();
550        for_each_interest(&mut |key, interest| {
551            let new_readiness = interest.relevant & active;
552            let prev_readiness = std::mem::replace(&mut interest.active, new_readiness);
553            if new_readiness == Readiness::EMPTY {
554                // Un-trigger this, there's nothing left to report here.
555                if let Some(idx) = ready.iter().position(|k| k == &key) {
556                    ready.remove(idx);
557                }
558            } else if force_edge || new_readiness != prev_readiness & new_readiness {
559                // Either we force an "edge" to be detected or there's a bit set in `new_readiness`
560                // that was not set in `prev_readiness`. In both cases, this is ready now.
561
562                // We need to ensure that this event is not already part of the `ready` queue
563                // before enqueueing, as Linux does it with epoll:
564                // <https://github.com/torvalds/linux/blob/13dce771bbad42da6ecf086446d8ddfd1fce3a1b/fs/eventpoll.c#L1290-L1293>
565                if !ready.contains(&key) {
566                    ready.push_back(key);
567                }
568            }
569            interp_ok(())
570        })?;
571
572        // While there are events ready to be delivered, wake up a thread to receive them.
573        while !ready.is_empty()
574            && let Some(thread_id) = watcher.queue.borrow_mut().pop_front()
575        {
576            drop(ready);
577            this.unblock_thread(thread_id, BlockReason::Readiness)?;
578            ready = watcher.ready.borrow_mut();
579        }
580        interp_ok(())
581    }
582}