Rationalize LazyCell/LazyLock/OnceCell/OnceLock usage

- Prefer using Lazy* for things that can be initialized by a "fn()",
  i.e. that don't depend on non-static data. Rationale: communicate
  that we don't need the flexibility that comes with Once*.
- Prefer using SingleThreaded (or MainThread) LazyCell/OnceCell rather
  than LazyLock/OnceLock. To communicate that these statics don't
  cross thread boundaries.

Incomplete. Not yet sure if this is a good idea.
This commit is contained in:
Johannes Altmanninger
2026-04-23 11:54:01 +08:00
parent 1e0b686730
commit cb95431990
22 changed files with 195 additions and 122 deletions

7
Cargo.lock generated
View File

@@ -303,6 +303,7 @@ dependencies = [
"fish-gettext-mo-file-parser",
"fish-printf",
"fish-tempfile",
"fish-thread",
"fish-util",
"fish-wcstringutil",
"fish-wgetopt",
@@ -358,6 +359,7 @@ dependencies = [
"bitflags",
"fish-build-helper",
"fish-feature-flags",
"fish-thread",
"fish-widestring",
"libc",
"nix",
@@ -435,6 +437,10 @@ dependencies = [
"rand",
]
[[package]]
name = "fish-thread"
version = "0.0.0"
[[package]]
name = "fish-util"
version = "0.0.0"
@@ -1236,6 +1242,7 @@ dependencies = [
"clap",
"fish-build-helper",
"fish-tempfile",
"fish-thread",
"ignore",
"pcre2",
"walkdir",

View File

@@ -30,6 +30,7 @@ fish-gettext-maps = { path = "crates/gettext-maps" }
fish-gettext-mo-file-parser = { path = "crates/gettext-mo-file-parser" }
fish-printf = { path = "crates/printf", features = ["widestring"] }
fish-tempfile = { path = "crates/tempfile" }
fish-thread = { path = "crates/thread" }
fish-util = { path = "crates/util" }
fish-wcstringutil = { path = "crates/wcstringutil" }
fish-widecharwidth = { path = "crates/widecharwidth" }
@@ -117,6 +118,7 @@ fish-gettext = { workspace = true, optional = true }
fish-gettext-extraction = { workspace = true, optional = true }
fish-printf.workspace = true
fish-tempfile.workspace = true
fish-thread.workspace = true
fish-util.workspace = true
fish-wcstringutil.workspace = true
fish-wgetopt.workspace = true

View File

@@ -10,6 +10,7 @@ license.workspace = true
bitflags.workspace = true
fish-feature-flags.workspace = true
fish-widestring.workspace = true
fish-thread.workspace = true
libc.workspace = true
nix.workspace = true

View File

@@ -1,5 +1,6 @@
use bitflags::bitflags;
use fish_feature_flags::{FeatureFlag, feature_test};
use fish_thread::SingleThreadedLazyCell;
use fish_widestring::{
ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE, ASCII_MAX, BRACE_BEGIN, BRACE_END, BRACE_SEP,
BRACE_SPACE, BYTE_MAX, HOME_DIRECTORY, INTERNAL_SEPARATOR, L, PROCESS_EXPAND_SELF,
@@ -18,7 +19,7 @@
unix::ffi::OsStrExt as _,
},
sync::{
Arc, OnceLock,
Arc,
atomic::{AtomicI32, AtomicU32, Ordering},
},
time,
@@ -1018,9 +1019,8 @@ pub fn read_unquoted_escape(
/// session. We err on the side of assuming it's not a console session. This approach isn't
/// bullet-proof and that's OK.
pub fn is_console_session() -> bool {
static IS_CONSOLE_SESSION: OnceLock<bool> = OnceLock::new();
// TODO(terminal-workaround)
*IS_CONSOLE_SESSION.get_or_init(|| {
static IS_CONSOLE_SESSION: SingleThreadedLazyCell<bool> = SingleThreadedLazyCell::new(||
// No console session on Apple, and ttyname may hang (#12506).
!cfg!(apple)
&& nix::unistd::ttyname(unsafe { std::os::fd::BorrowedFd::borrow_raw(STDIN_FILENO) })
@@ -1037,8 +1037,8 @@ pub fn is_console_session() -> bool {
// and that $TERM is simple, e.g. `xterm` or `vt100`, not `xterm-something` or `sun-color`.
is_console_tty
&& env::var_os("TERM").is_none_or(|t| !t.as_bytes().contains(&b'-'))
})
})
}));
*IS_CONSOLE_SESSION
}
/// Exits without invoking destructors (via _exit), useful for code after fork.

14
crates/thread/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "fish-thread"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
[build-dependencies]
[lints]
workspace = true

98
crates/thread/src/lib.rs Normal file
View File

@@ -0,0 +1,98 @@
use core::{cell::OnceCell, marker::PhantomData};
use std::{
cell::LazyCell,
sync::atomic::{AtomicUsize, Ordering},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ThreadId(usize);
/// Get the calling thread's fish-specific thread id.
///
/// This thread id is internal to the `threads` module for low-level purposes and should not be
/// leaked to other modules; general purpose code that needs a thread id should use rust's native
/// thread id functionality.
///
/// We use our own implementation because Rust's own `Thread::id()` allocates via `Arc`, is fairly
/// slow, and uses a `Mutex` on 32-bit platforms (or anywhere without an atomic 64-bit CAS).
#[inline(always)]
pub fn thread_id() -> ThreadId {
static THREAD_COUNTER: AtomicUsize = AtomicUsize::new(1);
// It would be faster and much nicer to use #[thread_local] here, but that's nightly only.
// This is still faster than going through Thread::thread_id(); it's something like 15ns
// for each `Thread::thread_id()` call vs 1-2 ns with `#[thread_local]` and 2-4ns with
// `thread_local!`.
thread_local! {
static THREAD_ID: ThreadId = ThreadId(THREAD_COUNTER.fetch_add(1, Ordering::Relaxed));
}
let id = THREAD_ID.with(|id| *id);
// This assertion is only here to reduce hair loss in case someone runs into a known linker bug;
// as it's not here to catch logic errors in our own code, it can be elided in release mode.
debug_assert_ne!(id, ThreadId(0), "TLS storage not initialized!");
id
}
/// A `Sync` and `Send` wrapper for non-`Sync`/`Send` types.
/// Only allows access from one thread.
pub struct SingleThreaded<T> {
thread_id: OnceCell<ThreadId>,
data: T,
// Make type !Send and !Sync by default
_marker: PhantomData<*const ()>,
}
// Can be shared across threads as long as T is 'static.
unsafe impl<T: 'static> Send for SingleThreaded<T> {}
unsafe impl<T: 'static> Sync for SingleThreaded<T> {}
impl<T> SingleThreaded<T> {
pub const fn new(value: T) -> Self {
Self {
thread_id: OnceCell::new(),
data: value,
_marker: PhantomData,
}
}
pub fn get(&self) -> &T {
assert!(thread_id() == *self.thread_id.get_or_init(thread_id));
&self.data
}
}
impl<T> std::ops::Deref for SingleThreaded<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}
pub struct SingleThreadedLazyCell<T, F = fn() -> T>(SingleThreaded<LazyCell<T, F>>);
impl<T, F: FnOnce() -> T> SingleThreadedLazyCell<T, F> {
pub const fn new(f: F) -> Self {
Self(SingleThreaded::new(LazyCell::new(f)))
}
}
impl<T, F: FnOnce() -> T> std::ops::Deref for SingleThreadedLazyCell<T, F> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct SingleThreadedOnceCell<T>(SingleThreaded<OnceCell<T>>);
impl<T> SingleThreadedOnceCell<T> {
pub const fn new() -> Self {
Self(SingleThreaded::new(OnceCell::new()))
}
}
impl<T> std::ops::Deref for SingleThreadedOnceCell<T> {
type Target = OnceCell<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@@ -10,6 +10,7 @@ anstyle.workspace = true
clap.workspace = true
fish-build-helper.workspace = true
fish-tempfile.workspace = true
fish-thread.workspace = true
ignore.workspace = true
pcre2.workspace = true
walkdir.workspace = true

View File

@@ -1,4 +1,5 @@
use fish_build_helper::workspace_root;
use fish_thread::SingleThreadedLazyCell;
use ignore::Walk;
use pcre2::bytes::Regex;
use std::{
@@ -6,7 +7,6 @@
io::{BufRead, BufReader},
path::{Path, PathBuf},
process::Command,
sync::OnceLock,
};
pub fn shellcheck() {
@@ -32,9 +32,9 @@ fn is_shell_script<P: AsRef<Path>>(path: P) -> bool {
let Ok(_) = BufReader::new(file).read_line(&mut first_line) else {
return false;
};
static SHEBANG_REGEX: OnceLock<Regex> = OnceLock::new();
static SHEBANG_REGEX: SingleThreadedLazyCell<Regex> =
SingleThreadedLazyCell::new(|| Regex::new("^#!.*[^i]sh").unwrap());
SHEBANG_REGEX
.get_or_init(|| Regex::new("^#!.*[^i]sh").unwrap())
.is_match(first_line.trim().as_bytes())
.unwrap()
}

View File

@@ -213,8 +213,8 @@ pub fn is_windows_subsystem_for_linux(_: WSL) -> bool {
/// See <https://github.com/Microsoft/WSL/issues/423> and [Microsoft/WSL#2997](https://github.com/Microsoft/WSL/issues/2997)
#[cfg(target_os = "linux")]
pub fn is_windows_subsystem_for_linux(v: WSL) -> bool {
use std::sync::OnceLock;
static RESULT: OnceLock<Option<WSL>> = OnceLock::new();
use fish_thread::SingleThreadedOnceCell;
static RESULT: SingleThreadedOnceCell<Option<WSL>> = SingleThreadedOnceCell::new();
// This is called post-fork from [`report_setpgid_error()`], so the fast path must not involve
// any allocations or mutexes. We can't rely on all the std functions to be alloc-free in both

View File

@@ -1,10 +1,10 @@
use crate::common::{BUILD_DIR, get_program_name};
use crate::{flog, flogf};
use fish_build_helper::workspace_root;
use fish_thread::SingleThreadedLazyCell;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt as _;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
/// A struct of configuration directories, determined in main() that fish will optionally pass to
/// env_init.
@@ -22,7 +22,6 @@ pub struct ConfigPaths {
impl ConfigPaths {
pub fn new() -> Self {
FISH_PATH.get_or_init(compute_fish_path);
let exec_path = get_fish_path();
flog!(
config,
@@ -167,11 +166,12 @@ pub enum FishPath {
LookUpInPath,
}
static FISH_PATH: OnceLock<FishPath> = OnceLock::new();
static FISH_PATH: SingleThreadedLazyCell<FishPath, fn() -> FishPath> =
SingleThreadedLazyCell::new(compute_fish_path);
/// Get the absolute path to the fish executable itself
pub fn get_fish_path() -> &'static FishPath {
FISH_PATH.get().unwrap()
&FISH_PATH
}
fn compute_fish_path() -> FishPath {

View File

@@ -30,6 +30,7 @@
wutil::{fish_wcstol, wgetcwd},
};
use fish_common::{UnescapeStringStyle, unescape_string};
use fish_thread::{SingleThreadedLazyCell, SingleThreadedOnceCell};
use fish_wcstringutil::join_strings;
use fish_widestring::{cstr2wcstring, osstr2wcstring, str2wcstring};
use libc::c_int;
@@ -41,7 +42,7 @@
collections::HashMap,
ffi::CStr,
path::PathBuf,
sync::{Arc, LazyLock, OnceLock},
sync::{Arc, LazyLock},
};
/// Set when a universal variable has been modified but not yet been written to disk via sync().
@@ -405,14 +406,14 @@ pub fn universal_sync(&self, always: bool, is_repainting: bool) -> Vec<Event> {
/// A variable stack that only represents globals.
/// Do not push or pop from this.
pub fn globals() -> &'static EnvStack {
use std::sync::OnceLock;
static GLOBALS: OnceLock<EnvStack> = OnceLock::new();
GLOBALS.get_or_init(|| EnvStack {
inner: EnvStackImpl::new(),
can_push_pop: false,
// Do not dispatch variable changes - this is used at startup when we are importing env vars.
dispatches_var_changes: false,
})
static GLOBALS: SingleThreadedLazyCell<EnvStack> =
SingleThreadedLazyCell::new(|| EnvStack {
inner: EnvStackImpl::new(),
can_push_pop: false,
// Do not dispatch variable changes - this is used at startup when we are importing env vars.
dispatches_var_changes: false,
});
&GLOBALS
}
pub fn set_argv(&self, argv: Vec<WString>, is_repainting: bool) {
@@ -562,7 +563,8 @@ fn setup_path(global_exported_mode: EnvSetMode) {
/// The originally inherited variables and their values.
/// This is a simple key->value map and not e.g. cut into paths.
pub static INHERITED_VARS: OnceLock<HashMap<WString, WString>> = OnceLock::new();
pub static INHERITED_VARS: SingleThreadedOnceCell<HashMap<WString, WString>> =
SingleThreadedOnceCell::new();
pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool) {
let vars = EnvStack::globals();

View File

@@ -13,6 +13,7 @@
use crate::reader::{commandline_get_state, reader_status_count};
use crate::threads::{is_forked_child, is_main_thread};
use crate::wutil::fish_wcstol_radix;
use fish_thread::SingleThreadedLazyCell;
use fish_widestring::wcs2zstring;
use nix::sys::stat::{Mode, umask};
use std::cell::{RefCell, UnsafeCell};
@@ -27,13 +28,10 @@
/// Getter for universal variables.
/// This is typically initialized in env_init(), and is considered empty before then.
pub fn uvars() -> MutexGuard<'static, EnvUniversal> {
use std::sync::OnceLock;
/// Universal variables instance.
static UVARS: OnceLock<Mutex<EnvUniversal>> = OnceLock::new();
UVARS
.get_or_init(|| Mutex::new(EnvUniversal::new()))
.lock()
.unwrap()
static UVARS: SingleThreadedLazyCell<Mutex<EnvUniversal>> =
SingleThreadedLazyCell::new(|| Mutex::new(EnvUniversal::new()));
UVARS.lock().unwrap()
}
/// Whether we were launched with no_config; in this case setting a uvar instead sets a global.

8
src/env/var.rs vendored
View File

@@ -1,6 +1,7 @@
use crate::signal::Signal;
use bitflags::bitflags;
use fish_common::assert_sorted_by_name;
use fish_thread::SingleThreadedLazyCell;
use fish_wcstringutil::join_strings;
use fish_widestring::{L, WString, wstr};
use libc::c_int;
@@ -130,13 +131,12 @@ pub struct EnvVar {
impl Default for EnvVar {
fn default() -> Self {
use std::sync::OnceLock;
/// A shared read-only empty list.
static EMPTY_LIST: OnceLock<Arc<[WString]>> = OnceLock::new();
let empty_list = EMPTY_LIST.get_or_init(|| Arc::new([]));
static EMPTY_LIST: SingleThreadedLazyCell<Arc<[WString]>> =
SingleThreadedLazyCell::new(|| Arc::new([]));
EnvVar {
values: Arc::clone(empty_list),
values: Arc::clone(&*EMPTY_LIST),
flags: EnvVarFlags::empty(),
}
}

View File

@@ -44,6 +44,7 @@
use crate::wutil::{fish_wcstol, perror_io};
use errno::{errno, set_errno};
use fish_common::{ScopeGuard, exit_without_destructors, truncate_at_nul, write_loop};
use fish_thread::SingleThreadedLazyCell;
use fish_widestring::{ToWString as _, bytes2wcstring, wcs2bytes, wcs2zstring};
use libc::{
EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, STDERR_FILENO,
@@ -62,7 +63,7 @@
os::fd::{AsRawFd as _, FromRawFd as _, OwnedFd, RawFd},
slice,
sync::{
Arc, OnceLock,
Arc,
atomic::{AtomicUsize, Ordering},
},
};
@@ -72,9 +73,10 @@
/// to their target fds.
/// TODO: this IO could be multiplexed using FdMonitor.
fn exec_thread_pool() -> &'static Arc<ThreadPool> {
static EXEC_THREAD_POOL: OnceLock<Arc<ThreadPool>> = OnceLock::new();
static EXEC_THREAD_POOL: SingleThreadedLazyCell<Arc<ThreadPool>> =
SingleThreadedLazyCell::new(|| ThreadPool::new(1, usize::MAX));
// Use an unbounded queue because otherwise we risk deadlock.
EXEC_THREAD_POOL.get_or_init(|| ThreadPool::new(1, usize::MAX))
&EXEC_THREAD_POOL
}
/// Execute the processes specified by `j` in the parser \p.

View File

@@ -1,3 +1,5 @@
use fish_thread::SingleThreadedLazyCell;
use crate::common::CancelChecker;
use crate::env::EnvDyn;
use crate::env::{EnvStack, Environment};
@@ -57,10 +59,9 @@ pub fn vars(&self) -> &dyn Environment {
// Return an "empty" context which contains no variables, no parser, and never cancels.
pub fn empty() -> OperationContext<'static> {
use std::sync::OnceLock;
static NULL_ENV: OnceLock<EnvStack> = OnceLock::new();
let null_env = NULL_ENV.get_or_init(EnvStack::new);
OperationContext::background(null_env, EXPANSION_LIMIT_DEFAULT)
static NULL_ENV: SingleThreadedLazyCell<EnvStack, fn() -> EnvStack> =
SingleThreadedLazyCell::new(EnvStack::new);
OperationContext::background(&*NULL_ENV, EXPANSION_LIMIT_DEFAULT)
}
// Return an operation context that contains only global variables, no parser, and never

View File

@@ -1,16 +1,15 @@
use crate::{common::get_program_name, nix::isatty, threads::is_main_thread};
use fish_common::read_blocked;
use fish_thread::SingleThreadedOnceCell;
use libc::STDIN_FILENO;
use std::{
panic::{UnwindSafe, set_hook, take_hook},
sync::{
OnceLock,
atomic::{AtomicBool, Ordering},
},
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
pub static AT_EXIT: OnceLock<Box<dyn Fn() + Send + Sync>> = OnceLock::new();
pub static AT_EXIT: SingleThreadedOnceCell<Box<dyn Fn() + Send + Sync>> =
SingleThreadedOnceCell::new();
pub fn panic_handler(main: impl FnOnce() -> i32 + UnwindSafe) -> ! {
// The isatty() check will stop us from hanging in most fish tests, but not those

View File

@@ -117,6 +117,7 @@
};
use fish_fallback::{fish_wcwidth, lowercase};
use fish_feature_flags::FeatureFlag;
use fish_thread::SingleThreadedOnceCell;
use fish_util::{perror, write_to_fd};
use fish_wcstringutil::{
CaseSensitivity, IsPrefix, StringFuzzyMatch, count_preceding_backslashes, is_prefix,
@@ -147,7 +148,7 @@
os::fd::{AsRawFd as _, BorrowedFd, FromRawFd as _, OwnedFd, RawFd},
pin::Pin,
sync::{
Arc, LazyLock, Mutex, MutexGuard, OnceLock,
Arc, LazyLock, Mutex, MutexGuard,
atomic::{AtomicI32, AtomicU8, AtomicU32, Ordering},
},
time::{Duration, Instant},
@@ -174,7 +175,8 @@ fn zeroed_termios() -> Termios {
pub static SHELL_MODES: LazyLock<Mutex<Termios>> = LazyLock::new(|| Mutex::new(zeroed_termios()));
/// The valid terminal modes on startup.
static TERMINAL_MODE_ON_STARTUP: OnceLock<libc::termios> = OnceLock::new();
static TERMINAL_MODE_ON_STARTUP: SingleThreadedOnceCell<libc::termios> =
SingleThreadedOnceCell::new();
/// Mode we use to execute programs.
static TTY_MODES_FOR_EXTERNAL_CMDS: LazyLock<Mutex<Termios>> =

View File

@@ -4,12 +4,12 @@
use crate::{
screen::{is_dumb, only_grayscale},
text_face::{ResettableStyle, TextFace, TextStyling, UnderlineStyle},
threads::MainThread,
};
use bitflags::bitflags;
use fish_color::{Color, Color24};
use fish_common::{EscapeStringStyle, escape_string, write_loop};
use fish_feature_flags::FeatureFlag;
use fish_thread::SingleThreaded;
use fish_widestring::{wcs2bytes, wcs2bytes_appending};
use std::{
cell::{RefCell, RefMut},
@@ -575,8 +575,8 @@ pub fn write_bytes(&mut self, buf: &[u8]) {
/// Access the outputter for stdout.
/// This should only be used from the main thread.
pub fn stdoutput() -> &'static RefCell<Outputter> {
static STDOUTPUT: MainThread<RefCell<Outputter>> =
MainThread::new(RefCell::new(Outputter::new_from_fd(libc::STDOUT_FILENO)));
static STDOUTPUT: SingleThreaded<RefCell<Outputter>> =
SingleThreaded::new(RefCell::new(Outputter::new_from_fd(libc::STDOUT_FILENO)));
STDOUTPUT.get()
}
}

View File

@@ -12,7 +12,6 @@
use std::collections::HashMap;
use std::env::set_current_dir;
use std::path::PathBuf;
use std::sync::OnceLock;
pub use serial_test::serial;

View File

@@ -1,14 +1,11 @@
//! Support for thread pools and thread management.
use crate::flog::{FloggableDebug, flog};
use fish_thread::{ThreadId, thread_id};
use nix::sys::signal::{SigSet, SigmaskHow, Signal};
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Duration;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ThreadId(usize);
impl FloggableDebug for ThreadId {}
impl FloggableDebug for std::thread::ThreadId {}
@@ -55,31 +52,6 @@ fn init_not_called() -> ! {
}
}
/// Get the calling thread's fish-specific thread id.
///
/// This thread id is internal to the `threads` module for low-level purposes and should not be
/// leaked to other modules; general purpose code that needs a thread id should use rust's native
/// thread id functionality.
///
/// We use our own implementation because Rust's own `Thread::id()` allocates via `Arc`, is fairly
/// slow, and uses a `Mutex` on 32-bit platforms (or anywhere without an atomic 64-bit CAS).
#[inline(always)]
fn thread_id() -> ThreadId {
static THREAD_COUNTER: AtomicUsize = AtomicUsize::new(1);
// It would be faster and much nicer to use #[thread_local] here, but that's nightly only.
// This is still faster than going through Thread::thread_id(); it's something like 15ns
// for each `Thread::thread_id()` call vs 1-2 ns with `#[thread_local]` and 2-4ns with
// `thread_local!`.
thread_local! {
static THREAD_ID: ThreadId = ThreadId(THREAD_COUNTER.fetch_add(1, Ordering::Relaxed));
}
let id = THREAD_ID.with(|id| *id);
// This assertion is only here to reduce hair loss in case someone runs into a known linker bug;
// as it's not here to catch logic errors in our own code, it can be elided in release mode.
debug_assert_ne!(id, ThreadId(0), "TLS storage not initialized!");
id
}
#[inline(always)]
pub fn is_main_thread() -> bool {
thread_id() == main_thread_id()
@@ -295,33 +267,6 @@ fn spawn_thread(self: &Arc<Self>) -> bool {
}
}
/// A `Sync` and `Send` wrapper for non-`Sync`/`Send` types.
/// Only allows access from the main thread.
pub struct MainThread<T> {
data: T,
// Make type !Send and !Sync by default
_marker: PhantomData<*const ()>,
}
// Manually implement Send and Sync for MainThread<T> to ensure it can be shared across threads
// as long as T is 'static.
unsafe impl<T: 'static> Send for MainThread<T> {}
unsafe impl<T: 'static> Sync for MainThread<T> {}
impl<T> MainThread<T> {
pub const fn new(value: T) -> Self {
Self {
data: value,
_marker: PhantomData,
}
}
pub fn get(&self) -> &T {
assert_is_main_thread();
&self.data
}
}
impl ThreadPool {
/// The worker loop entry point for this thread.
/// This is run in a background thread.

View File

@@ -18,25 +18,24 @@
use crate::threads::assert_is_main_thread;
use crate::wutil::{perror_nix, wcstoi};
use fish_common::write_loop;
use fish_thread::SingleThreadedOnceCell;
use fish_util::perror;
use libc::{EINVAL, ENOTTY, EPERM, STDIN_FILENO, WNOHANG};
use nix::sys::termios::tcgetattr;
use nix::unistd::getpgrp;
use std::os::fd::BorrowedFd;
use std::sync::{
OnceLock,
atomic::{AtomicPtr, Ordering},
};
use std::sync::atomic::{AtomicPtr, Ordering};
/// Whether kitty keyboard protocol support is present in the TTY.
static KITTY_KEYBOARD_SUPPORTED: OnceLock<bool> = OnceLock::new();
static KITTY_KEYBOARD_SUPPORTED: SingleThreadedOnceCell<bool> = SingleThreadedOnceCell::new();
/// Set that the TTY supports the kitty keyboard protocol.
pub fn maybe_set_kitty_keyboard_capability() {
KITTY_KEYBOARD_SUPPORTED.get_or_init(|| true);
}
pub(crate) static SCROLL_CONTENT_UP_SUPPORTED: OnceLock<bool> = OnceLock::new();
pub(crate) static SCROLL_CONTENT_UP_SUPPORTED: SingleThreadedOnceCell<bool> =
SingleThreadedOnceCell::new();
pub(crate) const SCROLL_CONTENT_UP_TERMINFO_CODE: &str = "indn";
// Get the support capability for kitty keyboard protocol.
@@ -51,10 +50,11 @@ pub fn maybe_set_scroll_content_up_capability() {
});
}
pub static TERMINAL_OS_NAME: OnceLock<Option<WString>> = OnceLock::new();
pub static TERMINAL_OS_NAME: SingleThreadedOnceCell<Option<WString>> =
SingleThreadedOnceCell::new();
pub(crate) const XTGETTCAP_QUERY_OS_NAME: &str = "query-os-name";
pub static XTVERSION: OnceLock<WString> = OnceLock::new();
pub static XTVERSION: SingleThreadedOnceCell<WString> = SingleThreadedOnceCell::new();
pub fn xtversion() -> Option<&'static wstr> {
XTVERSION.get().as_ref().map(|s| s.as_utfstr())

View File

@@ -1,4 +1,6 @@
use std::{os::fd::RawFd, sync::OnceLock};
use std::os::fd::RawFd;
use fish_thread::SingleThreadedLazyCell;
#[cfg(apple)]
mod notifyd;
@@ -67,9 +69,9 @@ pub fn create_notifier() -> Box<dyn UniversalNotifier> {
Box::new(NullNotifier)
}
// Default instance. Other instances are possible for testing.
static DEFAULT_NOTIFIER: OnceLock<Box<dyn UniversalNotifier>> = OnceLock::new();
pub fn default_notifier() -> &'static dyn UniversalNotifier {
DEFAULT_NOTIFIER.get_or_init(create_notifier).as_ref()
// Default instance. Other instances are possible for testing.
static DEFAULT_NOTIFIER: SingleThreadedLazyCell<Box<dyn UniversalNotifier>> =
SingleThreadedLazyCell::new(create_notifier);
&**DEFAULT_NOTIFIER
}