Skip to main content

shuttle_engine/
thread_support.rs

1use crate::runtime::execution::ExecutionState;
2use crate::runtime::thread;
3use std::marker::PhantomData;
4
5/// Cooperatively gives up a timeslice to the Shuttle scheduler.
6pub fn yield_now() {
7    let waker = ExecutionState::with(|state| state.current().waker());
8    waker.wake_by_ref();
9    ExecutionState::request_yield();
10    thread::switch();
11}
12
13/// The body of a spawned thread. Runs `f`, drops thread locals, publishes result.
14pub fn thread_fn<F, T>(
15    f: F,
16    switch_before_exit: bool,
17    result: std::sync::Arc<std::sync::Mutex<Option<std::thread::Result<T>>>>,
18) where
19    F: FnOnce() -> T,
20{
21    let ret = f();
22
23    if switch_before_exit && ExecutionState::with(|s| s.exit_current_truncates_execution()) {
24        thread::switch();
25    }
26
27    // Nobody can join a thread whose `JoinHandle` is gone, so drop what it returned now, while its
28    // thread-locals are still there for the value's destructors, as std does.
29    let ret = if std::sync::Arc::strong_count(&result) > 1 {
30        Some(ret)
31    } else {
32        drop(ret);
33        None
34    };
35
36    tracing::trace!("thread finished, dropping thread locals");
37    ExecutionState::drop_task_locals();
38    tracing::trace!("done dropping thread locals");
39
40    if let Some(ret) = ret {
41        *result.lock().unwrap() = Some(Ok(ret));
42    }
43    ExecutionState::with(|state| {
44        if let Some(waiter) = state.current_mut().take_waiter() {
45            state.get_mut(waiter).unblock();
46        }
47    });
48}
49
50/// A key into Shuttle's thread-local storage.
51pub struct LocalKey<T: 'static> {
52    #[doc(hidden)]
53    pub init: fn() -> T,
54    // A `LocalKey` holds no `T` of its own, only the means to reach each thread's, so like
55    // `std::thread::LocalKey` it is `Send`, `Sync` and unwind safe whatever `T` is. `fn() -> T`
56    // gives it all four.
57    #[doc(hidden)]
58    pub _p: PhantomData<fn() -> T>,
59}
60
61/// A [`LocalKey`] whose slot is destroyed after all of a thread's other thread-locals when the
62/// thread finishes, so that their destructors can still use it however early it was created.
63/// `shuttle-tokio` uses it for the slots of its task-locals, which in tokio are thread-locals that
64/// outlive every task.
65#[doc(hidden)]
66pub struct DestroyedLastKey<T: 'static> {
67    key: LocalKey<T>,
68}
69
70impl<T: 'static> DestroyedLastKey<T> {
71    /// A key whose slots start out as what `init` returns.
72    pub const fn new(init: fn() -> T) -> Self {
73        Self {
74            key: LocalKey { init, _p: PhantomData },
75        }
76    }
77
78    /// Like [`LocalKey::try_with`].
79    pub fn try_with<F, R>(&'static self, f: F) -> std::result::Result<R, AccessError>
80    where
81        F: FnOnce(&T) -> R,
82    {
83        self.key.try_with_slot(f, true)
84    }
85}
86
87impl<T: 'static> std::fmt::Debug for DestroyedLastKey<T> {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.debug_struct("DestroyedLastKey").finish_non_exhaustive()
90    }
91}
92
93impl<T: 'static> std::fmt::Debug for LocalKey<T> {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.debug_struct("LocalKey").finish_non_exhaustive()
96    }
97}
98
99impl<T: 'static> LocalKey<T> {
100    /// Acquires a reference to the value in this TLS key.
101    ///
102    /// This will lazily initialize the value if this thread has not referenced this key yet.
103    pub fn with<F, R>(&'static self, f: F) -> R
104    where
105        F: FnOnce(&T) -> R,
106    {
107        self.try_with(f).expect(
108            "cannot access a Thread Local Storage value \
109            during or after destruction",
110        )
111    }
112
113    /// Acquires a reference to the value in this TLS key.
114    ///
115    /// This will lazily initialize the value if this thread has not referenced this key yet. If the
116    /// key has been destroyed (which may happen if this is called in a destructor), this function
117    /// will return an AccessError.
118    pub fn try_with<F, R>(&'static self, f: F) -> std::result::Result<R, AccessError>
119    where
120        F: FnOnce(&T) -> R,
121    {
122        self.try_with_slot(f, false)
123    }
124
125    /// `try_with`, creating the slot, if it has to, as one that is destroyed after the thread's other
126    /// slots if `destroyed_last` is set.
127    fn try_with_slot<F, R>(&'static self, f: F, destroyed_last: bool) -> std::result::Result<R, AccessError>
128    where
129        F: FnOnce(&T) -> R,
130    {
131        let value = self.get().unwrap_or_else(|| {
132            let value = (self.init)();
133
134            // `init`, or a `tracing` subscriber of the event that `ExecutionState::with` emits, may
135            // have used this key, and so created the slot, since `get` found none. Keep that slot
136            // then, and drop the new value once the state is no longer borrowed.
137            let unused = ExecutionState::with(move |state| {
138                if state.current().local(self).is_none() {
139                    if destroyed_last {
140                        state.current_mut().init_local_destroyed_last(self, value);
141                    } else {
142                        state.current_mut().init_local(self, value);
143                    }
144                    None
145                } else {
146                    Some(value)
147                }
148            });
149            drop(unused);
150
151            self.get().unwrap()
152        })?;
153
154        Ok(f(value))
155    }
156
157    fn get(&'static self) -> Option<std::result::Result<&'static T, AccessError>> {
158        // Safety: see the usage below
159        unsafe fn extend_lt<'b, T>(t: &'_ T) -> &'b T {
160            std::mem::transmute(t)
161        }
162
163        ExecutionState::with(|state| {
164            if let Ok(value) = state.current().local(self)? {
165                // Safety: the `ExecutionState` outlives any thread, including the caller, and so
166                // it's safe to give the caller the lifetime it's asking for here.
167                Some(Ok(unsafe { extend_lt(value) }))
168            } else {
169                Some(Err(AccessError))
170            }
171        })
172    }
173}
174
175/// An error returned by [`LocalKey::try_with`]
176#[derive(Clone, Copy, PartialEq, Eq, Debug)]
177#[non_exhaustive]
178pub struct AccessError;
179
180impl std::fmt::Display for AccessError {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        std::fmt::Display::fmt("already destroyed", f)
183    }
184}
185
186impl std::error::Error for AccessError {}