mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-25 11:14:46 +00:00
Make GlobalContext Sync (#15967)
### What does this PR try to resolve? By making `GlobalContext` `Sync`, it will become much easier to parallelize parts of cargo. A concrete example is #15676. It was my understanding from https://github.com/rust-lang/cargo/issues/15934#issuecomment-3271203650 that this would be a welcome change by the cargo team. #### Overview In `GlobalContext` and structs used in its fields - `RefCell`-s were replaced by either an `std::sync::Mutex` or an `std::sync::RwLock`, depending on API needs - `LazyCell`-s were replaced by a new `OnceLock` implementation backed by `std::sync::OnceLock`, emulating unstable features needed by cargo - Removed `LazyCell`/`OnceLock` from fields where the initialization function is just a `Mutex<HashMap>::default()` - added `util::context::tests::sync_context` test that does not compile if `GlobalContext` is not `Sync` ### How to test and review this PR? This PR should add no user-facing changes. Tests must pass and benchmarks must report no changes.
This commit is contained in:
commit
c86bc374d5
@ -800,11 +800,9 @@ impl<'gctx> Registry for PackageRegistry<'gctx> {
|
|||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn block_until_ready(&mut self) -> CargoResult<()> {
|
fn block_until_ready(&mut self) -> CargoResult<()> {
|
||||||
if cfg!(debug_assertions) {
|
// Ensure `shell` is not already in use,
|
||||||
// Force borrow to catch invalid borrows, regardless of which source is used and how it
|
// regardless of which source is used and how it happens to behave this time
|
||||||
// happens to behave this time
|
self.gctx.debug_assert_shell_not_borrowed();
|
||||||
self.gctx.shell().verbosity();
|
|
||||||
}
|
|
||||||
for (source_id, source) in self.sources.sources_mut() {
|
for (source_id, source) in self.sources.sources_mut() {
|
||||||
source
|
source
|
||||||
.block_until_ready()
|
.block_until_ready()
|
||||||
|
@ -65,7 +65,7 @@ impl Shell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a shell from a plain writable object, with no color, and max verbosity.
|
/// Creates a shell from a plain writable object, with no color, and max verbosity.
|
||||||
pub fn from_write(out: Box<dyn Write>) -> Shell {
|
pub fn from_write(out: Box<dyn Write + Send + Sync>) -> Shell {
|
||||||
Shell {
|
Shell {
|
||||||
output: ShellOut::Write(AutoStream::never(out)), // strip all formatting on write
|
output: ShellOut::Write(AutoStream::never(out)), // strip all formatting on write
|
||||||
verbosity: Verbosity::Verbose,
|
verbosity: Verbosity::Verbose,
|
||||||
@ -432,7 +432,7 @@ impl Default for Shell {
|
|||||||
/// A `Write`able object, either with or without color support
|
/// A `Write`able object, either with or without color support
|
||||||
enum ShellOut {
|
enum ShellOut {
|
||||||
/// A plain write object without color support
|
/// A plain write object without color support
|
||||||
Write(AutoStream<Box<dyn Write>>),
|
Write(AutoStream<Box<dyn Write + Send + Sync>>),
|
||||||
/// Color-enabled stdio, with information on whether color should be used
|
/// Color-enabled stdio, with information on whether color should be used
|
||||||
Stream {
|
Stream {
|
||||||
stdout: AutoStream<std::io::Stdout>,
|
stdout: AutoStream<std::io::Stdout>,
|
||||||
|
@ -2037,7 +2037,7 @@ fn find_workspace_root_with_loader(
|
|||||||
) -> CargoResult<Option<PathBuf>> {
|
) -> CargoResult<Option<PathBuf>> {
|
||||||
// Check if there are any workspace roots that have already been found that would work
|
// Check if there are any workspace roots that have already been found that would work
|
||||||
{
|
{
|
||||||
let roots = gctx.ws_roots.borrow();
|
let roots = gctx.ws_roots();
|
||||||
// Iterate through the manifests parent directories until we find a workspace
|
// Iterate through the manifests parent directories until we find a workspace
|
||||||
// root. Note we skip the first item since that is just the path itself
|
// root. Note we skip the first item since that is just the path itself
|
||||||
for current in manifest_path.ancestors().skip(1) {
|
for current in manifest_path.ancestors().skip(1) {
|
||||||
|
@ -1528,7 +1528,7 @@ fn github_fast_path(
|
|||||||
"https://api.github.com/repos/{}/{}/commits/{}",
|
"https://api.github.com/repos/{}/{}/commits/{}",
|
||||||
username, repository, github_branch_name,
|
username, repository, github_branch_name,
|
||||||
);
|
);
|
||||||
let mut handle = gctx.http()?.borrow_mut();
|
let mut handle = gctx.http()?.lock().unwrap();
|
||||||
debug!("attempting GitHub fast path for {}", url);
|
debug!("attempting GitHub fast path for {}", url);
|
||||||
handle.get(true)?;
|
handle.get(true)?;
|
||||||
handle.url(&url)?;
|
handle.url(&url)?;
|
||||||
|
@ -91,8 +91,8 @@ use super::FileLock;
|
|||||||
use crate::CargoResult;
|
use crate::CargoResult;
|
||||||
use crate::GlobalContext;
|
use crate::GlobalContext;
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
/// The style of lock to acquire.
|
/// The style of lock to acquire.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
@ -435,7 +435,11 @@ pub struct CacheLock<'lock> {
|
|||||||
impl Drop for CacheLock<'_> {
|
impl Drop for CacheLock<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
use CacheLockMode::*;
|
use CacheLockMode::*;
|
||||||
let mut state = self.locker.state.borrow_mut();
|
let mut state = match self.locker.state.lock() {
|
||||||
|
Ok(result) => result,
|
||||||
|
// we should release the cache even if a thread panicked while holding a lock
|
||||||
|
Err(poison) => poison.into_inner(),
|
||||||
|
};
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Shared => {
|
Shared => {
|
||||||
state.mutate_lock.decrement();
|
state.mutate_lock.decrement();
|
||||||
@ -472,24 +476,25 @@ pub struct CacheLocker {
|
|||||||
///
|
///
|
||||||
/// [`CacheLocker`] uses interior mutability because it is stuffed inside
|
/// [`CacheLocker`] uses interior mutability because it is stuffed inside
|
||||||
/// [`GlobalContext`], which does not allow mutation.
|
/// [`GlobalContext`], which does not allow mutation.
|
||||||
state: RefCell<CacheState>,
|
state: Mutex<CacheState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CacheLocker {
|
impl CacheLocker {
|
||||||
/// Creates a new `CacheLocker`.
|
/// Creates a new `CacheLocker`.
|
||||||
pub fn new() -> CacheLocker {
|
pub fn new() -> CacheLocker {
|
||||||
CacheLocker {
|
CacheLocker {
|
||||||
state: RefCell::new(CacheState {
|
state: CacheState {
|
||||||
cache_lock: RecursiveLock::new(CACHE_LOCK_NAME),
|
cache_lock: RecursiveLock::new(CACHE_LOCK_NAME),
|
||||||
mutate_lock: RecursiveLock::new(MUTATE_NAME),
|
mutate_lock: RecursiveLock::new(MUTATE_NAME),
|
||||||
}),
|
}
|
||||||
|
.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acquires a lock with the given mode, possibly blocking if another
|
/// Acquires a lock with the given mode, possibly blocking if another
|
||||||
/// cargo is holding the lock.
|
/// cargo is holding the lock.
|
||||||
pub fn lock(&self, gctx: &GlobalContext, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
|
pub fn lock(&self, gctx: &GlobalContext, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
|
||||||
let mut state = self.state.borrow_mut();
|
let mut state = self.state.lock().unwrap();
|
||||||
let _ = state.lock(gctx, mode, Blocking)?;
|
let _ = state.lock(gctx, mode, Blocking)?;
|
||||||
Ok(CacheLock { mode, locker: self })
|
Ok(CacheLock { mode, locker: self })
|
||||||
}
|
}
|
||||||
@ -501,7 +506,7 @@ impl CacheLocker {
|
|||||||
gctx: &GlobalContext,
|
gctx: &GlobalContext,
|
||||||
mode: CacheLockMode,
|
mode: CacheLockMode,
|
||||||
) -> CargoResult<Option<CacheLock<'_>>> {
|
) -> CargoResult<Option<CacheLock<'_>>> {
|
||||||
let mut state = self.state.borrow_mut();
|
let mut state = self.state.lock().unwrap();
|
||||||
if state.lock(gctx, mode, NonBlocking)? == LockAcquired {
|
if state.lock(gctx, mode, NonBlocking)? == LockAcquired {
|
||||||
Ok(Some(CacheLock { mode, locker: self }))
|
Ok(Some(CacheLock { mode, locker: self }))
|
||||||
} else {
|
} else {
|
||||||
@ -519,7 +524,7 @@ impl CacheLocker {
|
|||||||
/// `DownloadExclusive` will return true if a `MutateExclusive` lock is
|
/// `DownloadExclusive` will return true if a `MutateExclusive` lock is
|
||||||
/// held since they overlap.
|
/// held since they overlap.
|
||||||
pub fn is_locked(&self, mode: CacheLockMode) -> bool {
|
pub fn is_locked(&self, mode: CacheLockMode) -> bool {
|
||||||
let state = self.state.borrow();
|
let state = self.state.lock().unwrap();
|
||||||
match (
|
match (
|
||||||
mode,
|
mode,
|
||||||
state.cache_lock.count,
|
state.cache_lock.count,
|
||||||
|
@ -51,7 +51,6 @@
|
|||||||
|
|
||||||
use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
|
use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::{RefCell, RefMut};
|
|
||||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::env;
|
use std::env;
|
||||||
@ -63,7 +62,7 @@ use std::io::prelude::*;
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Once};
|
use std::sync::{Arc, Mutex, MutexGuard, Once, OnceLock};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use self::ConfigValue as CV;
|
use self::ConfigValue as CV;
|
||||||
@ -74,6 +73,7 @@ use crate::core::{CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig,
|
|||||||
use crate::ops::RegistryCredentialConfig;
|
use crate::ops::RegistryCredentialConfig;
|
||||||
use crate::sources::CRATES_IO_INDEX;
|
use crate::sources::CRATES_IO_INDEX;
|
||||||
use crate::sources::CRATES_IO_REGISTRY;
|
use crate::sources::CRATES_IO_REGISTRY;
|
||||||
|
use crate::util::OnceExt as _;
|
||||||
use crate::util::errors::CargoResult;
|
use crate::util::errors::CargoResult;
|
||||||
use crate::util::network::http::configure_http_handle;
|
use crate::util::network::http::configure_http_handle;
|
||||||
use crate::util::network::http::http_handle;
|
use crate::util::network::http::http_handle;
|
||||||
@ -85,7 +85,6 @@ use cargo_util::paths;
|
|||||||
use cargo_util_schemas::manifest::RegistryName;
|
use cargo_util_schemas::manifest::RegistryName;
|
||||||
use curl::easy::Easy;
|
use curl::easy::Easy;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lazycell::LazyCell;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::de::IntoDeserializer as _;
|
use serde::de::IntoDeserializer as _;
|
||||||
use serde_untagged::UntaggedEnumVisitor;
|
use serde_untagged::UntaggedEnumVisitor;
|
||||||
@ -166,11 +165,11 @@ pub struct GlobalContext {
|
|||||||
/// The location of the user's Cargo home directory. OS-dependent.
|
/// The location of the user's Cargo home directory. OS-dependent.
|
||||||
home_path: Filesystem,
|
home_path: Filesystem,
|
||||||
/// Information about how to write messages to the shell
|
/// Information about how to write messages to the shell
|
||||||
shell: RefCell<Shell>,
|
shell: Mutex<Shell>,
|
||||||
/// A collection of configuration options
|
/// A collection of configuration options
|
||||||
values: LazyCell<HashMap<String, ConfigValue>>,
|
values: OnceLock<HashMap<String, ConfigValue>>,
|
||||||
/// A collection of configuration options from the credentials file
|
/// A collection of configuration options from the credentials file
|
||||||
credential_values: LazyCell<HashMap<String, ConfigValue>>,
|
credential_values: OnceLock<HashMap<String, ConfigValue>>,
|
||||||
/// CLI config values, passed in via `configure`.
|
/// CLI config values, passed in via `configure`.
|
||||||
cli_config: Option<Vec<String>>,
|
cli_config: Option<Vec<String>>,
|
||||||
/// The current working directory of cargo
|
/// The current working directory of cargo
|
||||||
@ -178,9 +177,9 @@ pub struct GlobalContext {
|
|||||||
/// Directory where config file searching should stop (inclusive).
|
/// Directory where config file searching should stop (inclusive).
|
||||||
search_stop_path: Option<PathBuf>,
|
search_stop_path: Option<PathBuf>,
|
||||||
/// The location of the cargo executable (path to current process)
|
/// The location of the cargo executable (path to current process)
|
||||||
cargo_exe: LazyCell<PathBuf>,
|
cargo_exe: OnceLock<PathBuf>,
|
||||||
/// The location of the rustdoc executable
|
/// The location of the rustdoc executable
|
||||||
rustdoc: LazyCell<PathBuf>,
|
rustdoc: OnceLock<PathBuf>,
|
||||||
/// Whether we are printing extra verbose messages
|
/// Whether we are printing extra verbose messages
|
||||||
extra_verbose: bool,
|
extra_verbose: bool,
|
||||||
/// `frozen` is the same as `locked`, but additionally will not access the
|
/// `frozen` is the same as `locked`, but additionally will not access the
|
||||||
@ -199,9 +198,9 @@ pub struct GlobalContext {
|
|||||||
/// Cli flags of the form "-Z something"
|
/// Cli flags of the form "-Z something"
|
||||||
unstable_flags_cli: Option<Vec<String>>,
|
unstable_flags_cli: Option<Vec<String>>,
|
||||||
/// A handle on curl easy mode for http calls
|
/// A handle on curl easy mode for http calls
|
||||||
easy: LazyCell<RefCell<Easy>>,
|
easy: OnceLock<Mutex<Easy>>,
|
||||||
/// Cache of the `SourceId` for crates.io
|
/// Cache of the `SourceId` for crates.io
|
||||||
crates_io_source_id: LazyCell<SourceId>,
|
crates_io_source_id: OnceLock<SourceId>,
|
||||||
/// If false, don't cache `rustc --version --verbose` invocations
|
/// If false, don't cache `rustc --version --verbose` invocations
|
||||||
cache_rustc_info: bool,
|
cache_rustc_info: bool,
|
||||||
/// Creation time of this config, used to output the total build time
|
/// Creation time of this config, used to output the total build time
|
||||||
@ -211,23 +210,23 @@ pub struct GlobalContext {
|
|||||||
/// Environment variable snapshot.
|
/// Environment variable snapshot.
|
||||||
env: Env,
|
env: Env,
|
||||||
/// Tracks which sources have been updated to avoid multiple updates.
|
/// Tracks which sources have been updated to avoid multiple updates.
|
||||||
updated_sources: LazyCell<RefCell<HashSet<SourceId>>>,
|
updated_sources: Mutex<HashSet<SourceId>>,
|
||||||
/// Cache of credentials from configuration or credential providers.
|
/// Cache of credentials from configuration or credential providers.
|
||||||
/// Maps from url to credential value.
|
/// Maps from url to credential value.
|
||||||
credential_cache: LazyCell<RefCell<HashMap<CanonicalUrl, CredentialCacheValue>>>,
|
credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
|
||||||
/// Cache of registry config from the `[registries]` table.
|
/// Cache of registry config from the `[registries]` table.
|
||||||
registry_config: LazyCell<RefCell<HashMap<SourceId, Option<RegistryConfig>>>>,
|
registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
|
||||||
/// Locks on the package and index caches.
|
/// Locks on the package and index caches.
|
||||||
package_cache_lock: CacheLocker,
|
package_cache_lock: CacheLocker,
|
||||||
/// Cached configuration parsed by Cargo
|
/// Cached configuration parsed by Cargo
|
||||||
http_config: LazyCell<CargoHttpConfig>,
|
http_config: OnceLock<CargoHttpConfig>,
|
||||||
future_incompat_config: LazyCell<CargoFutureIncompatConfig>,
|
future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
|
||||||
net_config: LazyCell<CargoNetConfig>,
|
net_config: OnceLock<CargoNetConfig>,
|
||||||
build_config: LazyCell<CargoBuildConfig>,
|
build_config: OnceLock<CargoBuildConfig>,
|
||||||
target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
|
target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
|
||||||
doc_extern_map: LazyCell<RustdocExternMap>,
|
doc_extern_map: OnceLock<RustdocExternMap>,
|
||||||
progress_config: ProgressConfig,
|
progress_config: ProgressConfig,
|
||||||
env_config: LazyCell<Arc<HashMap<String, OsString>>>,
|
env_config: OnceLock<Arc<HashMap<String, OsString>>>,
|
||||||
/// This should be false if:
|
/// This should be false if:
|
||||||
/// - this is an artifact of the rustc distribution process for "stable" or for "beta"
|
/// - this is an artifact of the rustc distribution process for "stable" or for "beta"
|
||||||
/// - this is an `#[test]` that does not opt in with `enable_nightly_features`
|
/// - this is an `#[test]` that does not opt in with `enable_nightly_features`
|
||||||
@ -245,12 +244,12 @@ pub struct GlobalContext {
|
|||||||
/// consider using `ConfigBuilder::enable_nightly_features` instead.
|
/// consider using `ConfigBuilder::enable_nightly_features` instead.
|
||||||
pub nightly_features_allowed: bool,
|
pub nightly_features_allowed: bool,
|
||||||
/// `WorkspaceRootConfigs` that have been found
|
/// `WorkspaceRootConfigs` that have been found
|
||||||
pub ws_roots: RefCell<HashMap<PathBuf, WorkspaceRootConfig>>,
|
ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
|
||||||
/// The global cache tracker is a database used to track disk cache usage.
|
/// The global cache tracker is a database used to track disk cache usage.
|
||||||
global_cache_tracker: LazyCell<RefCell<GlobalCacheTracker>>,
|
global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
|
||||||
/// A cache of modifications to make to [`GlobalContext::global_cache_tracker`],
|
/// A cache of modifications to make to [`GlobalContext::global_cache_tracker`],
|
||||||
/// saved to disk in a batch to improve performance.
|
/// saved to disk in a batch to improve performance.
|
||||||
deferred_global_last_use: LazyCell<RefCell<DeferredGlobalLastUse>>,
|
deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalContext {
|
impl GlobalContext {
|
||||||
@ -283,14 +282,14 @@ impl GlobalContext {
|
|||||||
|
|
||||||
GlobalContext {
|
GlobalContext {
|
||||||
home_path: Filesystem::new(homedir),
|
home_path: Filesystem::new(homedir),
|
||||||
shell: RefCell::new(shell),
|
shell: Mutex::new(shell),
|
||||||
cwd,
|
cwd,
|
||||||
search_stop_path: None,
|
search_stop_path: None,
|
||||||
values: LazyCell::new(),
|
values: Default::default(),
|
||||||
credential_values: LazyCell::new(),
|
credential_values: Default::default(),
|
||||||
cli_config: None,
|
cli_config: None,
|
||||||
cargo_exe: LazyCell::new(),
|
cargo_exe: Default::default(),
|
||||||
rustdoc: LazyCell::new(),
|
rustdoc: Default::default(),
|
||||||
extra_verbose: false,
|
extra_verbose: false,
|
||||||
frozen: false,
|
frozen: false,
|
||||||
locked: false,
|
locked: false,
|
||||||
@ -304,28 +303,28 @@ impl GlobalContext {
|
|||||||
},
|
},
|
||||||
unstable_flags: CliUnstable::default(),
|
unstable_flags: CliUnstable::default(),
|
||||||
unstable_flags_cli: None,
|
unstable_flags_cli: None,
|
||||||
easy: LazyCell::new(),
|
easy: Default::default(),
|
||||||
crates_io_source_id: LazyCell::new(),
|
crates_io_source_id: Default::default(),
|
||||||
cache_rustc_info,
|
cache_rustc_info,
|
||||||
creation_time: Instant::now(),
|
creation_time: Instant::now(),
|
||||||
target_dir: None,
|
target_dir: None,
|
||||||
env,
|
env,
|
||||||
updated_sources: LazyCell::new(),
|
updated_sources: Default::default(),
|
||||||
credential_cache: LazyCell::new(),
|
credential_cache: Default::default(),
|
||||||
registry_config: LazyCell::new(),
|
registry_config: Default::default(),
|
||||||
package_cache_lock: CacheLocker::new(),
|
package_cache_lock: CacheLocker::new(),
|
||||||
http_config: LazyCell::new(),
|
http_config: Default::default(),
|
||||||
future_incompat_config: LazyCell::new(),
|
future_incompat_config: Default::default(),
|
||||||
net_config: LazyCell::new(),
|
net_config: Default::default(),
|
||||||
build_config: LazyCell::new(),
|
build_config: Default::default(),
|
||||||
target_cfgs: LazyCell::new(),
|
target_cfgs: Default::default(),
|
||||||
doc_extern_map: LazyCell::new(),
|
doc_extern_map: Default::default(),
|
||||||
progress_config: ProgressConfig::default(),
|
progress_config: ProgressConfig::default(),
|
||||||
env_config: LazyCell::new(),
|
env_config: Default::default(),
|
||||||
nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
|
nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
|
||||||
ws_roots: RefCell::new(HashMap::new()),
|
ws_roots: Default::default(),
|
||||||
global_cache_tracker: LazyCell::new(),
|
global_cache_tracker: Default::default(),
|
||||||
deferred_global_last_use: LazyCell::new(),
|
deferred_global_last_use: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,8 +407,22 @@ impl GlobalContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a reference to the shell, e.g., for writing error messages.
|
/// Gets a reference to the shell, e.g., for writing error messages.
|
||||||
pub fn shell(&self) -> RefMut<'_, Shell> {
|
pub fn shell(&self) -> MutexGuard<'_, Shell> {
|
||||||
self.shell.borrow_mut()
|
self.shell.lock().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert [`Self::shell`] is not in use
|
||||||
|
///
|
||||||
|
/// Testing might not identify bugs with two accesses to `shell` at once
|
||||||
|
/// due to conditional logic,
|
||||||
|
/// so place this outside of the conditions to catch these bugs in more situations.
|
||||||
|
pub fn debug_assert_shell_not_borrowed(&self) {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
match self.shell.try_lock() {
|
||||||
|
Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
|
||||||
|
Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the path to the `rustdoc` executable.
|
/// Gets the path to the `rustdoc` executable.
|
||||||
@ -513,24 +526,20 @@ impl GlobalContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Which package sources have been updated, used to ensure it is only done once.
|
/// Which package sources have been updated, used to ensure it is only done once.
|
||||||
pub fn updated_sources(&self) -> RefMut<'_, HashSet<SourceId>> {
|
pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
|
||||||
self.updated_sources
|
self.updated_sources.lock().unwrap()
|
||||||
.borrow_with(|| RefCell::new(HashSet::new()))
|
|
||||||
.borrow_mut()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cached credentials from credential providers or configuration.
|
/// Cached credentials from credential providers or configuration.
|
||||||
pub fn credential_cache(&self) -> RefMut<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
|
pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
|
||||||
self.credential_cache
|
self.credential_cache.lock().unwrap()
|
||||||
.borrow_with(|| RefCell::new(HashMap::new()))
|
|
||||||
.borrow_mut()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cache of already parsed registries from the `[registries]` table.
|
/// Cache of already parsed registries from the `[registries]` table.
|
||||||
pub(crate) fn registry_config(&self) -> RefMut<'_, HashMap<SourceId, Option<RegistryConfig>>> {
|
pub(crate) fn registry_config(
|
||||||
self.registry_config
|
&self,
|
||||||
.borrow_with(|| RefCell::new(HashMap::new()))
|
) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
|
||||||
.borrow_mut()
|
self.registry_config.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets all config values from disk.
|
/// Gets all config values from disk.
|
||||||
@ -550,18 +559,15 @@ impl GlobalContext {
|
|||||||
/// using this if possible.
|
/// using this if possible.
|
||||||
pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
|
pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
|
||||||
let _ = self.values()?;
|
let _ = self.values()?;
|
||||||
Ok(self
|
Ok(self.values.get_mut().expect("already loaded config values"))
|
||||||
.values
|
|
||||||
.borrow_mut()
|
|
||||||
.expect("already loaded config values"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: this is used by RLS, not Cargo.
|
// Note: this is used by RLS, not Cargo.
|
||||||
pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
|
pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
|
||||||
if self.values.borrow().is_some() {
|
if self.values.get().is_some() {
|
||||||
bail!("config values already found")
|
bail!("config values already found")
|
||||||
}
|
}
|
||||||
match self.values.fill(values) {
|
match self.values.set(values.into()) {
|
||||||
Ok(()) => Ok(()),
|
Ok(()) => Ok(()),
|
||||||
Err(_) => bail!("could not fill values"),
|
Err(_) => bail!("could not fill values"),
|
||||||
}
|
}
|
||||||
@ -730,13 +736,13 @@ impl GlobalContext {
|
|||||||
/// This does NOT look at environment variables. See `get_cv_with_env` for
|
/// This does NOT look at environment variables. See `get_cv_with_env` for
|
||||||
/// a variant that supports environment variables.
|
/// a variant that supports environment variables.
|
||||||
fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
|
fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
|
||||||
if let Some(vals) = self.credential_values.borrow() {
|
if let Some(vals) = self.credential_values.get() {
|
||||||
let val = self.get_cv_helper(key, vals)?;
|
let val = self.get_cv_helper(key, vals)?;
|
||||||
if val.is_some() {
|
if val.is_some() {
|
||||||
return Ok(val);
|
return Ok(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.get_cv_helper(key, self.values()?)
|
self.get_cv_helper(key, &*self.values()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cv_helper(
|
fn get_cv_helper(
|
||||||
@ -1791,7 +1797,7 @@ impl GlobalContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.credential_values
|
self.credential_values
|
||||||
.fill(credential_values)
|
.set(credential_values)
|
||||||
.expect("was not filled at beginning of the function");
|
.expect("was not filled at beginning of the function");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1883,12 +1889,12 @@ impl GlobalContext {
|
|||||||
self.jobserver.as_ref()
|
self.jobserver.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
|
pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
|
||||||
let http = self
|
let http = self
|
||||||
.easy
|
.easy
|
||||||
.try_borrow_with(|| http_handle(self).map(RefCell::new))?;
|
.try_borrow_with(|| http_handle(self).map(Into::into))?;
|
||||||
{
|
{
|
||||||
let mut http = http.borrow_mut();
|
let mut http = http.lock().unwrap();
|
||||||
http.reset();
|
http.reset();
|
||||||
let timeout = configure_http_handle(self, &mut http)?;
|
let timeout = configure_http_handle(self, &mut http)?;
|
||||||
timeout.configure(&mut http)?;
|
timeout.configure(&mut http)?;
|
||||||
@ -2099,19 +2105,19 @@ impl GlobalContext {
|
|||||||
///
|
///
|
||||||
/// The package cache lock must be held to call this function (and to use
|
/// The package cache lock must be held to call this function (and to use
|
||||||
/// it in general).
|
/// it in general).
|
||||||
pub fn global_cache_tracker(&self) -> CargoResult<RefMut<'_, GlobalCacheTracker>> {
|
pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
|
||||||
let tracker = self.global_cache_tracker.try_borrow_with(|| {
|
let tracker = self.global_cache_tracker.try_borrow_with(|| {
|
||||||
Ok::<_, anyhow::Error>(RefCell::new(GlobalCacheTracker::new(self)?))
|
Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
|
||||||
})?;
|
})?;
|
||||||
Ok(tracker.borrow_mut())
|
Ok(tracker.lock().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the shared [`DeferredGlobalLastUse`].
|
/// Returns a reference to the shared [`DeferredGlobalLastUse`].
|
||||||
pub fn deferred_global_last_use(&self) -> CargoResult<RefMut<'_, DeferredGlobalLastUse>> {
|
pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
|
||||||
let deferred = self.deferred_global_last_use.try_borrow_with(|| {
|
let deferred = self
|
||||||
Ok::<_, anyhow::Error>(RefCell::new(DeferredGlobalLastUse::new()))
|
.deferred_global_last_use
|
||||||
})?;
|
.try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
|
||||||
Ok(deferred.borrow_mut())
|
Ok(deferred.lock().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the global [`WarningHandling`] configuration.
|
/// Get the global [`WarningHandling`] configuration.
|
||||||
@ -2122,6 +2128,10 @@ impl GlobalContext {
|
|||||||
Ok(WarningHandling::default())
|
Ok(WarningHandling::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
|
||||||
|
self.ws_roots.lock().unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal error for serde errors.
|
/// Internal error for serde errors.
|
||||||
@ -3192,4 +3202,10 @@ mod tests {
|
|||||||
assert_eq!(http.multiplexing, result);
|
assert_eq!(http.multiplexing, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sync_context() {
|
||||||
|
fn assert_sync<S: Sync>() {}
|
||||||
|
assert_sync::<GlobalContext>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,10 +389,9 @@ fn acquire(
|
|||||||
lock_try: &dyn Fn() -> Result<(), TryLockError>,
|
lock_try: &dyn Fn() -> Result<(), TryLockError>,
|
||||||
lock_block: &dyn Fn() -> io::Result<()>,
|
lock_block: &dyn Fn() -> io::Result<()>,
|
||||||
) -> CargoResult<()> {
|
) -> CargoResult<()> {
|
||||||
if cfg!(debug_assertions) {
|
// Ensure `shell` is not already in use,
|
||||||
// Force borrow to catch invalid borrows outside of contention situations
|
// regardless of whether we hit contention or not
|
||||||
gctx.shell().verbosity();
|
gctx.debug_assert_shell_not_borrowed();
|
||||||
}
|
|
||||||
if try_acquire(path, lock_try)? {
|
if try_acquire(path, lock_try)? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ pub use self::into_url::IntoUrl;
|
|||||||
pub use self::into_url_with_base::IntoUrlWithBase;
|
pub use self::into_url_with_base::IntoUrlWithBase;
|
||||||
pub(crate) use self::io::LimitErrorReader;
|
pub(crate) use self::io::LimitErrorReader;
|
||||||
pub use self::lockserver::{LockServer, LockServerClient, LockServerStarted};
|
pub use self::lockserver::{LockServer, LockServerClient, LockServerStarted};
|
||||||
|
pub use self::once::OnceExt;
|
||||||
pub use self::progress::{Progress, ProgressStyle};
|
pub use self::progress::{Progress, ProgressStyle};
|
||||||
pub use self::queue::Queue;
|
pub use self::queue::Queue;
|
||||||
pub use self::rustc::Rustc;
|
pub use self::rustc::Rustc;
|
||||||
@ -56,6 +57,7 @@ pub mod lints;
|
|||||||
mod lockserver;
|
mod lockserver;
|
||||||
pub mod machine_message;
|
pub mod machine_message;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
mod once;
|
||||||
mod progress;
|
mod progress;
|
||||||
mod queue;
|
mod queue;
|
||||||
pub mod restricted_names;
|
pub mod restricted_names;
|
||||||
|
90
src/cargo/util/once.rs
Normal file
90
src/cargo/util/once.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
//! Extension functions for [`std::sync::OnceLock`] / [`std::cell::OnceCell`]
|
||||||
|
//!
|
||||||
|
//! This adds polyfills for functionality in `lazycell` that is not stable within `std`.
|
||||||
|
|
||||||
|
pub trait OnceExt {
|
||||||
|
type T;
|
||||||
|
|
||||||
|
/// This might run `f` multiple times if different threads start initializing at once.
|
||||||
|
fn try_borrow_with<F, E>(&self, f: F) -> Result<&Self::T, E>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Result<Self::T, E>;
|
||||||
|
|
||||||
|
fn replace(&mut self, new_value: Self::T) -> Option<Self::T>;
|
||||||
|
|
||||||
|
fn filled(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OnceExt for std::sync::OnceLock<T> {
|
||||||
|
type T = T;
|
||||||
|
|
||||||
|
fn try_borrow_with<F, E>(&self, f: F) -> Result<&T, E>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Result<T, E>,
|
||||||
|
{
|
||||||
|
if let Some(value) = self.get() {
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not how the unstable `OnceLock::get_or_try_init` works. That only starts `f` if
|
||||||
|
// no other `f` is executing and the value is not initialized. However, correctly implementing that is
|
||||||
|
// hard (one has properly handle panics in `f`) and not doable with the stable API of `OnceLock`.
|
||||||
|
let value = f()?;
|
||||||
|
// Another thread might have initialized `self` since we checked that `self.get()` returns `None`. If this is the case, `self.set()`
|
||||||
|
// returns an error. We ignore it and return the value set by the other
|
||||||
|
// thread.
|
||||||
|
let _ = self.set(value);
|
||||||
|
Ok(self.get().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace(&mut self, new_value: T) -> Option<T> {
|
||||||
|
if let Some(value) = self.get_mut() {
|
||||||
|
Some(std::mem::replace(value, new_value))
|
||||||
|
} else {
|
||||||
|
let result = self.set(new_value);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filled(&self) -> bool {
|
||||||
|
self.get().is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OnceExt for std::cell::OnceCell<T> {
|
||||||
|
type T = T;
|
||||||
|
|
||||||
|
fn try_borrow_with<F, E>(&self, f: F) -> Result<&T, E>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Result<T, E>,
|
||||||
|
{
|
||||||
|
if let Some(value) = self.get() {
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not how the unstable `OnceLock::get_or_try_init` works. That only starts `f` if
|
||||||
|
// no other `f` is executing and the value is not initialized. However, correctly implementing that is
|
||||||
|
// hard (one has properly handle panics in `f`) and not doable with the stable API of `OnceLock`.
|
||||||
|
let value = f()?;
|
||||||
|
// Another thread might have initialized `self` since we checked that `self.get()` returns `None`. If this is the case, `self.set()`
|
||||||
|
// returns an error. We ignore it and return the value set by the other
|
||||||
|
// thread.
|
||||||
|
let _ = self.set(value);
|
||||||
|
Ok(self.get().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace(&mut self, new_value: T) -> Option<T> {
|
||||||
|
if let Some(value) = self.get_mut() {
|
||||||
|
Some(std::mem::replace(value, new_value))
|
||||||
|
} else {
|
||||||
|
let result = self.set(new_value);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filled(&self) -> bool {
|
||||||
|
self.get().is_some()
|
||||||
|
}
|
||||||
|
}
|
@ -81,8 +81,7 @@ pub fn read_manifest(
|
|||||||
to_workspace_config(&original_toml, path, is_embedded, gctx, &mut warnings)?;
|
to_workspace_config(&original_toml, path, is_embedded, gctx, &mut warnings)?;
|
||||||
if let WorkspaceConfig::Root(ws_root_config) = &workspace_config {
|
if let WorkspaceConfig::Root(ws_root_config) = &workspace_config {
|
||||||
let package_root = path.parent().unwrap();
|
let package_root = path.parent().unwrap();
|
||||||
gctx.ws_roots
|
gctx.ws_roots()
|
||||||
.borrow_mut()
|
|
||||||
.insert(package_root.to_owned(), ws_root_config.clone());
|
.insert(package_root.to_owned(), ws_root_config.clone());
|
||||||
}
|
}
|
||||||
let normalized_toml = normalize_toml(
|
let normalized_toml = normalize_toml(
|
||||||
@ -996,7 +995,7 @@ fn inheritable_from_path(
|
|||||||
|
|
||||||
// Let the borrow exit scope so that it can be picked up if there is a need to
|
// Let the borrow exit scope so that it can be picked up if there is a need to
|
||||||
// read a manifest
|
// read a manifest
|
||||||
if let Some(ws_root) = gctx.ws_roots.borrow().get(workspace_path_root) {
|
if let Some(ws_root) = gctx.ws_roots().get(workspace_path_root) {
|
||||||
return Ok(ws_root.inheritable().clone());
|
return Ok(ws_root.inheritable().clone());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1004,9 +1003,7 @@ fn inheritable_from_path(
|
|||||||
let man = read_manifest(&workspace_path, source_id, gctx)?;
|
let man = read_manifest(&workspace_path, source_id, gctx)?;
|
||||||
match man.workspace_config() {
|
match man.workspace_config() {
|
||||||
WorkspaceConfig::Root(root) => {
|
WorkspaceConfig::Root(root) => {
|
||||||
gctx.ws_roots
|
gctx.ws_roots().insert(workspace_path, root.clone());
|
||||||
.borrow_mut()
|
|
||||||
.insert(workspace_path, root.clone());
|
|
||||||
Ok(root.inheritable().clone())
|
Ok(root.inheritable().clone())
|
||||||
}
|
}
|
||||||
_ => bail!(
|
_ => bail!(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user