Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/icrate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ license = "MIT"
[dependencies]
objc2 = { path = "../objc2", version = "=0.3.0-beta.4", default-features = false, optional = true }
block2 = { path = "../block2", version = "=0.2.0-alpha.7", default-features = false, optional = true }
dispatch = { version = "0.2.0", optional = true }

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

targets = [
# MacOS
Expand Down
3 changes: 3 additions & 0 deletions crates/icrate/src/Foundation/additions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub use self::geometry::{
};
pub use self::range::NSRange;
#[cfg(feature = "Foundation_NSThread")]
#[cfg(feature = "dispatch")]
pub use self::thread::MainThreadBound;
#[cfg(feature = "Foundation_NSThread")]
pub use self::thread::{is_main_thread, is_multi_threaded, MainThreadMarker};

mod array;
Expand Down
194 changes: 193 additions & 1 deletion crates/icrate/src/Foundation/additions/thread.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg(feature = "Foundation_NSThread")]
use core::fmt;
use core::marker::PhantomData;
use core::mem::{self, ManuallyDrop};
use core::panic::{RefUnwindSafe, UnwindSafe};

use crate::common::*;
Expand Down Expand Up @@ -72,7 +73,7 @@ fn make_multithreaded() {
/// }
///
/// // Usage
/// let mtm = MainThreadMarker::new().unwrap();
/// let mtm = MainThreadMarker::new().expect("must be on the main thread");
/// unsafe { do_thing(obj, mtm) }
/// ```
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
Expand All @@ -88,6 +89,7 @@ impl MainThreadMarker {
///
/// Returns [`None`] if the current thread was not the main thread.
#[cfg(feature = "Foundation_NSThread")]
#[inline]
pub fn new() -> Option<Self> {
if NSThread::isMainThread_class() {
// SAFETY: We just checked that we are running on the main thread.
Expand All @@ -100,17 +102,207 @@ impl MainThreadMarker {
/// Construct a new [`MainThreadMarker`] without first checking whether
/// the current thread is the main one.
///
///
/// # Safety
///
/// The current thread must be the main thread.
///
/// Alternatively, you may create this briefly if you know that a an API
/// is safe in a specific case, but is not marked so. If you do that, you
/// must ensure that any use of the marker is actually safe to do from
/// another thread than the main one.
#[inline]
pub unsafe fn new_unchecked() -> Self {
// SAFETY: Upheld by caller
//
// We can't debug_assert that this actually is the main thread, see
// the comment above.
Self { _priv: PhantomData }
}

/// Submit the given closure to the runloop on the main thread.
///
/// If the current thread is the main thread, this simply runs the
/// closure.
///
/// The closure is passed a [`MainThreadMarker`] that it can further use
/// to access APIs that are only accessible from the main thread.
///
/// This function should only be used in applications whose main thread is
/// running an event loop with `dispatch_main`, `UIApplicationMain`,
/// `NSApplicationMain`, `CFRunLoop` or similar; it will block
/// indefinitely if that is not the case.
///
///
/// # Example
///
/// ```no_run
/// use icrate::Foundation::MainThreadMarker;
/// MainThreadMarker::run_on_main(|mtm| {
/// // Do something on the main thread with the given marker
/// });
/// ```
#[cfg(feature = "dispatch")]
pub fn run_on_main<F, R>(f: F) -> R
where
F: Send + FnOnce(MainThreadMarker) -> R,
R: Send,
{
if let Some(mtm) = MainThreadMarker::new() {
f(mtm)
} else {
dispatch::Queue::main().exec_sync(|| {
// SAFETY: The outer closure is submitted to run on the main
// thread, so now, when the closure actually runs, it's
// guaranteed to be on the main thread.
f(unsafe { MainThreadMarker::new_unchecked() })
})
}
}
}

impl fmt::Debug for MainThreadMarker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("MainThreadMarker").finish()
}
}

/// Make a type that can only be used on the main thread be `Send` + `Sync`.
///
/// On `Drop`, the inner type is sent to the main thread's runloop and dropped
/// there. This may lead to deadlocks if the main runloop is not running, or
/// if it is waiting on a lock that the dropping thread is holding. See
/// [`MainThreadMarker::run_on_main`] for some of the caveats around that.
///
///
/// # Related
///
/// This type takes inspiration from `threadbound::ThreadBound`.
///
/// The functionality also somewhat resembles Swift's `@MainActor`, which
/// ensures that a type is only usable from the main thread.
#[doc(alias = "@MainActor")]
#[cfg(feature = "dispatch")]
pub struct MainThreadBound<T>(ManuallyDrop<T>);

// SAFETY: The inner value is guaranteed to originate from the main thread
// because `new` takes [`MainThreadMarker`].
//
// `into_inner` is the only way to get the value out, and that is also
// guaranteed to happen on the main thread.
//
// Finally, the value is dropped on the main thread in `Drop`.
#[cfg(feature = "dispatch")]
unsafe impl<T> Send for MainThreadBound<T> {}

// SAFETY: We only provide access to the inner value via. `get` and `get_mut`.
//
// Both of these take [`MainThreadMarker`], which guarantees that the access
// is done from the main thread.
#[cfg(feature = "dispatch")]
unsafe impl<T> Sync for MainThreadBound<T> {}

#[cfg(feature = "dispatch")]
impl<T> Drop for MainThreadBound<T> {
fn drop(&mut self) {
if mem::needs_drop::<T>() {
// TODO: Figure out whether we should assume the main thread to be
// dead if we're panicking, and just leak instead?
MainThreadMarker::run_on_main(|_mtm| {
let this = self;
// SAFETY: The value is dropped on the main thread, which is
// the same thread that it originated from (guaranteed by
// `new` taking `MainThreadMarker`).
//
// Additionally, the value is never used again after this
// point.
unsafe { ManuallyDrop::drop(&mut this.0) };
})
}
}
}

/// Main functionality.
#[cfg(feature = "dispatch")]
impl<T> MainThreadBound<T> {
/// Create a new [`MainThreadBound`] value of type `T`.
///
///
/// # Example
///
/// ```no_run
/// use icrate::Foundation::{MainThreadMarker, MainThreadBound};
///
/// let foo;
/// # foo = ();
/// let mtm = MainThreadMarker::new().expect("must be on the main thread");
/// let foo = MainThreadBound::new(foo, mtm);
///
/// // `foo` is now `Send + Sync`.
/// ```
#[inline]
pub fn new(inner: T, _mtm: MainThreadMarker) -> Self {
Self(ManuallyDrop::new(inner))
}

/// Returns a reference to the value.
#[inline]
pub fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}

/// Returns a mutable reference to the value.
#[inline]
pub fn get_mut(&mut self, _mtm: MainThreadMarker) -> &mut T {
&mut self.0
}

/// Extracts the value from the [`MainThreadBound`] container.
#[inline]
pub fn into_inner(self, _mtm: MainThreadMarker) -> T {
// Prevent our `Drop` impl from running.
//
// This is a bit confusing, now `this` is:
// `ManuallyDrop<Self(ManuallyDrop<T>)>`
let mut this = ManuallyDrop::new(self);

// SAFETY: `self` is consumed by this function, and wrapped in
// `ManuallyDrop`, so the item's destructor is never run.
unsafe { ManuallyDrop::take(&mut this.0) }
}
}

/// Helper functions for running [`MainThreadMarker::run_on_main`].
#[cfg(feature = "dispatch")]
impl<T> MainThreadBound<T> {
/// Access the item on the main thread.
///
/// See [`MainThreadMarker::run_on_main`] for caveats.
#[inline]
pub fn get_on_main<F, R>(&self, f: F) -> R
where
F: Send + FnOnce(&T, MainThreadMarker) -> R,
R: Send,
{
MainThreadMarker::run_on_main(|mtm| f(self.get(mtm), mtm))
}

/// Access the item mutably on the main thread.
///
/// See [`MainThreadMarker::run_on_main`] for caveats.
#[inline]
pub fn get_on_main_mut<F, R>(&mut self, f: F) -> R
where
F: Send + FnOnce(&mut T, MainThreadMarker) -> R,
R: Send,
{
MainThreadMarker::run_on_main(|mtm| f(self.get_mut(mtm), mtm))
}
}

#[cfg(feature = "dispatch")]
impl<T> fmt::Debug for MainThreadBound<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MainThreadBound").finish_non_exhaustive()
}
}
52 changes: 52 additions & 0 deletions crates/icrate/tests/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,55 @@ fn test_debug() {
let marker = unsafe { MainThreadMarker::new_unchecked() };
assert_eq!(format!("{marker:?}"), "MainThreadMarker");
}

#[test]
#[cfg(feature = "dispatch")]
fn test_main_thread_bound_traits() {
use icrate::Foundation::MainThreadBound;

struct Foo(*const ());

fn assert_send_sync<T: Send + Sync>() {}

assert_send_sync::<MainThreadBound<MainThreadMarker>>();
assert_send_sync::<MainThreadBound<Foo>>();

fn foo<T>() {
assert_send_sync::<MainThreadBound<T>>();
}

foo::<()>();
}

#[test]
#[cfg(feature = "dispatch")]
fn test_main_thread_bound_into_inner() {
use core::cell::Cell;
use icrate::Foundation::MainThreadBound;

// SAFETY: For testing only
let mtm = unsafe { MainThreadMarker::new_unchecked() };

struct Foo<'a> {
is_dropped: &'a Cell<bool>,
}

impl Drop for Foo<'_> {
fn drop(&mut self) {
self.is_dropped.set(true);
}
}

let is_dropped = Cell::new(false);
let foo = Foo {
is_dropped: &is_dropped,
};
let foo = MainThreadBound::new(foo, mtm);
assert!(!is_dropped.get());

let foo = foo.into_inner(mtm);
assert!(!is_dropped.get());

drop(foo);
assert!(is_dropped.get());
}