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}