Skip to main content

tokio/runtime/
park.rs

1#![cfg_attr(not(feature = "full"), allow(dead_code))]
2
3use crate::loom::sync::atomic::AtomicUsize;
4use crate::loom::sync::{Arc, Condvar, Mutex};
5
6use std::sync::atomic::Ordering::SeqCst;
7use std::time::Duration;
8
9#[derive(Debug)]
10pub(crate) struct ParkThread {
11    inner: Arc<Inner>,
12}
13
14/// Unblocks a thread that was blocked by `ParkThread`.
15#[derive(Clone, Debug)]
16pub(crate) struct UnparkThread {
17    inner: Arc<Inner>,
18}
19
20#[derive(Debug)]
21struct Inner {
22    state: AtomicUsize,
23    mutex: Mutex<()>,
24    condvar: Condvar,
25    #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
26    jspi: crate::runtime::jspi::Slot,
27}
28
29const EMPTY: usize = 0;
30const PARKED: usize = 1;
31const NOTIFIED: usize = 2;
32
33#[cfg(not(all(target_os = "emscripten", not(target_feature = "atomics"))))]
34tokio_thread_local! {
35    static CURRENT_PARKER: ParkThread = ParkThread::new();
36}
37
38// Bit of a hack, but it is only for loom
39#[cfg(loom)]
40tokio_thread_local! {
41    pub(crate) static CURRENT_THREAD_PARK_COUNT: AtomicUsize = AtomicUsize::new(0);
42}
43
44// ==== impl ParkThread ====
45
46impl ParkThread {
47    pub(crate) fn new() -> Self {
48        Self {
49            inner: Arc::new(Inner {
50                state: AtomicUsize::new(EMPTY),
51                mutex: Mutex::new(()),
52                condvar: Condvar::new(),
53                #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
54                jspi: crate::runtime::jspi::Slot::new(),
55            }),
56        }
57    }
58
59    pub(crate) fn unpark(&self) -> UnparkThread {
60        let inner = self.inner.clone();
61        UnparkThread { inner }
62    }
63
64    pub(crate) fn park(&mut self) {
65        #[cfg(loom)]
66        CURRENT_THREAD_PARK_COUNT.with(|count| count.fetch_add(1, SeqCst));
67        self.inner.park();
68    }
69
70    pub(crate) fn park_timeout(&mut self, duration: Duration) {
71        #[cfg(loom)]
72        CURRENT_THREAD_PARK_COUNT.with(|count| count.fetch_add(1, SeqCst));
73        self.inner.park_timeout(duration);
74    }
75
76    pub(crate) fn shutdown(&mut self) {
77        self.inner.shutdown();
78    }
79}
80
81// ==== impl Inner ====
82
83impl Inner {
84    #[cfg(not(all(target_os = "emscripten", not(target_feature = "atomics"))))]
85    fn park(&self) {
86        // If we were previously notified then we consume this notification and
87        // return quickly.
88        if self
89            .state
90            .compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
91            .is_ok()
92        {
93            return;
94        }
95
96        // Otherwise we need to coordinate going to sleep
97        let mut m = self.mutex.lock();
98
99        match self.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
100            Ok(_) => {}
101            Err(NOTIFIED) => {
102                // We must read here, even though we know it will be `NOTIFIED`.
103                // This is because `unpark` may have been called again since we read
104                // `NOTIFIED` in the `compare_exchange` above. We must perform an
105                // acquire operation that synchronizes with that `unpark` to observe
106                // any writes it made before the call to unpark. To do that we must
107                // read from the write it made to `state`.
108                let old = self.state.swap(EMPTY, SeqCst);
109                debug_assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
110
111                return;
112            }
113            Err(actual) => panic!("inconsistent park state; actual = {actual}"),
114        }
115
116        loop {
117            m = self.condvar.wait(m).unwrap();
118
119            if self
120                .state
121                .compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
122                .is_ok()
123            {
124                // got a notification
125                return;
126            }
127
128            // spurious wakeup, go back to sleep
129        }
130    }
131
132    #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
133    fn park(&self) {
134        self.park_jspi(None);
135    }
136
137    /// Parks the activation until unparked or `dur` elapses, if given.
138    #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
139    fn park_jspi(&self, dur: Option<Duration>) {
140        // If we were previously notified then we consume this notification and
141        // return quickly.
142        if self
143            .state
144            .compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
145            .is_ok()
146        {
147            return;
148        }
149
150        // If JSPI is disabled, then we cannot park on JSPI, so instead use a
151        // spin loop for a timed park, and a panic for any other suspension.
152        // A zero-duration park returns immediately as on native.
153        if !crate::runtime::jspi::jspi_enabled() {
154            match dur {
155                Some(Duration::ZERO) => return,
156                Some(dur) => {
157                    std::thread::sleep(dur);
158                    return;
159                }
160                None => panic!(
161                    "cannot block on wasm32-unknown-emscripten: this wait would \
162                     suspend on the host event loop, which needs the build to \
163                     link `-sJSPI`"
164                ),
165            }
166        }
167
168        // Suspend until a host timer fires or a later activation (a host
169        // callback entering tokio) unparks us. A zero-duration park still
170        // takes a host turn so timers and microtasks run mid-drive (the
171        // scheduler's maintenance yield). No threads: nothing runs between
172        // here and the suspension, so an unpark can only land while parked,
173        // where it settles the promise.
174        let old = self.state.swap(PARKED, SeqCst);
175        debug_assert_eq!(old, EMPTY, "inconsistent park state");
176
177        // Consume any notification delivered during the park so the token
178        // does not leak into the next one. A `SuspendError` unwinding out of
179        // the import must also leave the parker `EMPTY` for its next park.
180        struct Unpark<'a>(&'a Inner);
181        impl Drop for Unpark<'_> {
182            fn drop(&mut self) {
183                match self.0.state.swap(EMPTY, SeqCst) {
184                    NOTIFIED => {} // got a notification, hurray!
185                    PARKED => {}   // timer deadline, alas
186                    n => debug_assert!(false, "inconsistent park state: {n}"),
187                }
188            }
189        }
190        let _unpark = Unpark(self);
191
192        crate::runtime::jspi::park(&self.jspi, dur);
193    }
194
195    /// Parks the current thread for at most `dur`.
196    #[cfg(not(all(target_os = "emscripten", not(target_feature = "atomics"))))]
197    fn park_timeout(&self, dur: Duration) {
198        // Like `park` above we have a fast path for an already-notified thread,
199        // and afterwards we start coordinating for a sleep. Return quickly.
200        if self
201            .state
202            .compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
203            .is_ok()
204        {
205            return;
206        }
207
208        if dur == Duration::from_millis(0) {
209            return;
210        }
211
212        let m = self.mutex.lock();
213
214        match self.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
215            Ok(_) => {}
216            Err(NOTIFIED) => {
217                // We must read again here, see `park`.
218                let old = self.state.swap(EMPTY, SeqCst);
219                debug_assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
220
221                return;
222            }
223            Err(actual) => panic!("inconsistent park_timeout state; actual = {actual}"),
224        }
225
226        #[cfg(not(all(target_family = "wasm", not(target_feature = "atomics"))))]
227        // Wait with a timeout, and if we spuriously wake up or otherwise wake up
228        // from a notification, we just want to unconditionally set the state back to
229        // empty, either consuming a notification or un-flagging ourselves as
230        // parked.
231        let (_m, _result) = self.condvar.wait_timeout(m, dur).unwrap();
232
233        #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))]
234        // Wasm without atomics doesn't have threads, so just sleep.
235        {
236            let _m = m;
237            std::thread::sleep(dur);
238        }
239
240        match self.state.swap(EMPTY, SeqCst) {
241            NOTIFIED => {} // got a notification, hurray!
242            PARKED => {}   // no notification, alas
243            n => panic!("inconsistent park_timeout state: {n}"),
244        }
245    }
246
247    #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
248    fn park_timeout(&self, dur: Duration) {
249        self.park_jspi(Some(dur));
250    }
251
252    fn unpark(&self) {
253        // To ensure the unparked thread will observe any writes we made before
254        // this call, we must perform a release operation that `park` can
255        // synchronize with. To do that we must write `NOTIFIED` even if `state`
256        // is already `NOTIFIED`. That is why this must be a swap rather than a
257        // compare-and-swap that returns if it reads `NOTIFIED` on failure.
258        match self.state.swap(NOTIFIED, SeqCst) {
259            EMPTY => return,    // no one was waiting
260            NOTIFIED => return, // already unparked
261            PARKED => {}        // gotta go wake someone up
262            _ => panic!("inconsistent state in unpark"),
263        }
264
265        // The parked activation is suspended in the host; settle its promise.
266        #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
267        crate::runtime::jspi::unpark(&self.jspi);
268
269        // There is a period between when the parked thread sets `state` to
270        // `PARKED` (or last checked `state` in the case of a spurious wake
271        // up) and when it actually waits on `cvar`. If we were to notify
272        // during this period it would be ignored and then when the parked
273        // thread went to sleep it would never wake up. Fortunately, it has
274        // `lock` locked at this stage so we can acquire `lock` to wait until
275        // it is ready to receive the notification.
276        //
277        // Releasing `lock` before the call to `notify_one` means that when the
278        // parked thread wakes it doesn't get woken only to have to wait for us
279        // to release `lock`.
280        drop(self.mutex.lock());
281
282        self.condvar.notify_one();
283    }
284
285    fn shutdown(&self) {
286        self.condvar.notify_all();
287    }
288}
289
290impl Default for ParkThread {
291    fn default() -> Self {
292        Self::new()
293    }
294}
295
296// ===== impl UnparkThread =====
297
298impl UnparkThread {
299    pub(crate) fn unpark(&self) {
300        self.inner.unpark();
301    }
302}
303
304use crate::loom::thread::AccessError;
305use std::future::Future;
306use std::marker::PhantomData;
307use std::rc::Rc;
308use std::task::{RawWaker, RawWakerVTable, Waker};
309
310/// Blocks the current thread using a condition variable.
311#[derive(Debug)]
312pub(crate) struct CachedParkThread {
313    // While a suspended stack is parked, host callbacks can still run tokio
314    // code on this thread, and a shared thread-local parker would hand this
315    // stack's notification token to that code. Each blocking call gets its
316    // own parker instead.
317    #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
318    park: ParkThread,
319    _anchor: PhantomData<Rc<()>>,
320}
321
322impl CachedParkThread {
323    /// Creates a new `ParkThread` handle for the current thread.
324    ///
325    /// This type cannot be moved to other threads, so it should be created on
326    /// the thread that the caller intends to park.
327    pub(crate) fn new() -> CachedParkThread {
328        CachedParkThread {
329            #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
330            park: ParkThread::new(),
331            _anchor: PhantomData,
332        }
333    }
334
335    pub(crate) fn waker(&self) -> Result<Waker, AccessError> {
336        self.unpark().map(UnparkThread::into_waker)
337    }
338
339    fn unpark(&self) -> Result<UnparkThread, AccessError> {
340        self.with_current(ParkThread::unpark)
341    }
342
343    pub(crate) fn park(&mut self) {
344        self.with_current(|park_thread| park_thread.inner.park())
345            .unwrap();
346    }
347
348    pub(crate) fn park_timeout(&mut self, duration: Duration) {
349        self.with_current(|park_thread| park_thread.inner.park_timeout(duration))
350            .unwrap();
351    }
352
353    /// Gets a reference to the `ParkThread` handle for this thread.
354    fn with_current<F, R>(&self, f: F) -> Result<R, AccessError>
355    where
356        F: FnOnce(&ParkThread) -> R,
357    {
358        #[cfg(not(all(target_os = "emscripten", not(target_feature = "atomics"))))]
359        return CURRENT_PARKER.try_with(|inner| f(inner));
360
361        // See the comment on the `park` field.
362        #[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
363        return Ok(f(&self.park));
364    }
365
366    pub(crate) fn block_on<F: Future>(&mut self, f: F) -> Result<F::Output, AccessError> {
367        use std::task::Context;
368        use std::task::Poll::Ready;
369
370        let waker = self.waker()?;
371        let mut cx = Context::from_waker(&waker);
372
373        pin!(f);
374
375        loop {
376            if let Ready(v) = crate::task::coop::budget(|| f.as_mut().poll(&mut cx)) {
377                return Ok(v);
378            }
379
380            self.park();
381        }
382    }
383}
384
385impl UnparkThread {
386    pub(crate) fn into_waker(self) -> Waker {
387        unsafe {
388            let raw = unparker_to_raw_waker(self.inner);
389            Waker::from_raw(raw)
390        }
391    }
392}
393
394impl Inner {
395    #[allow(clippy::wrong_self_convention)]
396    fn into_raw(this: Arc<Inner>) -> *const () {
397        Arc::into_raw(this) as *const ()
398    }
399
400    /// # Safety
401    ///
402    /// The pointer must have been created by [`Self::into_raw`].
403    unsafe fn from_raw(ptr: *const ()) -> Arc<Inner> {
404        unsafe { Arc::from_raw(ptr as *const Inner) }
405    }
406}
407
408// TODO: Is this really an unsafe function?
409unsafe fn unparker_to_raw_waker(unparker: Arc<Inner>) -> RawWaker {
410    RawWaker::new(
411        Inner::into_raw(unparker),
412        &RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker),
413    )
414}
415
416/// # Safety
417///
418/// The pointer must have been created by [`Inner::into_raw`].
419unsafe fn clone(raw: *const ()) -> RawWaker {
420    unsafe {
421        Arc::increment_strong_count(raw as *const Inner);
422    }
423    unsafe { unparker_to_raw_waker(Inner::from_raw(raw)) }
424}
425
426/// # Safety
427///
428/// The pointer must have been created by [`Inner::into_raw`].
429unsafe fn drop_waker(raw: *const ()) {
430    drop(unsafe { Inner::from_raw(raw) });
431}
432
433/// # Safety
434///
435/// The pointer must have been created by [`Inner::into_raw`].
436unsafe fn wake(raw: *const ()) {
437    let unparker = unsafe { Inner::from_raw(raw) };
438    unparker.unpark();
439}
440
441/// # Safety
442///
443/// The pointer must have been created by [`Inner::into_raw`].
444unsafe fn wake_by_ref(raw: *const ()) {
445    let raw = raw as *const Inner;
446    unsafe {
447        (*raw).unpark();
448    }
449}
450
451#[cfg(loom)]
452pub(crate) fn current_thread_park_count() -> usize {
453    CURRENT_THREAD_PARK_COUNT.with(|count| count.load(SeqCst))
454}