Skip to content

Commit c2fa0ab

Browse files
authored
Merge pull request #344 from madsmtm/main-thread-bound
Add `MainThreadBound`
2 parents 01778b4 + 5f3a8f0 commit c2fa0ab

5 files changed

Lines changed: 257 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/icrate/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ license = "MIT"
2222
[dependencies]
2323
objc2 = { path = "../objc2", version = "=0.3.0-beta.4", default-features = false, optional = true }
2424
block2 = { path = "../block2", version = "=0.2.0-alpha.7", default-features = false, optional = true }
25+
dispatch = { version = "0.2.0", optional = true }
2526

2627
[package.metadata.docs.rs]
2728
default-target = "x86_64-apple-darwin"
28-
features = ["block", "objective-c", "unstable-frameworks-all", "unstable-private", "unstable-docsrs"]
29+
features = ["block", "objective-c", "dispatch", "unstable-frameworks-all", "unstable-private", "unstable-docsrs"]
2930

3031
targets = [
3132
# MacOS

crates/icrate/src/Foundation/additions/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ pub use self::geometry::{
77
};
88
pub use self::range::NSRange;
99
#[cfg(feature = "Foundation_NSThread")]
10+
#[cfg(feature = "dispatch")]
11+
pub use self::thread::MainThreadBound;
12+
#[cfg(feature = "Foundation_NSThread")]
1013
pub use self::thread::{is_main_thread, is_multi_threaded, MainThreadMarker};
1114

1215
mod array;

crates/icrate/src/Foundation/additions/thread.rs

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![cfg(feature = "Foundation_NSThread")]
22
use core::fmt;
33
use core::marker::PhantomData;
4+
use core::mem::{self, ManuallyDrop};
45
use core::panic::{RefUnwindSafe, UnwindSafe};
56

67
use crate::common::*;
@@ -72,7 +73,7 @@ fn make_multithreaded() {
7273
/// }
7374
///
7475
/// // Usage
75-
/// let mtm = MainThreadMarker::new().unwrap();
76+
/// let mtm = MainThreadMarker::new().expect("must be on the main thread");
7677
/// unsafe { do_thing(obj, mtm) }
7778
/// ```
7879
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
@@ -88,6 +89,7 @@ impl MainThreadMarker {
8889
///
8990
/// Returns [`None`] if the current thread was not the main thread.
9091
#[cfg(feature = "Foundation_NSThread")]
92+
#[inline]
9193
pub fn new() -> Option<Self> {
9294
if NSThread::isMainThread_class() {
9395
// SAFETY: We just checked that we are running on the main thread.
@@ -100,17 +102,207 @@ impl MainThreadMarker {
100102
/// Construct a new [`MainThreadMarker`] without first checking whether
101103
/// the current thread is the main one.
102104
///
105+
///
103106
/// # Safety
104107
///
105108
/// The current thread must be the main thread.
109+
///
110+
/// Alternatively, you may create this briefly if you know that a an API
111+
/// is safe in a specific case, but is not marked so. If you do that, you
112+
/// must ensure that any use of the marker is actually safe to do from
113+
/// another thread than the main one.
114+
#[inline]
106115
pub unsafe fn new_unchecked() -> Self {
107116
// SAFETY: Upheld by caller
117+
//
118+
// We can't debug_assert that this actually is the main thread, see
119+
// the comment above.
108120
Self { _priv: PhantomData }
109121
}
122+
123+
/// Submit the given closure to the runloop on the main thread.
124+
///
125+
/// If the current thread is the main thread, this simply runs the
126+
/// closure.
127+
///
128+
/// The closure is passed a [`MainThreadMarker`] that it can further use
129+
/// to access APIs that are only accessible from the main thread.
130+
///
131+
/// This function should only be used in applications whose main thread is
132+
/// running an event loop with `dispatch_main`, `UIApplicationMain`,
133+
/// `NSApplicationMain`, `CFRunLoop` or similar; it will block
134+
/// indefinitely if that is not the case.
135+
///
136+
///
137+
/// # Example
138+
///
139+
/// ```no_run
140+
/// use icrate::Foundation::MainThreadMarker;
141+
/// MainThreadMarker::run_on_main(|mtm| {
142+
/// // Do something on the main thread with the given marker
143+
/// });
144+
/// ```
145+
#[cfg(feature = "dispatch")]
146+
pub fn run_on_main<F, R>(f: F) -> R
147+
where
148+
F: Send + FnOnce(MainThreadMarker) -> R,
149+
R: Send,
150+
{
151+
if let Some(mtm) = MainThreadMarker::new() {
152+
f(mtm)
153+
} else {
154+
dispatch::Queue::main().exec_sync(|| {
155+
// SAFETY: The outer closure is submitted to run on the main
156+
// thread, so now, when the closure actually runs, it's
157+
// guaranteed to be on the main thread.
158+
f(unsafe { MainThreadMarker::new_unchecked() })
159+
})
160+
}
161+
}
110162
}
111163

112164
impl fmt::Debug for MainThreadMarker {
113165
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114166
f.debug_tuple("MainThreadMarker").finish()
115167
}
116168
}
169+
170+
/// Make a type that can only be used on the main thread be `Send` + `Sync`.
171+
///
172+
/// On `Drop`, the inner type is sent to the main thread's runloop and dropped
173+
/// there. This may lead to deadlocks if the main runloop is not running, or
174+
/// if it is waiting on a lock that the dropping thread is holding. See
175+
/// [`MainThreadMarker::run_on_main`] for some of the caveats around that.
176+
///
177+
///
178+
/// # Related
179+
///
180+
/// This type takes inspiration from `threadbound::ThreadBound`.
181+
///
182+
/// The functionality also somewhat resembles Swift's `@MainActor`, which
183+
/// ensures that a type is only usable from the main thread.
184+
#[doc(alias = "@MainActor")]
185+
#[cfg(feature = "dispatch")]
186+
pub struct MainThreadBound<T>(ManuallyDrop<T>);
187+
188+
// SAFETY: The inner value is guaranteed to originate from the main thread
189+
// because `new` takes [`MainThreadMarker`].
190+
//
191+
// `into_inner` is the only way to get the value out, and that is also
192+
// guaranteed to happen on the main thread.
193+
//
194+
// Finally, the value is dropped on the main thread in `Drop`.
195+
#[cfg(feature = "dispatch")]
196+
unsafe impl<T> Send for MainThreadBound<T> {}
197+
198+
// SAFETY: We only provide access to the inner value via. `get` and `get_mut`.
199+
//
200+
// Both of these take [`MainThreadMarker`], which guarantees that the access
201+
// is done from the main thread.
202+
#[cfg(feature = "dispatch")]
203+
unsafe impl<T> Sync for MainThreadBound<T> {}
204+
205+
#[cfg(feature = "dispatch")]
206+
impl<T> Drop for MainThreadBound<T> {
207+
fn drop(&mut self) {
208+
if mem::needs_drop::<T>() {
209+
// TODO: Figure out whether we should assume the main thread to be
210+
// dead if we're panicking, and just leak instead?
211+
MainThreadMarker::run_on_main(|_mtm| {
212+
let this = self;
213+
// SAFETY: The value is dropped on the main thread, which is
214+
// the same thread that it originated from (guaranteed by
215+
// `new` taking `MainThreadMarker`).
216+
//
217+
// Additionally, the value is never used again after this
218+
// point.
219+
unsafe { ManuallyDrop::drop(&mut this.0) };
220+
})
221+
}
222+
}
223+
}
224+
225+
/// Main functionality.
226+
#[cfg(feature = "dispatch")]
227+
impl<T> MainThreadBound<T> {
228+
/// Create a new [`MainThreadBound`] value of type `T`.
229+
///
230+
///
231+
/// # Example
232+
///
233+
/// ```no_run
234+
/// use icrate::Foundation::{MainThreadMarker, MainThreadBound};
235+
///
236+
/// let foo;
237+
/// # foo = ();
238+
/// let mtm = MainThreadMarker::new().expect("must be on the main thread");
239+
/// let foo = MainThreadBound::new(foo, mtm);
240+
///
241+
/// // `foo` is now `Send + Sync`.
242+
/// ```
243+
#[inline]
244+
pub fn new(inner: T, _mtm: MainThreadMarker) -> Self {
245+
Self(ManuallyDrop::new(inner))
246+
}
247+
248+
/// Returns a reference to the value.
249+
#[inline]
250+
pub fn get(&self, _mtm: MainThreadMarker) -> &T {
251+
&self.0
252+
}
253+
254+
/// Returns a mutable reference to the value.
255+
#[inline]
256+
pub fn get_mut(&mut self, _mtm: MainThreadMarker) -> &mut T {
257+
&mut self.0
258+
}
259+
260+
/// Extracts the value from the [`MainThreadBound`] container.
261+
#[inline]
262+
pub fn into_inner(self, _mtm: MainThreadMarker) -> T {
263+
// Prevent our `Drop` impl from running.
264+
//
265+
// This is a bit confusing, now `this` is:
266+
// `ManuallyDrop<Self(ManuallyDrop<T>)>`
267+
let mut this = ManuallyDrop::new(self);
268+
269+
// SAFETY: `self` is consumed by this function, and wrapped in
270+
// `ManuallyDrop`, so the item's destructor is never run.
271+
unsafe { ManuallyDrop::take(&mut this.0) }
272+
}
273+
}
274+
275+
/// Helper functions for running [`MainThreadMarker::run_on_main`].
276+
#[cfg(feature = "dispatch")]
277+
impl<T> MainThreadBound<T> {
278+
/// Access the item on the main thread.
279+
///
280+
/// See [`MainThreadMarker::run_on_main`] for caveats.
281+
#[inline]
282+
pub fn get_on_main<F, R>(&self, f: F) -> R
283+
where
284+
F: Send + FnOnce(&T, MainThreadMarker) -> R,
285+
R: Send,
286+
{
287+
MainThreadMarker::run_on_main(|mtm| f(self.get(mtm), mtm))
288+
}
289+
290+
/// Access the item mutably on the main thread.
291+
///
292+
/// See [`MainThreadMarker::run_on_main`] for caveats.
293+
#[inline]
294+
pub fn get_on_main_mut<F, R>(&mut self, f: F) -> R
295+
where
296+
F: Send + FnOnce(&mut T, MainThreadMarker) -> R,
297+
R: Send,
298+
{
299+
MainThreadMarker::run_on_main(|mtm| f(self.get_mut(mtm), mtm))
300+
}
301+
}
302+
303+
#[cfg(feature = "dispatch")]
304+
impl<T> fmt::Debug for MainThreadBound<T> {
305+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306+
f.debug_struct("MainThreadBound").finish_non_exhaustive()
307+
}
308+
}

crates/icrate/tests/thread.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,55 @@ fn test_debug() {
6565
let marker = unsafe { MainThreadMarker::new_unchecked() };
6666
assert_eq!(format!("{marker:?}"), "MainThreadMarker");
6767
}
68+
69+
#[test]
70+
#[cfg(feature = "dispatch")]
71+
fn test_main_thread_bound_traits() {
72+
use icrate::Foundation::MainThreadBound;
73+
74+
struct Foo(*const ());
75+
76+
fn assert_send_sync<T: Send + Sync>() {}
77+
78+
assert_send_sync::<MainThreadBound<MainThreadMarker>>();
79+
assert_send_sync::<MainThreadBound<Foo>>();
80+
81+
fn foo<T>() {
82+
assert_send_sync::<MainThreadBound<T>>();
83+
}
84+
85+
foo::<()>();
86+
}
87+
88+
#[test]
89+
#[cfg(feature = "dispatch")]
90+
fn test_main_thread_bound_into_inner() {
91+
use core::cell::Cell;
92+
use icrate::Foundation::MainThreadBound;
93+
94+
// SAFETY: For testing only
95+
let mtm = unsafe { MainThreadMarker::new_unchecked() };
96+
97+
struct Foo<'a> {
98+
is_dropped: &'a Cell<bool>,
99+
}
100+
101+
impl Drop for Foo<'_> {
102+
fn drop(&mut self) {
103+
self.is_dropped.set(true);
104+
}
105+
}
106+
107+
let is_dropped = Cell::new(false);
108+
let foo = Foo {
109+
is_dropped: &is_dropped,
110+
};
111+
let foo = MainThreadBound::new(foo, mtm);
112+
assert!(!is_dropped.get());
113+
114+
let foo = foo.into_inner(mtm);
115+
assert!(!is_dropped.get());
116+
117+
drop(foo);
118+
assert!(is_dropped.get());
119+
}

0 commit comments

Comments
 (0)