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}