objc2/
main_thread_marker.rs

1//! This belongs in `std` IMO, but wasn't accepted there:
2//! <https://github.com/rust-lang/rust/pull/136616>
3use core::fmt;
4use core::marker::PhantomData;
5
6use crate::rc::Allocated;
7use crate::{ClassType, MainThreadOnly};
8
9/// Whether the current thread is the main thread.
10#[inline]
11fn is_main_thread() -> bool {
12    #[cfg(target_vendor = "apple")]
13    {
14        // Normally you would use `+[NSThread isMainThread]`, but benchmarks
15        // have shown that calling the underlying `pthread_main_np` directly
16        // is up to four times faster, so we use that instead.
17
18        // SAFETY: The signature in here is the exact same as in `libc`.
19        //
20        // `pthread_main_np` is included via `libSystem` when `libstd` is
21        // linked. All of this is done to avoid a dependency on the `libc`
22        // crate.
23        //
24        // `extern "C"` is safe because this will never unwind.
25        #[cfg_attr(not(feature = "std"), link(name = "c", kind = "dylib"))]
26        extern "C" {
27            fn pthread_main_np() -> core::ffi::c_int;
28        }
29
30        // SAFETY: Can be called from any thread.
31        //
32        // Apple's man page says:
33        // > The pthread_main_np() function returns 1 if the calling thread is the initial thread, 0 if
34        // > the calling thread is not the initial thread, and -1 if the thread's initialization has not
35        // > yet completed.
36        //
37        // However, Apple's header says:
38        // > Returns non-zero if the current thread is the main thread.
39        //
40        // So unclear if we should be doing a comparison against 1, or a negative comparison against 0?
41        // To be safe, we compare against 1, though in reality, the current implementation can only ever
42        // return 0 or 1:
43        // https://github.com/apple-oss-distributions/libpthread/blob/libpthread-535/src/pthread.c#L1084-L1089
44        unsafe { pthread_main_np() == 1 }
45    }
46
47    #[cfg(not(target_vendor = "apple"))]
48    {
49        // Fall back to isMainThread on non-Apple platforms, as
50        // `pthread_main_np` is not always available there.
51        unsafe { crate::msg_send![crate::class!(NSThread), isMainThread] }
52    }
53}
54
55/// A marker type for functionality only available on the main thread.
56///
57/// The main thread is a system-level property on Apple/Darwin platforms, and
58/// has extra capabilities not available on other threads. This is usually
59/// relevant when using native GUI frameworks, where most operations must be
60/// done on the main thread.
61///
62/// This type enables you to manage that capability. By design, it is neither
63/// [`Send`] nor [`Sync`], and can only be created on the main thread, meaning
64/// that if you have an instance of this, you are guaranteed to be on the main
65/// thread / have the "main-thread capability".
66///
67/// [The `main` function][main-functions] will run on the main thread. This
68/// type can also be used with `#![no_main]` or other such cases where Rust is
69/// not defining the binary entry point.
70///
71/// See the following links for more information on main-thread-only APIs:
72/// - [Are the Cocoa Frameworks Thread Safe?](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47)
73/// - [About Threaded Programming](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html)
74/// - [Thread Safety Summary](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1)
75/// - [Technical Note TN2028 - Threading Architectures](https://developer.apple.com/library/archive/technotes/tn/tn2028.html#//apple_ref/doc/uid/DTS10003065)
76/// - [Thread Management](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html)
77/// - [Swift's `@MainActor`](https://developer.apple.com/documentation/swift/mainactor)
78/// - [Main Thread Only APIs on OS X](https://www.dribin.org/dave/blog/archives/2009/02/01/main_thread_apis/)
79/// - [Mike Ash' article on thread safety](https://www.mikeash.com/pyblog/friday-qa-2009-01-09.html)
80///
81/// [main-functions]: https://doc.rust-lang.org/reference/crates-and-source-files.html#main-functions
82///
83///
84/// # Main Thread Checker
85///
86/// Xcode provides a tool called the ["Main Thread Checker"][mtc] which
87/// verifies that UI APIs are being used from the correct thread. This is not
88/// as principled as `MainThreadMarker`, but is helpful for catching mistakes.
89///
90/// You can use this tool on macOS by loading `libMainThreadChecker.dylib`
91/// into your process using `DYLD_INSERT_LIBRARIES`:
92///
93/// ```console
94/// DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/usr/lib/libMainThreadChecker.dylib MTC_RESET_INSERT_LIBRARIES=0 cargo run
95/// ```
96///
97/// If you're not running your binary through Cargo, you can omit
98/// [`MTC_RESET_INSERT_LIBRARIES`][mtc-reset].
99///
100/// ```console
101/// DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/usr/lib/libMainThreadChecker.dylib target/debug/myapp
102/// ```
103///
104/// If you're developing for iOS, you probably better off enabling the tool in
105/// Xcode's own UI.
106///
107/// See [this excellent blog post][mtc-cfg] for details on further
108/// configuration options.
109///
110/// [mtc]: https://developer.apple.com/documentation/xcode/diagnosing-memory-thread-and-crash-issues-early#Detect-improper-UI-updates-on-background-threads
111/// [mtc-reset]: https://bryce.co/main-thread-checker-configuration/#mtc_reset_insert_libraries
112/// [mtc-cfg]: https://bryce.co/main-thread-checker-configuration/
113///
114///
115/// # Examples
116///
117/// Retrieve the main thread marker in different situations.
118///
119/// ```
120/// use objc2::MainThreadMarker;
121///
122/// # // explicitly uses `fn main`
123/// fn main() {
124///     // The thread that `fn main` runs on is the main thread.
125///     assert!(MainThreadMarker::new().is_some());
126///
127///     // Subsequently spawned threads are not the main thread.
128///     std::thread::spawn(|| {
129///         assert!(MainThreadMarker::new().is_none());
130///     }).join().unwrap();
131/// }
132/// ```
133///
134/// Use when accessing APIs that are only safe to use on the main thread.
135///
136/// ```no_run
137/// use objc2::MainThreadMarker;
138/// # #[cfg(needs_app_kit)]
139/// use objc2_app_kit::NSApplication;
140/// #
141/// # use objc2::runtime::NSObject as NSApplication;
142/// # trait Foo {
143/// #     fn sharedApplication(_mtm: MainThreadMarker) {}
144/// # }
145/// # impl Foo for NSApplication {}
146///
147/// # // explicitly uses `fn main`
148/// fn main() {
149///     // Create a new MainThreadMarker.
150///     let mtm = MainThreadMarker::new().expect("must be on the main thread");
151///
152///     // NSApplication is only usable on the main thread,
153///     // so we need to pass the marker as an argument.
154///     let app = NSApplication::sharedApplication(mtm);
155///
156///     // Do something with the application
157///     // app.run();
158/// }
159/// ```
160///
161/// Create a static that is only usable on the main thread. This is similar to
162/// a thread-local, but can be more efficient because it doesn't handle
163/// multiple threads.
164///
165/// See also `dispatch2::MainThreadBound`.
166///
167/// ```
168/// use objc2::MainThreadMarker;
169/// use std::cell::UnsafeCell;
170///
171/// struct SyncUnsafeCell<T>(UnsafeCell<T>);
172///
173/// unsafe impl<T> Sync for SyncUnsafeCell<T> {}
174///
175/// static MAIN_THREAD_ONLY_VALUE: SyncUnsafeCell<i32> = SyncUnsafeCell(UnsafeCell::new(0));
176///
177/// fn set(value: i32, _mtm: MainThreadMarker) {
178///     // SAFETY: We have an instance of `MainThreadMarker`, so we know that
179///     // we're running on the main thread (and thus do not need any
180///     // synchronization, since the only accesses to this value is from the
181///     // main thread).
182///     unsafe { *MAIN_THREAD_ONLY_VALUE.0.get() = value };
183/// }
184///
185/// fn get(_mtm: MainThreadMarker) -> i32 {
186///     // SAFETY: Same as above.
187///     unsafe { *MAIN_THREAD_ONLY_VALUE.0.get() }
188/// }
189///
190/// # // explicitly uses `fn main`
191/// fn main() {
192///     let mtm = MainThreadMarker::new().expect("must be on the main thread");
193///     set(42, mtm);
194///     assert_eq!(get(mtm), 42);
195/// }
196/// ```
197#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
198//              ^^^^ this is valid because it's still `!Send` and `!Sync`.
199pub struct MainThreadMarker {
200    // No lifetime information needed; the main thread is static and available
201    // throughout the entire program!
202
203    // Ensure `!Send` and `!Sync`.
204    _priv: PhantomData<*mut ()>,
205}
206
207impl MainThreadMarker {
208    /// Construct a new `MainThreadMarker`.
209    ///
210    /// Returns [`None`] if the current thread was not the main thread.
211    ///
212    ///
213    /// # Example
214    ///
215    /// Check whether the current thread is the main thread.
216    ///
217    /// ```
218    /// use objc2::MainThreadMarker;
219    ///
220    /// if MainThreadMarker::new().is_some() {
221    ///     // Is the main thread
222    /// } else {
223    ///     // Not the main thread
224    /// }
225    /// ```
226    #[inline]
227    #[doc(alias = "is_main_thread")]
228    #[doc(alias = "pthread_main_np")]
229    #[doc(alias = "isMainThread")]
230    pub fn new() -> Option<Self> {
231        if is_main_thread() {
232            // SAFETY: We just checked that we are running on the main thread.
233            Some(unsafe { Self::new_unchecked() })
234        } else {
235            None
236        }
237    }
238
239    /// Construct a new `MainThreadMarker` without first checking whether the
240    /// current thread is the main one.
241    ///
242    ///
243    /// # Safety
244    ///
245    /// The current thread must be the main thread.
246    ///
247    /// Alternatively, you may create this briefly if you know that a an API
248    /// is safe in a specific case, but is not marked so. If you do that, you
249    /// must ensure that any use of the marker is actually safe to do from
250    /// another thread than the main one.
251    #[inline]
252    pub const unsafe fn new_unchecked() -> Self {
253        // SAFETY: Upheld by caller.
254        //
255        // We can't debug_assert that this actually is the main thread, both
256        // because this is `const` (to allow usage in `static`s), and because
257        // users may sometimes want to create this briefly, e.g. to access an
258        // API that in most cases requires the marker, but is safe to use
259        // without in specific cases.
260        Self { _priv: PhantomData }
261    }
262
263    /// Allocate a new instance of the specified class on the main thread.
264    ///
265    /// This can be useful in certain situations, such as generic contexts
266    /// where you don't know whether the class is main thread or not, but
267    /// usually you should prefer [`MainThreadOnly::alloc`].
268    #[inline]
269    pub fn alloc<T: ClassType>(self) -> Allocated<T> {
270        // SAFETY: We hold `MainThreadMarker`, and classes are either only
271        // safe to allocate on the main thread, or safe to allocate
272        // everywhere.
273        unsafe { Allocated::alloc(T::class()) }
274    }
275}
276
277/// Get a [`MainThreadMarker`] from a main-thread-only object.
278///
279/// This is a shorthand for [`MainThreadOnly::mtm`].
280impl<T: ?Sized + MainThreadOnly> From<&T> for MainThreadMarker {
281    #[inline]
282    fn from(obj: &T) -> Self {
283        obj.mtm()
284    }
285}
286
287impl fmt::Debug for MainThreadMarker {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        f.debug_tuple("MainThreadMarker").finish()
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use std::panic::{RefUnwindSafe, UnwindSafe};
297
298    static_assertions::assert_impl_all!(MainThreadMarker: Unpin, UnwindSafe, RefUnwindSafe, Sized);
299    static_assertions::assert_not_impl_any!(MainThreadMarker: Send, Sync);
300
301    #[test]
302    fn debug() {
303        // SAFETY: We don't use the marker for anything other than its Debug
304        // impl, so this test doesn't actually need to run on the main thread!
305        let marker = unsafe { MainThreadMarker::new_unchecked() };
306        assert_eq!(std::format!("{marker:?}"), "MainThreadMarker");
307    }
308
309    #[test]
310    fn test_not_main_thread() {
311        let res = std::thread::spawn(|| MainThreadMarker::new().is_none())
312            .join()
313            .unwrap();
314        assert!(res);
315    }
316}