Skip to content

Commit 50aeb53

Browse files
committed
Add std::os::darwin::thread::MainThreadMarker
1 parent 6741521 commit 50aeb53

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed

library/std/src/os/darwin/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ pub mod fs;
2121
// well as `std::os::macos`/`std::os::ios`, because those modules precede the
2222
// decision to remove these.
2323
pub(super) mod raw;
24+
25+
pub mod thread;
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Test that `MainThreadMarker` is neither `Send` nor `Sync`.
2+
3+
//@ only-apple
4+
5+
use std::os::darwin::thread::MainThreadMarker;
6+
7+
fn needs_sync<T: Sync>() {}
8+
fn needs_send<T: Send>() {}
9+
10+
fn main() {
11+
needs_sync::<MainThreadMarker>();
12+
//~^ ERROR is not Sync
13+
needs_send::<MainThreadMarker>();
14+
//~^ ERROR is not Send
15+
}

0 commit comments

Comments
 (0)