-
Notifications
You must be signed in to change notification settings - Fork 368
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
MainThreadToken
to ensure file-dialogs only run on the main thr…
…ead (#8467) ### What We have a foot-gun in our code: our file dialogs (via `rfd`) [are only allowed to be run from the main thread (at least on Mac)](https://docs.rs/rfd/latest/rfd/#macos-non-windowed-applications-async-and-threading). However, there is nothing stopping a user from accidentally calling these functions from a background thread, and if you test it on e.g. Linux, it may very well work. So this PR introduces a new crate `re_capabilities` and a new type `MainThreadToken`. Any function that uses `rfd` should require the `MainThreadToken` as an argument. The `MainThreadToken` is only allowed to be created in `fn main`, and since it is neither `Send` nor `Sync`, this guarantees at compile-time that any functions that take a `MainThreadToken` can only be called from the main thread. NOTE: I have no way to enforce that all uses of `rfd` also require `MainThreadToken` - we need to remember this ourselves, but I've made sure that all our _current_ uses of `rfd` require a `MainThreadToken`.
- Loading branch information
Showing
30 changed files
with
235 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
[package] | ||
name = "re_capabilities" | ||
authors.workspace = true | ||
description = "Capability tokens for the Rerun code base." | ||
edition.workspace = true | ||
homepage.workspace = true | ||
include.workspace = true | ||
license.workspace = true | ||
publish = true | ||
readme = "README.md" | ||
repository.workspace = true | ||
rust-version.workspace = true | ||
version.workspace = true | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[package.metadata.docs.rs] | ||
all-features = true | ||
|
||
|
||
[features] | ||
default = [] | ||
|
||
## Enable constructing the [`MainThreadToken`] from an [`egui::Ui`]. | ||
egui = ["dep:egui"] | ||
|
||
|
||
[dependencies] | ||
# Internal dependencies: | ||
|
||
# External dependencies: | ||
document-features.workspace = true | ||
egui = { workspace = true, default-features = false, optional = true } | ||
static_assertions.workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# re_capabilities | ||
|
||
Part of the [`rerun`](https://github.com/rerun-io/rerun) family of crates. | ||
|
||
[![Latest version](https://img.shields.io/crates/v/re_capabilities.svg)](https://crates.io/crates/re_capabilitiescrates/utils/) | ||
[![Documentation](https://docs.rs/re_capabilities/badge.svg?speculative-link)](https://docs.rs/re_capabilities?speculative-link) | ||
![MIT](https://img.shields.io/badge/license-MIT-blue.svg) | ||
![Apache](https://img.shields.io/badge/license-Apache-blue.svg) | ||
|
||
Specifies capability tokens, required by different parts of the code base. | ||
These are tokens passed down the call tree, to explicitly allow different capabilities in different parts of the code base. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
//! Specifies capability tokens, required by different parts of the code base. | ||
//! These are tokens passed down the call tree, to explicitly allow different capabilities in different parts of the code base. | ||
//! | ||
//! For instance, the [`MainThreadToken`] is taken by argument in functions that needs to run on the main thread. | ||
//! By requiring this token, you guarantee at compile-time that the function is only called on the main thread. | ||
//! | ||
//! All capability tokens should be created in the top-level of the call tree, | ||
//! (i.e. in `fn main`) and passed down to all functions that require it. | ||
//! That way you can be certain in what an area of code is allowed to do. | ||
//! | ||
//! See [`cap-std`](https://crates.io/crates/cap-std) for another capability-centric crate. | ||
//! | ||
//! ## Feature flags | ||
#![doc = document_features::document_features!()] | ||
//! | ||
mod main_thread_token; | ||
|
||
pub use main_thread_token::MainThreadToken; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
use static_assertions::assert_not_impl_any; | ||
|
||
/// A token that (almost) proves we are on the main thread. | ||
/// | ||
/// Certain operations are only allowed on the main thread. | ||
/// These operations should require this token. | ||
/// For instance, any function using file dialogs (e.g. using [`rfd`](https://docs.rs/rfd/latest/rfd/)) should require this token. | ||
/// | ||
/// The token should only be constructed in `fn main`, using [`MainThreadToken::i_promise_i_am_on_the_main_thread`], | ||
/// and then be passed down the call tree to where it is needed. | ||
/// [`MainThreadToken`] is neither `Send` nor `Sync`, | ||
/// thus guaranteeing that it cannot be found in other threads. | ||
/// | ||
/// Of course, there is nothing stopping you from calling [`MainThreadToken::i_promise_i_am_on_the_main_thread`] from a background thread, | ||
/// but PLEASE DON'T DO THAT. | ||
/// In other words, don't use this as a guarantee for unsafe code. | ||
/// | ||
/// There is also [`MainThreadToken::from_egui_ui`] which uses the implicit guarantee of egui | ||
/// (which _usually_ is run on the main thread) to construct a [`MainThreadToken`]. | ||
/// Use this only in a code base where you are sure that egui is running only on the main thread. | ||
#[derive(Clone, Copy)] | ||
pub struct MainThreadToken { | ||
/// Prevent from being sent between threads. | ||
/// | ||
/// Workaround until `impl !Send for X {}` is stable. | ||
_dont_send_me: std::marker::PhantomData<*const ()>, | ||
} | ||
|
||
impl MainThreadToken { | ||
/// Only call this from `fn main`, or you may get weird runtime errors! | ||
pub fn i_promise_i_am_on_the_main_thread() -> Self { | ||
// On web there is no thread name. | ||
// On native the thread-name is always "main" in Rust, | ||
// but there is nothing preventing a user from also naming another thread "main". | ||
// In any case, since `MainThreadToken` is just best-effort, we only check this in debug builds. | ||
#[cfg(not(target_arch = "wasm32"))] | ||
debug_assert_eq!(std::thread::current().name(), Some("main"), | ||
"DEBUG ASSERT: Trying to construct a MainThreadToken on a thread that is not the main thread!" | ||
); | ||
|
||
Self { | ||
_dont_send_me: std::marker::PhantomData, | ||
} | ||
} | ||
|
||
/// We _should_ only create an [`egui::Ui`] on the main thread, | ||
/// so having it is good enough to "prove" that we are on the main thread. | ||
/// | ||
/// Use this only in a code base where you are sure that egui is running only on the main thread. | ||
/// | ||
/// In theory there is nothing preventing anyone from creating a [`egui::Ui`] on another thread, | ||
/// but practice that is unlikely (or intentionally malicious). | ||
#[cfg(feature = "egui")] | ||
pub fn from_egui_ui(_ui: &egui::Ui) -> Self { | ||
Self::i_promise_i_am_on_the_main_thread() | ||
} | ||
} | ||
|
||
assert_not_impl_any!(MainThreadToken: Send, Sync); | ||
assert_not_impl_any!(&MainThreadToken: Send, Sync); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.