diff --git a/fish-rust/src/common.rs b/fish-rust/src/common.rs index 3042ad9cb..a2f7d0ad0 100644 --- a/fish-rust/src/common.rs +++ b/fish-rust/src/common.rs @@ -1,8 +1,7 @@ -use crate::{ - ffi, - wchar_ffi::{wstr, WCharFromFFI, WString}, -}; -use std::{ffi::c_uint, mem}; +use crate::ffi; +use crate::wchar_ffi::{wstr, WCharFromFFI, WString}; +use std::ffi::c_uint; +use std::mem; /// A scoped manager to save the current value of some variable, and optionally set it to a new /// value. When dropped, it restores the variable to its old value. diff --git a/fish-rust/src/fd_monitor.rs b/fish-rust/src/fd_monitor.rs index 7b5712685..e330c034d 100644 --- a/fish-rust/src/fd_monitor.rs +++ b/fish-rust/src/fd_monitor.rs @@ -8,6 +8,7 @@ use crate::fds::AutoCloseFd; use crate::ffi::void_ptr; use crate::flog::FLOG; +use crate::threads::assert_is_background_thread; use crate::wutil::perror; use cxx::SharedPtr; @@ -407,6 +408,8 @@ impl BackgroundFdMonitor { /// Starts monitoring the fd set and listening for new fds to add to the set. Takes ownership /// over its instance so that this method cannot be called again. fn run(mut self) { + assert_is_background_thread(); + let mut pokelist: Vec = Vec::new(); let mut fds = FdReadableSet::new(); diff --git a/fish-rust/src/ffi_init.rs b/fish-rust/src/ffi_init.rs index 95293e8e2..8a8ba12b9 100644 --- a/fish-rust/src/ffi_init.rs +++ b/fish-rust/src/ffi_init.rs @@ -19,6 +19,7 @@ mod ffi2 { fn rust_init() { crate::topic_monitor::topic_monitor_init(); crate::future_feature_flags::future_feature_flags_init(); + crate::threads::init(); } /// FFI bridge for activate_flog_categories_by_pattern(). diff --git a/fish-rust/src/threads.rs b/fish-rust/src/threads.rs index d21975053..842b8000e 100644 --- a/fish-rust/src/threads.rs +++ b/fish-rust/src/threads.rs @@ -2,6 +2,65 @@ //! ported directly from the cpp code so we can use rust threads instead of using pthreads. use crate::flog::FLOG; +use std::thread::{self, ThreadId}; + +// We don't want to use a full-blown Lazy for the cached main thread id, but we can't use +// AtomicU64 since std::thread::ThreadId::as_u64() is a nightly-only feature (issue #67939, +// thread_id_value). We also can't safely transmute `ThreadId` to `NonZeroU64` because there's no +// guarantee that's what the underlying type will always be on all platforms and in all cases, +// `ThreadId` isn't marked `#[repr(transparent)]`. We could generate our own thread-local value, but +// `#[thread_local]` is nightly-only while the stable `thread_local!()` macro doesn't generate +// efficient/fast/low-overhead code. + +/// The thread id of the main thread, as set by [`init()`] at startup. +static mut MAIN_THREAD_ID: Option = None; + +/// Initialize some global static variables. Must be called at startup from the main thread. +pub fn init() { + unsafe { + if MAIN_THREAD_ID.is_some() { + panic!("threads::init() must only be called once (at startup)!"); + } + MAIN_THREAD_ID = Some(thread::current().id()); + } +} + +#[inline(always)] +fn main_thread_id() -> ThreadId { + #[cold] + fn init_not_called() -> ! { + panic!("threads::init() was not called at startup!"); + } + + match unsafe { MAIN_THREAD_ID } { + None => init_not_called(), + Some(id) => id, + } +} + +#[inline(always)] +pub fn assert_is_main_thread() { + #[cold] + fn not_main_thread() -> ! { + panic!("Function is not running on the main thread!"); + } + + if thread::current().id() != main_thread_id() { + not_main_thread(); + } +} + +#[inline(always)] +pub fn assert_is_background_thread() { + #[cold] + fn not_background_thread() -> ! { + panic!("Function is not allowed to be called on the main thread!"); + } + + if thread::current().id() == main_thread_id() { + not_background_thread(); + } +} /// The rusty version of `iothreads::make_detached_pthread()`. We will probably need a /// `spawn_scoped` version of the same to handle some more advanced borrow cases safely, and maybe