Simplify Parser and EnvStack singletons and clarify thread semantics

`Parser` is a single-threaded `!Send`, `!Sync` type and does not need to use
`Arc` for anything. We were using it because that's all we had for the parser's
`EnvStack`, but though that is *technically* protected internally by a mutex
(shared with global EnvStack), there's nothing to say that other parsers with a
narrower scope/lifetime on other threads will be necessarily using the same
backing mutex.

We can safely marshal the existing `Arc<EnvStack>` we get from
`environment::principal()` into an `Rc<EnvStack>` since the underlying reference
is always valid. To prove this point, we could have PRINCIPAL_STACK be a static
`EnvStack` and have `environment::principal()` use `Arc::from_raw()` to turn
that into an `Arc<EnvStack>`, but there's no need to factorize this process.
This commit is contained in:
Mahmoud Al-Qudsi 2024-05-16 17:40:23 -05:00
parent e4282f3798
commit 0f18480559
6 changed files with 36 additions and 45 deletions

View file

@ -990,11 +990,7 @@ fn throwing_main() -> i32 {
} }
} }
OutputType::Ansi => { OutputType::Ansi => {
colored_output = colorize( colored_output = colorize(&output_wtext, &colors, &**EnvStack::globals());
&output_wtext,
&colors,
EnvStack::globals().as_ref().get_ref(),
);
} }
OutputType::Html => { OutputType::Html => {
colored_output = html_colorize(&output_wtext, &colors); colored_output = html_colorize(&output_wtext, &colors);

View file

@ -27,7 +27,6 @@ use crate::wcstringutil::join_strings;
use crate::wutil::{fish_wcstol, wgetcwd, wgettext}; use crate::wutil::{fish_wcstol, wgetcwd, wgettext};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use lazy_static::lazy_static;
use libc::{c_int, uid_t, STDOUT_FILENO, _IONBF}; use libc::{c_int, uid_t, STDOUT_FILENO, _IONBF};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::collections::HashMap; use std::collections::HashMap;
@ -35,7 +34,6 @@ use std::ffi::CStr;
use std::io::Write; use std::io::Write;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::os::unix::prelude::*; use std::os::unix::prelude::*;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
/// Set when a universal variable has been modified but not yet been written to disk via sync(). /// Set when a universal variable has been modified but not yet been written to disk via sync().
@ -190,7 +188,7 @@ impl EnvStack {
/// Return whether we are the principal stack. /// Return whether we are the principal stack.
pub fn is_principal(&self) -> bool { pub fn is_principal(&self) -> bool {
std::ptr::eq(self, Self::principal().as_ref().get_ref()) std::ptr::eq(self, &**Self::principal())
} }
/// Helpers to get and set the proc statuses. /// Helpers to get and set the proc statuses.
@ -362,13 +360,17 @@ impl EnvStack {
/// A variable stack that only represents globals. /// A variable stack that only represents globals.
/// Do not push or pop from this. /// Do not push or pop from this.
pub fn globals() -> &'static EnvStackRef { pub fn globals() -> &'static Arc<EnvStack> {
&GLOBALS use std::sync::OnceLock;
static GLOBALS: OnceLock<Arc<EnvStack>> = OnceLock::new();
&GLOBALS.get_or_init(|| Arc::new(EnvStack::new()))
} }
/// Access the principal variable stack, associated with the principal parser. /// Access the principal variable stack, associated with the principal parser.
pub fn principal() -> &'static EnvStackRef { pub fn principal() -> &'static Arc<EnvStack> {
&PRINCIPAL_STACK use std::sync::OnceLock;
static PRINCIPAL_STACK: OnceLock<Arc<EnvStack>> = OnceLock::new();
&PRINCIPAL_STACK.get_or_init(|| Arc::new(EnvStack::new()))
} }
pub fn set_argv(&self, argv: Vec<WString>) { pub fn set_argv(&self, argv: Vec<WString>) {
@ -408,20 +410,6 @@ impl Environment for EnvStack {
} }
} }
// TODO Remove Pin?
pub type EnvStackRef = Pin<Arc<EnvStack>>;
// A variable stack that only represents globals.
// Do not push or pop from this.
lazy_static! {
static ref GLOBALS: EnvStackRef = Arc::pin(EnvStack::new());
}
// Our singleton "principal" stack.
lazy_static! {
static ref PRINCIPAL_STACK: EnvStackRef = Arc::pin(EnvStack::new());
}
/// Some configuration path environment variables. /// Some configuration path environment variables.
const FISH_DATADIR_VAR: &wstr = L!("__fish_data_dir"); const FISH_DATADIR_VAR: &wstr = L!("__fish_data_dir");
const FISH_SYSCONFDIR_VAR: &wstr = L!("__fish_sysconf_dir"); const FISH_SYSCONFDIR_VAR: &wstr = L!("__fish_sysconf_dir");
@ -539,7 +527,7 @@ fn setup_user(vars: &EnvStack) {
fn setup_path() { fn setup_path() {
use crate::libc::{confstr, _CS_PATH}; use crate::libc::{confstr, _CS_PATH};
let vars = &GLOBALS; let vars = EnvStack::globals();
let path = vars.get_unless_empty(L!("PATH")); let path = vars.get_unless_empty(L!("PATH"));
if path.is_none() { if path.is_none() {
// _CS_PATH: colon-separated paths to find POSIX utilities // _CS_PATH: colon-separated paths to find POSIX utilities
@ -566,7 +554,7 @@ fn setup_path() {
pub static INHERITED_VARS: OnceCell<HashMap<WString, WString>> = OnceCell::new(); pub static INHERITED_VARS: OnceCell<HashMap<WString, WString>> = OnceCell::new();
pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool) { pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool) {
let vars = &PRINCIPAL_STACK; let vars = &**EnvStack::principal();
let env_iter: Vec<_> = std::env::vars_os() let env_iter: Vec<_> = std::env::vars_os()
.map(|(k, v)| (str2wcstring(k.as_bytes()), str2wcstring(v.as_bytes()))) .map(|(k, v)| (str2wcstring(k.as_bytes()), str2wcstring(v.as_bytes())))
@ -725,7 +713,7 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
// Initialize termsize variables. // Initialize termsize variables.
// PORTING: 3x deref is weird // PORTING: 3x deref is weird
let termsize = termsize::SHARED_CONTAINER.initialize(&***vars as &dyn Environment); let termsize = termsize::SHARED_CONTAINER.initialize(vars as &dyn Environment);
if vars.get_unless_empty(L!("COLUMNS")).is_none() { if vars.get_unless_empty(L!("COLUMNS")).is_none() {
vars.set_one(L!("COLUMNS"), EnvMode::GLOBAL, termsize.width.to_wstring()); vars.set_one(L!("COLUMNS"), EnvMode::GLOBAL, termsize.width.to_wstring());
} }

View file

@ -122,7 +122,7 @@ pub fn load(name: &wstr, parser: &Parser) -> bool {
if funcset.allow_autoload(name) { if funcset.allow_autoload(name) {
if let Some(path) = funcset if let Some(path) = funcset
.autoloader .autoloader
.resolve_command(name, EnvStack::globals().as_ref().get_ref()) .resolve_command(name, &**EnvStack::globals())
{ {
path_to_autoload = Some(path); path_to_autoload = Some(path);
} }

View file

@ -1,10 +1,8 @@
use crate::common::CancelChecker; use crate::common::CancelChecker;
use crate::env::EnvDyn; use crate::env::EnvDyn;
use crate::env::{EnvStack, EnvStackRef, Environment}; use crate::env::{EnvStack, Environment};
use crate::parser::{Parser, ParserRef}; use crate::parser::{Parser, ParserRef};
use crate::proc::JobGroupRef; use crate::proc::JobGroupRef;
use once_cell::sync::Lazy;
use std::sync::Arc;
use crate::reader::read_generation_count; use crate::reader::read_generation_count;
@ -47,8 +45,6 @@ pub struct OperationContext<'a> {
pub cancel_checker: CancelChecker, pub cancel_checker: CancelChecker,
} }
static nullenv: Lazy<EnvStackRef> = Lazy::new(|| Arc::pin(EnvStack::new()));
impl<'a> OperationContext<'a> { impl<'a> OperationContext<'a> {
pub fn vars(&self) -> &dyn Environment { pub fn vars(&self) -> &dyn Environment {
match &self.vars { match &self.vars {
@ -60,7 +56,10 @@ impl<'a> OperationContext<'a> {
// Return an "empty" context which contains no variables, no parser, and never cancels. // Return an "empty" context which contains no variables, no parser, and never cancels.
pub fn empty() -> OperationContext<'static> { pub fn empty() -> OperationContext<'static> {
OperationContext::background(&**nullenv, EXPANSION_LIMIT_DEFAULT) 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)
} }
// Return an operation context that contains only global variables, no parser, and never // Return an operation context that contains only global variables, no parser, and never

View file

@ -7,7 +7,7 @@ use crate::common::{
FilenameRef, ScopeGuarding, PROFILING_ACTIVE, FilenameRef, ScopeGuarding, PROFILING_ACTIVE,
}; };
use crate::complete::CompletionList; use crate::complete::CompletionList;
use crate::env::{EnvMode, EnvStack, EnvStackRef, EnvStackSetResult, Environment, Statuses}; use crate::env::{EnvMode, EnvStack, EnvStackSetResult, Environment, Statuses};
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::expand::{ use crate::expand::{
expand_string, replace_home_directory_with_tilde, ExpandFlags, ExpandResultCode, expand_string, replace_home_directory_with_tilde, ExpandFlags, ExpandResultCode,
@ -37,7 +37,6 @@ use std::cell::{Ref, RefCell, RefMut};
use std::ffi::{CStr, OsStr}; use std::ffi::{CStr, OsStr};
use std::os::fd::{AsRawFd, OwnedFd, RawFd}; use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use std::os::unix::prelude::OsStrExt; use std::os::unix::prelude::OsStrExt;
use std::pin::Pin;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::sync::{ use std::sync::{
atomic::{AtomicIsize, AtomicU64, Ordering}, atomic::{AtomicIsize, AtomicU64, Ordering},
@ -312,7 +311,7 @@ pub struct Parser {
pub eval_level: AtomicIsize, pub eval_level: AtomicIsize,
/// Set of variables for the parser. /// Set of variables for the parser.
pub variables: EnvStackRef, pub variables: Rc<EnvStack>,
/// Miscellaneous library data. /// Miscellaneous library data.
library_data: RefCell<LibraryData>, library_data: RefCell<LibraryData>,
@ -333,7 +332,7 @@ pub struct Parser {
impl Parser { impl Parser {
/// Create a parser /// Create a parser
pub fn new(variables: EnvStackRef, is_principal: bool) -> ParserRef { pub fn new(variables: Rc<EnvStack>, is_principal: bool) -> ParserRef {
let result = Rc::new_cyclic(|this: &Weak<Self>| Self { let result = Rc::new_cyclic(|this: &Weak<Self>| Self {
this: Weak::clone(this), this: Weak::clone(this),
execution_context: RefCell::default(), execution_context: RefCell::default(),
@ -405,7 +404,16 @@ impl Parser {
static PRINCIPAL: MainThread<OnceCell<ParserRef>> = MainThread::new(OnceCell::new()); static PRINCIPAL: MainThread<OnceCell<ParserRef>> = MainThread::new(OnceCell::new());
&PRINCIPAL &PRINCIPAL
.get() .get()
.get_or_init(|| Parser::new(EnvStack::principal().clone(), true)) // The parser is !Send/!Sync and strictly single-threaded, but we can have
// multi-threaded access to its variables stack (why, though?) so EnvStack::principal()
// returns an Arc<EnvStack> instead of an Rc<EnvStack>. Since the Arc<EnvStack> is
// statically allocated and always valid (not even destroyed on exit), we can safely
// transform the Arc<T> into an Rc<T> and save Parser from needing atomic ref counting
// to manage its further references.
.get_or_init(|| {
let env_rc = unsafe { Rc::from_raw(&**EnvStack::principal() as *const _) };
Parser::new(env_rc, true)
})
} }
/// Assert that this parser is allowed to execute on the current thread. /// Assert that this parser is allowed to execute on the current thread.
@ -753,8 +761,8 @@ impl Parser {
} }
/// Get the variables as an Arc. /// Get the variables as an Arc.
pub fn vars_ref(&self) -> Arc<EnvStack> { pub fn vars_ref(&self) -> Rc<EnvStack> {
Pin::into_inner(Pin::clone(&self.variables)) Rc::clone(&self.variables)
} }
/// Get the library data. /// Get the library data.

View file

@ -4,14 +4,14 @@ use crate::key::Key;
use crate::parser::Parser; use crate::parser::Parser;
use crate::tests::prelude::*; use crate::tests::prelude::*;
use crate::wchar::prelude::*; use crate::wchar::prelude::*;
use std::sync::Arc; use std::rc::Rc;
#[test] #[test]
#[serial] #[serial]
fn test_input() { fn test_input() {
let _cleanup = test_init(); let _cleanup = test_init();
use crate::env::EnvStack; use crate::env::EnvStack;
let parser = Parser::new(Arc::pin(EnvStack::new()), false); let parser = Parser::new(Rc::new(EnvStack::new()), false);
let mut input = Inputter::new(parser, libc::STDIN_FILENO); let mut input = Inputter::new(parser, libc::STDIN_FILENO);
// Ensure sequences are order independent. Here we add two bindings where the first is a prefix // Ensure sequences are order independent. Here we add two bindings where the first is a prefix
// of the second, and then emit the second key list. The second binding should be invoked, not // of the second, and then emit the second key list. The second binding should be invoked, not