|
| 1 | +//! Darwin-specific extensions to threads. |
| 2 | +#![unstable(feature = "darwin_mtm", issue = "none")] |
| 3 | + |
| 4 | +use crate::fmt; |
| 5 | +use crate::marker::PhantomData; |
| 6 | + |
| 7 | +/// A marker type for functionality only available on the main thread. |
| 8 | +/// |
| 9 | +/// The main thread is a system-level property on Darwin platforms, and has extra capabilities not |
| 10 | +/// available on other threads. This is usually relevant when using native GUI frameworks, where |
| 11 | +/// most operations must be done on the main thread. |
| 12 | +/// |
| 13 | +/// This type enables you to manage that capability. By design, it is neither [`Send`] nor [`Sync`], |
| 14 | +/// and can only be created on the main thread, meaning that if you have an instance of this, you |
| 15 | +/// are guaranteed to be on the main thread / have the "main-thread capability". |
| 16 | +/// |
| 17 | +/// [The `main` function][main-functions] will run on the main thread. This type can also be used |
| 18 | +/// with `#![no_main]` or other such cases where Rust is not defining the binary entry point. |
| 19 | +/// |
| 20 | +/// See the following links for more information on main-thread-only APIs: |
| 21 | +/// - [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) |
| 22 | +/// - [About Threaded Programming](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html) |
| 23 | +/// - [Thread Safety Summary](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1) |
| 24 | +/// - [Technical Note TN2028 - Threading Architectures](https://developer.apple.com/library/archive/technotes/tn/tn2028.html#//apple_ref/doc/uid/DTS10003065) |
| 25 | +/// - [Thread Management](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html) |
| 26 | +/// - [Swift's `@MainActor`](https://developer.apple.com/documentation/swift/mainactor) |
| 27 | +/// - [Main Thread Only APIs on OS X](https://www.dribin.org/dave/blog/archives/2009/02/01/main_thread_apis/) |
| 28 | +/// - [Mike Ash' article on thread safety](https://www.mikeash.com/pyblog/friday-qa-2009-01-09.html) |
| 29 | +/// |
| 30 | +/// [main-functions]: https://doc.rust-lang.org/reference/crates-and-source-files.html#main-functions |
| 31 | +/// |
| 32 | +/// |
| 33 | +/// # Main Thread Checker |
| 34 | +/// |
| 35 | +/// Xcode provides a tool called the ["Main Thread Checker"][mtc] which verifies that UI APIs are |
| 36 | +/// being used from the correct thread. This is not as principled as `MainThreadMarker`, but is |
| 37 | +/// helpful for catching mistakes. |
| 38 | +/// |
| 39 | +/// You can use this tool on macOS by loading `libMainThreadChecker.dylib` into your process using |
| 40 | +/// `DYLD_INSERT_LIBRARIES`: |
| 41 | +/// |
| 42 | +/// ```console |
| 43 | +/// DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/usr/lib/libMainThreadChecker.dylib MTC_RESET_INSERT_LIBRARIES=0 cargo run |
| 44 | +/// ``` |
| 45 | +/// |
| 46 | +/// If you're not running your binary through Cargo, you can omit |
| 47 | +/// [`MTC_RESET_INSERT_LIBRARIES`][mtc-reset]. |
| 48 | +/// |
| 49 | +/// ```console |
| 50 | +/// DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/usr/lib/libMainThreadChecker.dylib target/debug/myapp |
| 51 | +/// ``` |
| 52 | +/// |
| 53 | +/// If you're developing for iOS, you probably better off enabling the tool in Xcode's own UI. |
| 54 | +/// |
| 55 | +/// See [this excellent blog post][mtc-cfg] for details on further configuration options. |
| 56 | +/// |
| 57 | +/// [mtc]: https://developer.apple.com/documentation/xcode/diagnosing-memory-thread-and-crash-issues-early#Detect-improper-UI-updates-on-background-threads |
| 58 | +/// [mtc-reset]: https://bryce.co/main-thread-checker-configuration/#mtc_reset_insert_libraries |
| 59 | +/// [mtc-cfg]: https://bryce.co/main-thread-checker-configuration/ |
| 60 | +/// |
| 61 | +/// |
| 62 | +/// # Examples |
| 63 | +/// |
| 64 | +/// Retrieve the main thread marker in different situations. |
| 65 | +/// |
| 66 | +/// ``` |
| 67 | +/// #![feature(darwin_mtm)] |
| 68 | +/// use std::os::darwin::thread::MainThreadMarker; |
| 69 | +/// |
| 70 | +/// # // doc test explicitly uses `fn main` to show that that's where it counts. |
| 71 | +/// fn main() { |
| 72 | +/// // The thread that `fn main` runs on is the main thread. |
| 73 | +/// assert!(MainThreadMarker::new().is_some()); |
| 74 | +/// |
| 75 | +/// // Subsequently spawned threads are not the main thread. |
| 76 | +/// std::thread::spawn(|| { |
| 77 | +/// assert!(MainThreadMarker::new().is_none()); |
| 78 | +/// }).join().unwrap(); |
| 79 | +/// } |
| 80 | +/// ``` |
| 81 | +/// |
| 82 | +/// Create a static that is only usable on the main thread. This is similar to a thread-local, but |
| 83 | +/// can be more efficient because it doesn't handle multiple threads. |
| 84 | +/// |
| 85 | +/// ``` |
| 86 | +/// #![feature(sync_unsafe_cell)] |
| 87 | +/// #![feature(darwin_mtm)] |
| 88 | +/// use std::os::darwin::thread::MainThreadMarker; |
| 89 | +/// use std::sync::SyncUnsafeCell; |
| 90 | +/// |
| 91 | +/// static MAIN_THREAD_ONLY_VALUE: SyncUnsafeCell<i32> = SyncUnsafeCell::new(0); |
| 92 | +/// |
| 93 | +/// fn set(value: i32, _mtm: MainThreadMarker) { |
| 94 | +/// // SAFETY: We have an instance of `MainThreadMarker`, so we know that |
| 95 | +/// // we're running on the main thread (and thus do not need any |
| 96 | +/// // synchronization, since the only accesses to this value is from the |
| 97 | +/// // main thread). |
| 98 | +/// unsafe { *MAIN_THREAD_ONLY_VALUE.get() = value }; |
| 99 | +/// } |
| 100 | +/// |
| 101 | +/// fn get(_mtm: MainThreadMarker) -> &'static u32 { |
| 102 | +/// // SAFETY: Same as above. |
| 103 | +/// unsafe { *MAIN_THREAD_ONLY_VALUE.get() } |
| 104 | +/// } |
| 105 | +/// |
| 106 | +/// // Usage |
| 107 | +/// fn main() { |
| 108 | +/// let mtm = MainThreadMarker::new().expect("must be on the main thread"); |
| 109 | +/// set(42, mtm); |
| 110 | +/// assert_eq!(get(mtm), 42); |
| 111 | +/// } |
| 112 | +/// ``` |
| 113 | +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| 114 | +// ^^^^ this is valid because it's still `!Send` and `!Sync`. |
| 115 | +#[unstable(feature = "darwin_mtm", issue = "none")] |
| 116 | +pub struct MainThreadMarker { |
| 117 | + // No lifetime information needed; the main thread is static and available throughout the entire |
| 118 | + // program! |
| 119 | + |
| 120 | + // Ensure `!Send` and `!Sync`. |
| 121 | + _priv: PhantomData<*mut ()>, |
| 122 | +} |
| 123 | + |
| 124 | +// Manually implementing these results in slightly better error messages. |
| 125 | +#[unstable(feature = "darwin_mtm", issue = "none")] |
| 126 | +impl !Send for MainThreadMarker {} |
| 127 | +#[unstable(feature = "darwin_mtm", issue = "none")] |
| 128 | +impl !Sync for MainThreadMarker {} |
| 129 | + |
| 130 | +impl MainThreadMarker { |
| 131 | + /// Construct a new `MainThreadMarker`. |
| 132 | + /// |
| 133 | + /// Returns [`None`] if the current thread was not the main thread. |
| 134 | + /// |
| 135 | + /// |
| 136 | + /// # Example |
| 137 | + /// |
| 138 | + /// Check whether the current thread is the main thread. |
| 139 | + /// |
| 140 | + /// ``` |
| 141 | + /// use std::os::darwin::thread::MainThreadMarker; |
| 142 | + /// |
| 143 | + /// if MainThreadMarker::new().is_some() { |
| 144 | + /// // Is the main thread |
| 145 | + /// } else { |
| 146 | + /// // Not the main thread |
| 147 | + /// } |
| 148 | + /// ``` |
| 149 | + #[inline] |
| 150 | + #[doc(alias = "is_main_thread")] |
| 151 | + #[doc(alias = "pthread_main_np")] |
| 152 | + #[doc(alias = "isMainThread")] |
| 153 | + #[unstable(feature = "darwin_mtm", issue = "none")] |
| 154 | + pub fn new() -> Option<Self> { |
| 155 | + if is_main_thread() { |
| 156 | + // SAFETY: We just checked that we are running on the main thread. |
| 157 | + Some(unsafe { Self::new_unchecked() }) |
| 158 | + } else { |
| 159 | + None |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + /// Construct a new `MainThreadMarker` without first checking whether the current thread is |
| 164 | + /// the main one. |
| 165 | + /// |
| 166 | + /// |
| 167 | + /// # Safety |
| 168 | + /// |
| 169 | + /// The current thread must be the main thread. |
| 170 | + #[inline] |
| 171 | + #[unstable(feature = "darwin_mtm", issue = "none")] |
| 172 | + #[rustc_const_unstable(feature = "darwin_mtm", issue = "none")] |
| 173 | + pub const unsafe fn new_unchecked() -> Self { |
| 174 | + // SAFETY: Upheld by caller. |
| 175 | + // |
| 176 | + // We can't debug_assert that this actually is the main thread, both because this is |
| 177 | + // `const` (to allow usage in `static`s), and because users may sometimes want to create |
| 178 | + // this briefly, e.g. to access an API that in most cases requires the marker, but is safe |
| 179 | + // to use without in specific cases. |
| 180 | + Self { _priv: PhantomData } |
| 181 | + } |
| 182 | +} |
| 183 | + |
| 184 | +#[unstable(feature = "darwin_mtm", issue = "none")] |
| 185 | +impl fmt::Debug for MainThreadMarker { |
| 186 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 187 | + f.debug_tuple("MainThreadMarker").finish() |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +// Implementation: |
| 192 | + |
| 193 | +/// Whether the current thread is the main thread. |
| 194 | +#[inline] |
| 195 | +fn is_main_thread() -> bool { |
| 196 | + // In Objective-C you would use `+[NSThread isMainThread]`, but benchmarks have shown that |
| 197 | + // calling the underlying `pthread_main_np` directly is up to four times faster, so we use that |
| 198 | + // instead. |
| 199 | + // |
| 200 | + // `pthread_main_np` is also included via. libSystem, so that avoids linking Foundation. |
| 201 | + |
| 202 | + // SAFETY: Can be called from any thread. |
| 203 | + // |
| 204 | + // Apple's man page says: |
| 205 | + // > The pthread_main_np() function returns 1 if the calling thread is the initial thread, 0 if |
| 206 | + // > the calling thread is not the initial thread, and -1 if the thread's initialization has not |
| 207 | + // > yet completed. |
| 208 | + // |
| 209 | + // However, Apple's header says: |
| 210 | + // > Returns non-zero if the current thread is the main thread. |
| 211 | + // |
| 212 | + // So unclear if we should be doing a comparison against 1, or a negative comparison against 0? |
| 213 | + // To be safe, we compare against 1, though in reality, the current implementation can only ever |
| 214 | + // return 0 or 1: |
| 215 | + // https://github.com/apple-oss-distributions/libpthread/blob/libpthread-535/src/pthread.c#L1084-L1089 |
| 216 | + unsafe { libc::pthread_main_np() == 1 } |
| 217 | +} |
0 commit comments