Skip to content

Commit 8cef3f1

Browse files
committed
Add std::os::darwin::thread::MainThreadMarker
1 parent 6741521 commit 8cef3f1

File tree

4 files changed

+264
-0
lines changed

4 files changed

+264
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ 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+
pub mod thread;
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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::cell::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) -> i32 {
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+
/// #![feature(darwin_mtm)]
142+
/// use std::os::darwin::thread::MainThreadMarker;
143+
///
144+
/// if MainThreadMarker::new().is_some() {
145+
/// // Is the main thread
146+
/// } else {
147+
/// // Not the main thread
148+
/// }
149+
/// ```
150+
#[inline]
151+
#[doc(alias = "is_main_thread")]
152+
#[doc(alias = "pthread_main_np")]
153+
#[doc(alias = "isMainThread")]
154+
#[unstable(feature = "darwin_mtm", issue = "none")]
155+
pub fn new() -> Option<Self> {
156+
if is_main_thread() {
157+
// SAFETY: We just checked that we are running on the main thread.
158+
Some(unsafe { Self::new_unchecked() })
159+
} else {
160+
None
161+
}
162+
}
163+
164+
/// Construct a new `MainThreadMarker` without first checking whether the current thread is
165+
/// the main one.
166+
///
167+
///
168+
/// # Safety
169+
///
170+
/// The current thread must be the main thread.
171+
#[inline]
172+
#[unstable(feature = "darwin_mtm", issue = "none")]
173+
#[rustc_const_unstable(feature = "darwin_mtm", issue = "none")]
174+
pub const unsafe fn new_unchecked() -> Self {
175+
// SAFETY: Upheld by caller.
176+
//
177+
// We can't debug_assert that this actually is the main thread, both because this is
178+
// `const` (to allow usage in `static`s), and because users may sometimes want to create
179+
// this briefly, e.g. to access an API that in most cases requires the marker, but is safe
180+
// to use without in specific cases.
181+
Self { _priv: PhantomData }
182+
}
183+
}
184+
185+
#[unstable(feature = "darwin_mtm", issue = "none")]
186+
impl fmt::Debug for MainThreadMarker {
187+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188+
f.debug_tuple("MainThreadMarker").finish()
189+
}
190+
}
191+
192+
// Implementation:
193+
194+
/// Whether the current thread is the main thread.
195+
#[inline]
196+
fn is_main_thread() -> bool {
197+
// In Objective-C you would use `+[NSThread isMainThread]`, but benchmarks have shown that
198+
// calling the underlying `pthread_main_np` directly is up to four times faster, so we use that
199+
// instead.
200+
//
201+
// `pthread_main_np` is also included via. libSystem, so that avoids linking Foundation.
202+
203+
// SAFETY: Can be called from any thread.
204+
//
205+
// Apple's man page says:
206+
// > The pthread_main_np() function returns 1 if the calling thread is the initial thread, 0 if
207+
// > the calling thread is not the initial thread, and -1 if the thread's initialization has not
208+
// > yet completed.
209+
//
210+
// However, Apple's header says:
211+
// > Returns non-zero if the current thread is the main thread.
212+
//
213+
// So unclear if we should be doing a comparison against 1, or a negative comparison against 0?
214+
// To be safe, we compare against 1, though in reality, the current implementation can only ever
215+
// return 0 or 1:
216+
// https://github.com/apple-oss-distributions/libpthread/blob/libpthread-535/src/pthread.c#L1084-L1089
217+
unsafe { libc::pthread_main_np() == 1 }
218+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Test that `MainThreadMarker` is neither `Send` nor `Sync`.
2+
#![feature(darwin_mtm)]
3+
4+
//@ only-apple
5+
6+
use std::os::darwin::thread::MainThreadMarker;
7+
8+
fn needs_sync<T: Sync>() {}
9+
fn needs_send<T: Send>() {}
10+
11+
fn main() {
12+
needs_sync::<MainThreadMarker>();
13+
//~^ ERROR `MainThreadMarker` cannot be shared between threads safely
14+
needs_send::<MainThreadMarker>();
15+
//~^ ERROR `MainThreadMarker` cannot be sent between threads safely
16+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
error[E0277]: `MainThreadMarker` cannot be shared between threads safely
2+
--> $DIR/darwin_mtm_not_send_sync.rs:12:18
3+
|
4+
LL | needs_sync::<MainThreadMarker>();
5+
| ^^^^^^^^^^^^^^^^ `MainThreadMarker` cannot be shared between threads safely
6+
|
7+
= help: the trait `Sync` is not implemented for `MainThreadMarker`
8+
note: required by a bound in `needs_sync`
9+
--> $DIR/darwin_mtm_not_send_sync.rs:8:18
10+
|
11+
LL | fn needs_sync<T: Sync>() {}
12+
| ^^^^ required by this bound in `needs_sync`
13+
14+
error[E0277]: `MainThreadMarker` cannot be sent between threads safely
15+
--> $DIR/darwin_mtm_not_send_sync.rs:14:18
16+
|
17+
LL | needs_send::<MainThreadMarker>();
18+
| ^^^^^^^^^^^^^^^^ `MainThreadMarker` cannot be sent between threads safely
19+
|
20+
= help: the trait `Send` is not implemented for `MainThreadMarker`
21+
note: required by a bound in `needs_send`
22+
--> $DIR/darwin_mtm_not_send_sync.rs:9:18
23+
|
24+
LL | fn needs_send<T: Send>() {}
25+
| ^^^^ required by this bound in `needs_send`
26+
27+
error: aborting due to 2 previous errors
28+
29+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)