Skip to main content

tokio/runtime/io/
registration_set.rs

1use crate::loom::sync::atomic::AtomicUsize;
2use crate::runtime::io::ScheduledIo;
3use crate::util::linked_list::{self, LinkedList};
4
5use crate::loom::sync::Arc;
6use std::io;
7use std::ptr::NonNull;
8use std::sync::atomic::Ordering::{Acquire, Release};
9
10// Kind of arbitrary, but buffering 16 `ScheduledIo`s doesn't seem like much
11const NOTIFY_AFTER: usize = 16;
12
13pub(super) struct RegistrationSet {
14    num_pending_release: AtomicUsize,
15}
16
17pub(super) struct Synced {
18    // True when the I/O driver shutdown. At this point, no more registrations
19    // should be added to the set.
20    is_shutdown: bool,
21
22    // List of all registrations tracked by the set
23    registrations: LinkedList<Arc<ScheduledIo>>,
24
25    // Registrations that are pending drop. When a `Registration` is dropped, it
26    // stores its `ScheduledIo` in this list. The I/O driver is responsible for
27    // dropping it. This ensures the `ScheduledIo` is not freed while it can
28    // still be included in an I/O event.
29    pending_release: Vec<Arc<ScheduledIo>>,
30}
31
32impl RegistrationSet {
33    pub(super) fn new() -> (RegistrationSet, Synced) {
34        let set = RegistrationSet {
35            num_pending_release: AtomicUsize::new(0),
36        };
37
38        let synced = Synced {
39            is_shutdown: false,
40            registrations: LinkedList::new(),
41            pending_release: Vec::with_capacity(NOTIFY_AFTER),
42        };
43
44        (set, synced)
45    }
46
47    pub(super) fn is_shutdown(&self, synced: &Synced) -> bool {
48        synced.is_shutdown
49    }
50
51    /// Returns `true` if there are registrations that need to be released
52    pub(super) fn needs_release(&self) -> bool {
53        self.num_pending_release.load(Acquire) != 0
54    }
55
56    pub(super) fn allocate(&self, synced: &mut Synced) -> io::Result<Arc<ScheduledIo>> {
57        if synced.is_shutdown {
58            return Err(io::Error::other(
59                crate::util::error::RUNTIME_SHUTTING_DOWN_ERROR,
60            ));
61        }
62
63        let ret = Arc::new(ScheduledIo::default());
64
65        // Push a ref into the list of all resources.
66        synced.registrations.push_front(ret.clone());
67
68        Ok(ret)
69    }
70
71    // Returns `true` if the caller should unblock the I/O driver to purge
72    // registrations pending release.
73    pub(super) fn deregister(&self, synced: &mut Synced, registration: &Arc<ScheduledIo>) -> bool {
74        synced.pending_release.push(registration.clone());
75
76        let len = synced.pending_release.len();
77        self.num_pending_release.store(len, Release);
78
79        len == NOTIFY_AFTER
80    }
81
82    pub(super) fn shutdown(&self, synced: &mut Synced) -> Vec<Arc<ScheduledIo>> {
83        if synced.is_shutdown {
84            return vec![];
85        }
86
87        synced.is_shutdown = true;
88        synced.pending_release.clear();
89
90        // Building a vec of all outstanding I/O handles could be expensive, but
91        // this is the shutdown operation. In theory, shutdowns should be
92        // "clean" with no outstanding I/O resources. Even if it is slow, we
93        // aren't optimizing for shutdown.
94        let mut ret = vec![];
95
96        while let Some(io) = synced.registrations.pop_back() {
97            ret.push(io);
98        }
99
100        ret
101    }
102
103    pub(super) fn release(&self, synced: &mut Synced) {
104        let pending = std::mem::take(&mut synced.pending_release);
105
106        for io in pending {
107            // safety: the registration is part of our list
108            unsafe { self.remove(synced, &io) }
109        }
110
111        self.num_pending_release.store(0, Release);
112    }
113
114    // This function is marked as unsafe, because the caller must make sure that
115    // `io` is part of the registration set.
116    pub(super) unsafe fn remove(&self, synced: &mut Synced, io: &Arc<ScheduledIo>) {
117        // SAFETY: Pointers into an Arc are never null.
118        let io = unsafe { NonNull::new_unchecked(Arc::as_ptr(io).cast_mut()) };
119
120        super::EXPOSE_IO.unexpose_provenance(io.as_ptr());
121        // SAFETY: the caller guarantees that `io` is part of this list.
122        let _ = unsafe { synced.registrations.remove(io) };
123    }
124}
125
126// Safety: `Arc` pins the inner data
127unsafe impl linked_list::Link for Arc<ScheduledIo> {
128    type Handle = Arc<ScheduledIo>;
129    type Target = ScheduledIo;
130
131    fn as_raw(handle: &Self::Handle) -> NonNull<ScheduledIo> {
132        // safety: Arc::as_ptr never returns null
133        unsafe { NonNull::new_unchecked(Arc::as_ptr(handle) as *mut _) }
134    }
135
136    unsafe fn from_raw(ptr: NonNull<Self::Target>) -> Arc<ScheduledIo> {
137        // safety: the linked list currently owns a ref count
138        unsafe { Arc::from_raw(ptr.as_ptr() as *const _) }
139    }
140
141    unsafe fn pointers(
142        target: NonNull<Self::Target>,
143    ) -> NonNull<linked_list::Pointers<ScheduledIo>> {
144        // safety: `target.as_ref().linked_list_pointers` is a `UnsafeCell` that
145        // always returns a non-null pointer.
146        unsafe { NonNull::new_unchecked(target.as_ref().linked_list_pointers.get()) }
147    }
148}
149
150#[cfg(all(test, loom))]
151mod loom_tests {
152    use super::*;
153    use crate::io::ready::Ready;
154    use crate::loom::sync::Mutex;
155    use crate::loom::thread;
156
157    // Models only Tokio's registration lifetime. Mio polling and OS deregistration
158    // are outside the model; the production Handle remains unchanged under Loom.
159    struct Registrations {
160        set: RegistrationSet,
161        synced: Mutex<Synced>,
162    }
163
164    impl Registrations {
165        fn new() -> Self {
166            let (set, synced) = RegistrationSet::new();
167            Self {
168                set,
169                synced: Mutex::new(synced),
170            }
171        }
172
173        fn allocate(&self) -> Arc<ScheduledIo> {
174            self.set.allocate(&mut self.synced.lock()).unwrap()
175        }
176
177        fn deregister(&self, io: &Arc<ScheduledIo>) {
178            self.set.deregister(&mut self.synced.lock(), io);
179        }
180
181        fn release(&self) {
182            if self.set.needs_release() {
183                self.set.release(&mut self.synced.lock());
184            }
185        }
186    }
187
188    /// Models deregistration racing with delivery of an event already returned by
189    /// the poller. The driver completes delivery before its next release step.
190    #[test]
191    fn deregister_races_with_prior_event_delivery() {
192        loom::model(|| {
193            let handle = Arc::new(Registrations::new());
194            let scheduled_io = handle.allocate();
195            let token = scheduled_io.token();
196
197            let handle_clone = handle.clone();
198            let driver_th = thread::spawn(move || {
199                // Driver thread: has already obtained an event from poll() for `token`.
200                // Delivers event to ScheduledIo via its exposed raw pointer token.
201                //
202                // SAFETY: The driver holds the registration alive in `RegistrationSet`
203                // until `release()` is called. In this model, event delivery occurs
204                // before the driver's release step.
205                unsafe {
206                    super::super::driver::dispatch_event(token, Ready::READABLE);
207                }
208                // Simulates deferred release performed at the start of the next turn/poll.
209                handle_clone.release();
210            });
211
212            // Worker thread: deregisters the resource and drops its Arc handle.
213            handle.deregister(&scheduled_io);
214            drop(scheduled_io);
215
216            driver_th.join().unwrap();
217
218            // Flush any remaining release if worker deregistered after driver's release step.
219            handle.release();
220
221            assert!(!handle.set.needs_release());
222        });
223    }
224}