Skip to main content

miri/shims/
readiness.rs

1use std::cell::{Ref, RefCell};
2use std::collections::{BTreeMap, VecDeque};
3use std::rc::{Rc, Weak};
4
5use crate::concurrency::VClock;
6use crate::shims::files::{DynFileDescriptionRef, FdNum};
7use crate::shims::*;
8use crate::*;
9
10/// Struct reflecting the readiness of a file description.
11#[derive(Debug, Clone, PartialEq)]
12pub struct Readiness {
13    /// Boolean whether the file description is readable.
14    pub readable: bool,
15    /// Boolean whether the file description is writable.
16    pub writable: bool,
17    /// Boolean whether the read end of the file description
18    /// is closed.
19    pub read_closed: bool,
20    /// Boolean whether the write end of the file description
21    /// is closed.
22    pub write_closed: bool,
23    /// Boolean whether the file description has an error.
24    pub error: bool,
25}
26
27impl std::ops::BitAnd for Readiness {
28    type Output = Readiness;
29
30    fn bitand(self, rhs: Readiness) -> Self::Output {
31        Readiness {
32            readable: self.readable && rhs.readable,
33            writable: self.writable && rhs.writable,
34            read_closed: self.read_closed && rhs.read_closed,
35            write_closed: self.write_closed && rhs.write_closed,
36            error: self.error && rhs.error,
37        }
38    }
39}
40
41impl std::ops::BitOr for Readiness {
42    type Output = Readiness;
43
44    fn bitor(self, rhs: Readiness) -> Self::Output {
45        Readiness {
46            readable: self.readable | rhs.readable,
47            writable: self.writable | rhs.writable,
48            read_closed: self.read_closed | rhs.read_closed,
49            write_closed: self.write_closed | rhs.write_closed,
50            error: self.error | rhs.error,
51        }
52    }
53}
54
55impl std::ops::BitOrAssign for Readiness {
56    fn bitor_assign(&mut self, rhs: Self) {
57        self.readable |= rhs.readable;
58        self.writable |= rhs.writable;
59        self.read_closed |= rhs.read_closed;
60        self.write_closed |= rhs.write_closed;
61        self.error |= rhs.error;
62    }
63}
64
65impl Readiness {
66    pub const EMPTY: Readiness = Readiness {
67        readable: false,
68        writable: false,
69        read_closed: false,
70        write_closed: false,
71        error: false,
72    };
73}
74
75pub type ReadinessInterestKey = (FdId, FdNum);
76
77/// Returns the range of all [`ReadinessInterestKey`] for the given FD ID.
78fn range_for_id(id: FdId) -> std::ops::RangeInclusive<ReadinessInterestKey> {
79    (id, 0)..=(id, FdNum::MAX)
80}
81
82#[derive(Debug, Clone)]
83pub struct ReadinessInterest {
84    /// The mask of events the interest is interested in
85    /// for this file descriptor.
86    pub relevant: Readiness,
87    /// Boolean whether this is an edge-triggered interest.
88    /// When [`false`] it's a level-triggered interest instead.
89    pub is_edge_triggered: bool,
90    /// Data attached to the interest.
91    // FIXME: In the future we might want to support more data types,
92    // then this should no longer be a `u64` but a `dyn Any` instead.
93    pub data: u64,
94    /// The currently active readiness for this file descriptor.
95    active: Readiness,
96    /// The vector clock for wakeups.
97    clock: VClock,
98}
99
100impl ReadinessInterest {
101    pub fn active(&self) -> &Readiness {
102        &self.active
103    }
104
105    pub fn clock(&self) -> &VClock {
106        &self.clock
107    }
108}
109
110type ReadinessWatcherId = usize;
111
112/// A struct which stores [`ReadinessInterest`]s for a set of file descriptions
113/// together with which interests are currently satisfied, and a list of
114/// threads which should be unblocked once a [`ReadinessInterest`] of the
115/// watcher is fulfilled.
116#[derive(Debug)]
117pub struct ReadinessWatcher {
118    /// Globally unique identifier of the watcher.
119    id: ReadinessWatcherId,
120    /// A map of [`ReadinessInterest`]s registered for this watcher. Each entry is
121    /// identified using a [`FdId`] [`FdNum`] tuple.
122    interests: RefCell<BTreeMap<ReadinessInterestKey, ReadinessInterest>>,
123    /// The subset of interests that is currently considered "ready". Stored separately so we
124    /// can access it more efficiently.
125    /// This is implemented as a queue so that with level-triggered interests, all events eventually
126    /// get returned from [`ReadinessWatcher::get_ready_interests`]. The queue does not contain any
127    /// duplicates.
128    ready: RefCell<VecDeque<ReadinessInterestKey>>,
129    /// The queue of threads blocked on this watcher.
130    queue: RefCell<VecDeque<ThreadId>>,
131}
132
133impl ReadinessWatcher {
134    /// Get a reference to the map of registered interests of the watcher
135    /// together with their keys.
136    pub fn interests(&self) -> Ref<'_, BTreeMap<ReadinessInterestKey, ReadinessInterest>> {
137        self.interests.borrow()
138    }
139
140    /// Add an interest for the file description to which the file descriptor
141    /// `fd_num` belongs.
142    /// `relevant` contains the readiness mask of relevant events.
143    /// `is_edge_triggered` specifies whether the interest is edge-triggered
144    /// ([`true`]) or level-triggered ([`false`]).
145    /// `data` is the user-data which is associated with the interest.
146    ///
147    /// The function returns `Ok(())` when the interest was successfully
148    /// added, and `Err(())` when an interest with this key was already registered.
149    pub fn add_interest<'tcx>(
150        self: &Rc<Self>,
151        fd_num: FdNum,
152        relevant: Readiness,
153        is_edge_triggered: bool,
154        data: u64,
155        ecx: &mut MiriInterpCx<'tcx>,
156    ) -> InterpResult<'tcx, Result<(), ()>> {
157        let fd_ref = ecx.machine.fds.get(fd_num).expect("File description should exist");
158        let fd_id = fd_ref.id();
159        let key = (fd_id, fd_num);
160
161        let interest = ReadinessInterest {
162            active: Readiness::EMPTY,
163            clock: VClock::default(),
164            relevant,
165            is_edge_triggered,
166            data,
167        };
168        let mut interests = self.interests.borrow_mut();
169        if interests.range(range_for_id(fd_id)).next().is_none() {
170            // This is the first time this FD got added to the watcher.
171            // We need to remember that in the global list such that we
172            // get notified about FD events.
173            ecx.machine.readiness_interests.insert(fd_id, self);
174        }
175        if interests.try_insert(key, interest).is_err() {
176            return interp_ok(Err(()));
177        }
178
179        // After adding a new interest for a fd, we need to forcefully update
180        // the readiness of this fd.
181
182        ecx.update_readiness(
183            self,
184            fd_ref.readiness()?,
185            /* force_edge */ true,
186            move |callback| {
187                // Need to release the RefCell when this closure returns, so we have to move
188                // it into the closure, so we have to do a re-lookup here.
189                callback(key, interests.get_mut(&key).unwrap())
190            },
191        )?;
192
193        interp_ok(Ok(()))
194    }
195
196    /// Update the interest which is registered for `key`.
197    /// `cb` gets invoked with a mutable reference to the registered
198    /// [`ReadinessInterest`].
199    ///
200    /// This function returns [`None`] when no interest is registered
201    /// for the specified `key`.
202    pub fn update_interest<'tcx>(
203        self: &Rc<Self>,
204        key: ReadinessInterestKey,
205        ecx: &mut MiriInterpCx<'tcx>,
206        cb: impl FnOnce(&mut ReadinessInterest),
207    ) -> InterpResult<'tcx, Option<()>> {
208        let mut interests = self.interests.borrow_mut();
209        let Some(interest) = interests.get_mut(&key) else { return interp_ok(None) };
210        cb(interest);
211
212        // After updating an interest for a fd, we need to forcefully update
213        // the readiness of this fd.
214
215        let fd_ref = ecx.machine.fds.get(key.1).expect("File description should exist");
216        ecx.update_readiness(
217            self,
218            fd_ref.readiness()?,
219            /* force_edge */ true,
220            move |callback| {
221                // Need to release the RefCell when this closure returns, so we have to move
222                // it into the closure, so we have to do a re-lookup here.
223                callback(key, interests.get_mut(&key).unwrap())
224            },
225        )?;
226
227        interp_ok(Some(()))
228    }
229
230    /// Remove the interest registered for `key`.
231    ///
232    /// This function returns [`None`] when no interest is registered
233    /// for the specified `key`.
234    pub fn remove_interest<'tcx>(
235        self: &Rc<ReadinessWatcher>,
236        key: ReadinessInterestKey,
237        ecx: &mut MiriInterpCx<'tcx>,
238    ) -> Option<()> {
239        let mut interests = self.interests.borrow_mut();
240
241        if interests.remove(&key).is_none() {
242            // We did not have interest in this.
243            return None;
244        }
245
246        // Remove the ready event for this key, should one exist.
247        let mut ready_events = self.ready.borrow_mut();
248        if let Some(idx) = ready_events.iter().position(|k| k == &key) {
249            ready_events.remove(idx);
250        }
251        // If this was the last interest in this FD, remove us from the global list
252        // of who is interested in this FD.
253        if interests.range(range_for_id(key.0)).next().is_none() {
254            ecx.machine.readiness_interests.remove(key.0, self);
255        }
256
257        Some(())
258    }
259
260    /// Add the thread with id `thread_id` to the queue of
261    /// blocked threads which will be unblocked when the
262    /// watcher becomes ready.
263    pub fn add_blocked_thread(&self, thread_id: ThreadId) {
264        self.queue.borrow_mut().push_back(thread_id);
265    }
266
267    /// Remove all threads with id `thread_id` from the queue
268    /// of blocked threads which will be unblocked when the
269    /// watcher becomes ready.
270    pub fn remove_blocked_thread(&self, thread_id: ThreadId) {
271        self.queue.borrow_mut().retain(|id| id != &thread_id);
272    }
273
274    /// Get the amount of interests which are registered to this
275    /// watcher and which are currently ready.
276    pub fn ready_count(&self) -> usize {
277        self.ready.borrow().len()
278    }
279
280    /// Get at most the first `count` ready interests from the ready queue.
281    ///
282    /// If the interest is a level-triggered interest, it's automatically
283    /// added to the end of the queue again such that it will only be reported
284    /// after all other ready interest have been returned.
285    ///
286    /// This method returns at most every event from the ready queue once.
287    /// This ensures that every returned interest is unique, even when there
288    /// are level-triggered interests.
289    pub fn get_ready_interests<'tcx>(
290        &self,
291        count: usize,
292        ecx: &mut MiriInterpCx<'tcx>,
293    ) -> InterpResult<'tcx, Vec<ReadinessInterest>> {
294        let interests = self.interests.borrow();
295        let mut ready = self.ready.borrow_mut();
296
297        // Sanity-check to ensure that all event info is up-to-date.
298        if cfg!(debug_assertions) {
299            for (key, interest) in interests.iter() {
300                // Ensure this matches the latest readiness of this FD.
301                // We have to do an FD lookup by ID for this. The FdNum might be already closed.
302                let fd = ecx.machine.fds.fds.values().find(|fd| fd.id() == key.0).unwrap();
303                let current_active = fd.readiness()?;
304                assert_eq!(interest.active(), &(current_active & interest.relevant.clone()));
305            }
306        }
307
308        // Compute how many events we can actually return.
309        let count = count.min(ready.len());
310        let mut ready_interests = Vec::with_capacity(count);
311
312        // We have to bound this iterator by `count`. Iterating until `ready` is empty would not
313        // work since we are re-adding level-triggered events to `ready` during the loop.
314        while ready_interests.len() < count {
315            let key = ready.pop_front().unwrap();
316            let interest = interests.get(&key).expect("non-existing interest in ready set");
317
318            if !interest.is_edge_triggered {
319                // This is a level-triggered interest, so we need to re-add the event
320                // at the end of the ready queue like Linux does with epoll:
321                // <https://github.com/torvalds/linux/blob/HEAD/fs/eventpoll.c#L1835-L1847>
322                ready.push_back(key);
323            }
324
325            ready_interests.push(interest.clone());
326        }
327
328        interp_ok(ready_interests)
329    }
330
331    /// Destroy the watcher instance.
332    ///
333    /// This also deregisters all interests of the watcher
334    /// from the global readiness interest table.
335    pub fn destroy<'tcx>(self, ecx: &mut MiriInterpCx<'tcx>) {
336        // If we were interested in some FDs, we can remove that now.
337        let mut ids = self.interests.borrow().keys().map(|(id, _num)| *id).collect::<Vec<_>>();
338        // Because the ids come out of the map sorted,
339        // deduping only keeps all unique entries.
340        ids.dedup();
341        for id in ids {
342            ecx.machine.readiness_interests.remove(id, &self);
343        }
344    }
345}
346
347impl VisitProvenance for ReadinessWatcher {
348    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
349}
350
351/// The table with all [`ReadinessWatcher`]s.
352/// This tracks, for each file description, which watchers have an interest in events
353/// for this file description.
354pub struct ReadinessInterestTable {
355    /// The id of the next [`ReadinessWatcher`] created through
356    /// [`ReadinessInterestTable::new_watcher`].
357    next_watcher_id: ReadinessWatcherId,
358    /// Maps each file description (identified by its [`FdId`]) to the list of watchers that are
359    /// interested in that FD. We also store the [`ReadinessWatcher`]s ID
360    /// separately so we can access it without calling `upgrade`. The list
361    /// is sorted by that id. We use an ID so that we can identify the watcher even after it has
362    /// been moved, e.g. in [`ReadinessWatcher::destroy`].
363    interests: BTreeMap<FdId, Vec<(ReadinessWatcherId, Weak<ReadinessWatcher>)>>,
364}
365
366impl ReadinessInterestTable {
367    pub(crate) fn new() -> Self {
368        ReadinessInterestTable { interests: BTreeMap::new(), next_watcher_id: 0 }
369    }
370
371    /// Create a new [`ReadinessWatcher`] with a globally unique id.
372    /// Every watcher gets a sequentially increasing id such that no two
373    /// watchers ever get the same id.
374    pub fn new_watcher(&mut self) -> ReadinessWatcher {
375        let id = self.next_watcher_id;
376        self.next_watcher_id = id.strict_add(1);
377        ReadinessWatcher {
378            id,
379            interests: RefCell::new(BTreeMap::new()),
380            ready: RefCell::new(VecDeque::new()),
381            queue: RefCell::new(VecDeque::new()),
382        }
383    }
384
385    /// Add an interest for `watcher` for the file description with id `fd_id`.
386    fn insert(&mut self, fd_id: FdId, watcher: &Rc<ReadinessWatcher>) {
387        let watchers = self.interests.entry(fd_id).or_default();
388        let idx = watchers
389            .binary_search_by_key(&watcher.id, |&(id, _)| id)
390            .expect_err("watcher already has a registered interest in the provided fd");
391        watchers.insert(idx, (watcher.id, Rc::downgrade(watcher)));
392    }
393
394    /// Remove the interest of `watcher` for the file description with id `fd_id`.
395    fn remove(&mut self, fd_id: FdId, watcher: &ReadinessWatcher) {
396        let watchers = self.interests.entry(fd_id).or_default();
397        let idx = watchers
398            .binary_search_by_key(&watcher.id, |&(id, _)| id)
399            .expect("watcher has no registered interest in the provided fd");
400        watchers.remove(idx);
401    }
402
403    /// Get all watchers which have a registered interest in the file description
404    /// with id `fd_id`.
405    fn get_watchers_for_fd(
406        &self,
407        fd_id: FdId,
408    ) -> Option<impl Iterator<Item = Rc<ReadinessWatcher>>> {
409        let watchers = self.interests.get(&fd_id)?;
410        Some(watchers.iter().map(|(_id, watcher)| {
411            watcher
412                .upgrade()
413                .expect("someone forgot to remove the garbage from `machine.readiness_interests`")
414        }))
415    }
416
417    /// Remove all watchers for the file description with id `fd_id`.
418    pub fn remove_watchers_for_fd(&mut self, fd_id: FdId) {
419        let Some(watchers) = self.interests.remove(&fd_id) else {
420            return;
421        };
422
423        for watcher in watchers.iter().filter_map(|(_id, watcher)| Weak::upgrade(watcher)) {
424            // This is a still-live watcher with interest in this FD. Remove all
425            // relevant interests (including from the ready set).
426            watcher
427                .interests
428                .borrow_mut()
429                .extract_if(range_for_id(fd_id), |_, _| true)
430                // Consume the iterator.
431                .for_each(drop);
432            // Remove the ready interests for this file description.
433            watcher.ready.borrow_mut().retain(|(id, _)| id != &fd_id);
434        }
435    }
436}
437
438impl<'tcx> EvalContextExt<'tcx> for MiriInterpCx<'tcx> {}
439pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
440    /// Returns whether the given FD has any readiness watcher with a blocked thread watching it.
441    fn has_watcher_with_blocked_thread(&self, fd_id: FdId) -> bool {
442        let this = self.eval_context_ref();
443        let Some(mut watchers) = this.machine.readiness_interests.get_watchers_for_fd(fd_id) else {
444            return false;
445        };
446        // See if any of those watchers has a blocked thread.
447        watchers.any(|w| w.queue.borrow().len() > 0)
448    }
449
450    /// For a specific file description, get its current readiness and send it to everyone who
451    /// registered interest in this FD. This function must be called whenever the result of
452    /// `FileDescription::readiness` might change.
453    ///
454    /// If `force_edge` is set, edge-triggered interests will be triggered even if the set of
455    /// ready events did not change. This can lead to spurious wakeups. Use with caution!
456    fn update_fd_readiness(
457        &mut self,
458        fd: DynFileDescriptionRef,
459        force_edge: bool,
460    ) -> InterpResult<'tcx> {
461        let this = self.eval_context_mut();
462        let fd_id = fd.id();
463
464        let Some(watchers) = this.machine.readiness_interests.get_watchers_for_fd(fd_id) else {
465            return interp_ok(());
466        };
467        let watchers = watchers.collect::<Vec<_>>(); // need to make a copy so below we can unblock threads
468        let active_readiness = fd.readiness()?;
469        for watcher in watchers {
470            this.update_readiness(&watcher, active_readiness.clone(), force_edge, |callback| {
471                for (&key, interest) in
472                    watcher.interests.borrow_mut().range_mut(range_for_id(fd_id))
473                {
474                    callback(key, interest)?;
475                }
476                interp_ok(())
477            })?;
478        }
479
480        interp_ok(())
481    }
482}
483
484impl<'tcx> EvalContextPrivExt<'tcx> for MiriInterpCx<'tcx> {}
485pub trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
486    /// Call this when the interests denoted by `for_each_interest` have their active readiness changed
487    /// to `active`. The list is provided indirectly via the `for_each_interest` closure, which
488    /// will call its argument closure for each relevant interest.
489    ///
490    /// Any [`RefCell`]s should be released by the time `for_each_interest` returns since we will then
491    /// be waking up threads which might require access to those [`RefCell`]s.
492    fn update_readiness(
493        &mut self,
494        watcher: &Rc<ReadinessWatcher>,
495        active: Readiness,
496        force_edge: bool,
497        for_each_interest: impl FnOnce(
498            &mut dyn FnMut(ReadinessInterestKey, &mut ReadinessInterest) -> InterpResult<'tcx>,
499        ) -> InterpResult<'tcx>,
500    ) -> InterpResult<'tcx> {
501        let this = self.eval_context_mut();
502        let mut ready = watcher.ready.borrow_mut();
503        for_each_interest(&mut |key, interest| {
504            let new_readiness = interest.relevant.clone() & active.clone();
505            let prev_readiness = std::mem::replace(&mut interest.active, new_readiness.clone());
506            if new_readiness == Readiness::EMPTY {
507                // Un-trigger this, there's nothing left to report here.
508                if let Some(idx) = ready.iter().position(|k| k == &key) {
509                    ready.remove(idx);
510                }
511            } else if force_edge || new_readiness != prev_readiness & new_readiness.clone() {
512                // Either we force an "edge" to be detected or there's a bit set in `new_readiness`
513                // that was not set in `prev_readiness`. In both cases, this is ready now.
514
515                // We need to ensure that this event is not already part of the `ready` queue
516                // before enqueueing, as Linux does it with epoll:
517                // <https://github.com/torvalds/linux/blob/HEAD/fs/eventpoll.c#L1292-L1296>
518                if !ready.contains(&key) {
519                    ready.push_back(key);
520                }
521
522                // No matter whether this is newly ready or just re-triggered,
523                // the waiter fetching this event should sync with the current thread.
524                this.release_clock(|clock| {
525                    interest.clock.join(clock);
526                })?;
527            }
528            interp_ok(())
529        })?;
530
531        // While there are events ready to be delivered, wake up a thread to receive them.
532        while !ready.is_empty()
533            && let Some(thread_id) = watcher.queue.borrow_mut().pop_front()
534        {
535            drop(ready);
536            this.unblock_thread(thread_id, BlockReason::Readiness)?;
537            ready = watcher.ready.borrow_mut();
538        }
539        interp_ok(())
540    }
541}