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)]
|
||||
fn block_until_ready(&mut self) -> CargoResult<()> {
|
||||
if cfg!(debug_assertions) {
|
||||
// Force borrow to catch invalid borrows, regardless of which source is used and how it
|
||||
// happens to behave this time
|
||||
self.gctx.shell().verbosity();
|
||||
}
|
||||
// Ensure `shell` is not already in use,
|
||||
// regardless of which source is used and how it happens to behave this time
|
||||
self.gctx.debug_assert_shell_not_borrowed();
|
||||
for (source_id, source) in self.sources.sources_mut() {
|
||||
source
|
||||
.block_until_ready()
|
||||
|
@ -65,7 +65,7 @@ impl Shell {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
output: ShellOut::Write(AutoStream::never(out)), // strip all formatting on write
|
||||
verbosity: Verbosity::Verbose,
|
||||
@ -432,7 +432,7 @@ impl Default for Shell {
|
||||
/// A `Write`able object, either with or without color support
|
||||
enum ShellOut {
|
||||
/// 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
|
||||
Stream {
|
||||
stdout: AutoStream<std::io::Stdout>,
|
||||
|
@ -2037,7 +2037,7 @@ fn find_workspace_root_with_loader(
|
||||
) -> CargoResult<Option<PathBuf>> {
|
||||
// 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
|
||||
// root. Note we skip the first item since that is just the path itself
|
||||
for current in manifest_path.ancestors().skip(1) {
|
||||
|
@ -1528,7 +1528,7 @@ fn github_fast_path(
|
||||
"https://api.github.com/repos/{}/{}/commits/{}",
|
||||
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);
|
||||
handle.get(true)?;
|
||||
handle.url(&url)?;
|
||||
|
@ -91,8 +91,8 @@ use super::FileLock;
|
||||
use crate::CargoResult;
|
||||
use crate::GlobalContext;
|
||||
use anyhow::Context as _;
|
||||
use std::cell::RefCell;
|
||||
use std::io;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// The style of lock to acquire.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
@ -435,7 +435,11 @@ pub struct CacheLock<'lock> {
|
||||
impl Drop for CacheLock<'_> {
|
||||
fn drop(&mut self) {
|
||||
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 {
|
||||
Shared => {
|
||||
state.mutate_lock.decrement();
|
||||
@ -472,24 +476,25 @@ pub struct CacheLocker {
|
||||
///
|
||||
/// [`CacheLocker`] uses interior mutability because it is stuffed inside
|
||||
/// [`GlobalContext`], which does not allow mutation.
|
||||
state: RefCell<CacheState>,
|
||||
state: Mutex<CacheState>,
|
||||
}
|
||||
|
||||
impl CacheLocker {
|
||||
/// Creates a new `CacheLocker`.
|
||||
pub fn new() -> CacheLocker {
|
||||
CacheLocker {
|
||||
state: RefCell::new(CacheState {
|
||||
state: CacheState {
|
||||
cache_lock: RecursiveLock::new(CACHE_LOCK_NAME),
|
||||
mutate_lock: RecursiveLock::new(MUTATE_NAME),
|
||||
}),
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Acquires a lock with the given mode, possibly blocking if another
|
||||
/// cargo is holding the lock.
|
||||
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)?;
|
||||
Ok(CacheLock { mode, locker: self })
|
||||
}
|
||||
@ -501,7 +506,7 @@ impl CacheLocker {
|
||||
gctx: &GlobalContext,
|
||||
mode: CacheLockMode,
|
||||
) -> CargoResult<Option<CacheLock<'_>>> {
|
||||
let mut state = self.state.borrow_mut();
|
||||
let mut state = self.state.lock().unwrap();
|
||||
if state.lock(gctx, mode, NonBlocking)? == LockAcquired {
|
||||
Ok(Some(CacheLock { mode, locker: self }))
|
||||
} else {
|
||||
@ -519,7 +524,7 @@ impl CacheLocker {
|
||||
/// `DownloadExclusive` will return true if a `MutateExclusive` lock is
|
||||
/// held since they overlap.
|
||||
pub fn is_locked(&self, mode: CacheLockMode) -> bool {
|
||||
let state = self.state.borrow();
|
||||
let state = self.state.lock().unwrap();
|
||||
match (
|
||||
mode,
|
||||
state.cache_lock.count,
|
||||
|
@ -51,7 +51,6 @@
|
||||
|
||||
use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
|
||||
use std::borrow::Cow;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
@ -63,7 +62,7 @@ use std::io::prelude::*;
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Once};
|
||||
use std::sync::{Arc, Mutex, MutexGuard, Once, OnceLock};
|
||||
use std::time::Instant;
|
||||
|
||||
use self::ConfigValue as CV;
|
||||
@ -74,6 +73,7 @@ use crate::core::{CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig,
|
||||
use crate::ops::RegistryCredentialConfig;
|
||||
use crate::sources::CRATES_IO_INDEX;
|
||||
use crate::sources::CRATES_IO_REGISTRY;
|
||||
use crate::util::OnceExt as _;
|
||||
use crate::util::errors::CargoResult;
|
||||
use crate::util::network::http::configure_http_handle;
|
||||
use crate::util::network::http::http_handle;
|
||||
@ -85,7 +85,6 @@ use cargo_util::paths;
|
||||
use cargo_util_schemas::manifest::RegistryName;
|
||||
use curl::easy::Easy;
|
||||
use itertools::Itertools;
|
||||
use lazycell::LazyCell;
|
||||
use serde::Deserialize;
|
||||
use serde::de::IntoDeserializer as _;
|
||||
use serde_untagged::UntaggedEnumVisitor;
|
||||
@ -166,11 +165,11 @@ pub struct GlobalContext {
|
||||
/// The location of the user's Cargo home directory. OS-dependent.
|
||||
home_path: Filesystem,
|
||||
/// Information about how to write messages to the shell
|
||||
shell: RefCell<Shell>,
|
||||
shell: Mutex<Shell>,
|
||||
/// A collection of configuration options
|
||||
values: LazyCell<HashMap<String, ConfigValue>>,
|
||||
values: OnceLock<HashMap<String, ConfigValue>>,
|
||||
/// 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: Option<Vec<String>>,
|
||||
/// The current working directory of cargo
|
||||
@ -178,9 +177,9 @@ pub struct GlobalContext {
|
||||
/// Directory where config file searching should stop (inclusive).
|
||||
search_stop_path: Option<PathBuf>,
|
||||
/// The location of the cargo executable (path to current process)
|
||||
cargo_exe: LazyCell<PathBuf>,
|
||||
cargo_exe: OnceLock<PathBuf>,
|
||||
/// The location of the rustdoc executable
|
||||
rustdoc: LazyCell<PathBuf>,
|
||||
rustdoc: OnceLock<PathBuf>,
|
||||
/// Whether we are printing extra verbose messages
|
||||
extra_verbose: bool,
|
||||
/// `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"
|
||||
unstable_flags_cli: Option<Vec<String>>,
|
||||
/// A handle on curl easy mode for http calls
|
||||
easy: LazyCell<RefCell<Easy>>,
|
||||
easy: OnceLock<Mutex<Easy>>,
|
||||
/// 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
|
||||
cache_rustc_info: bool,
|
||||
/// Creation time of this config, used to output the total build time
|
||||
@ -211,23 +210,23 @@ pub struct GlobalContext {
|
||||
/// Environment variable snapshot.
|
||||
env: Env,
|
||||
/// 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.
|
||||
/// 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.
|
||||
registry_config: LazyCell<RefCell<HashMap<SourceId, Option<RegistryConfig>>>>,
|
||||
registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
|
||||
/// Locks on the package and index caches.
|
||||
package_cache_lock: CacheLocker,
|
||||
/// Cached configuration parsed by Cargo
|
||||
http_config: LazyCell<CargoHttpConfig>,
|
||||
future_incompat_config: LazyCell<CargoFutureIncompatConfig>,
|
||||
net_config: LazyCell<CargoNetConfig>,
|
||||
build_config: LazyCell<CargoBuildConfig>,
|
||||
target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
|
||||
doc_extern_map: LazyCell<RustdocExternMap>,
|
||||
http_config: OnceLock<CargoHttpConfig>,
|
||||
future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
|
||||
net_config: OnceLock<CargoNetConfig>,
|
||||
build_config: OnceLock<CargoBuildConfig>,
|
||||
target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
|
||||
doc_extern_map: OnceLock<RustdocExternMap>,
|
||||
progress_config: ProgressConfig,
|
||||
env_config: LazyCell<Arc<HashMap<String, OsString>>>,
|
||||
env_config: OnceLock<Arc<HashMap<String, OsString>>>,
|
||||
/// This should be false if:
|
||||
/// - 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`
|
||||
@ -245,12 +244,12 @@ pub struct GlobalContext {
|
||||
/// consider using `ConfigBuilder::enable_nightly_features` instead.
|
||||
pub nightly_features_allowed: bool,
|
||||
/// `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.
|
||||
global_cache_tracker: LazyCell<RefCell<GlobalCacheTracker>>,
|
||||
global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
|
||||
/// A cache of modifications to make to [`GlobalContext::global_cache_tracker`],
|
||||
/// 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 {
|
||||
@ -283,14 +282,14 @@ impl GlobalContext {
|
||||
|
||||
GlobalContext {
|
||||
home_path: Filesystem::new(homedir),
|
||||
shell: RefCell::new(shell),
|
||||
shell: Mutex::new(shell),
|
||||
cwd,
|
||||
search_stop_path: None,
|
||||
values: LazyCell::new(),
|
||||
credential_values: LazyCell::new(),
|
||||
values: Default::default(),
|
||||
credential_values: Default::default(),
|
||||
cli_config: None,
|
||||
cargo_exe: LazyCell::new(),
|
||||
rustdoc: LazyCell::new(),
|
||||
cargo_exe: Default::default(),
|
||||
rustdoc: Default::default(),
|
||||
extra_verbose: false,
|
||||
frozen: false,
|
||||
locked: false,
|
||||
@ -304,28 +303,28 @@ impl GlobalContext {
|
||||
},
|
||||
unstable_flags: CliUnstable::default(),
|
||||
unstable_flags_cli: None,
|
||||
easy: LazyCell::new(),
|
||||
crates_io_source_id: LazyCell::new(),
|
||||
easy: Default::default(),
|
||||
crates_io_source_id: Default::default(),
|
||||
cache_rustc_info,
|
||||
creation_time: Instant::now(),
|
||||
target_dir: None,
|
||||
env,
|
||||
updated_sources: LazyCell::new(),
|
||||
credential_cache: LazyCell::new(),
|
||||
registry_config: LazyCell::new(),
|
||||
updated_sources: Default::default(),
|
||||
credential_cache: Default::default(),
|
||||
registry_config: Default::default(),
|
||||
package_cache_lock: CacheLocker::new(),
|
||||
http_config: LazyCell::new(),
|
||||
future_incompat_config: LazyCell::new(),
|
||||
net_config: LazyCell::new(),
|
||||
build_config: LazyCell::new(),
|
||||
target_cfgs: LazyCell::new(),
|
||||
doc_extern_map: LazyCell::new(),
|
||||
http_config: Default::default(),
|
||||
future_incompat_config: Default::default(),
|
||||
net_config: Default::default(),
|
||||
build_config: Default::default(),
|
||||
target_cfgs: Default::default(),
|
||||
doc_extern_map: Default::default(),
|
||||
progress_config: ProgressConfig::default(),
|
||||
env_config: LazyCell::new(),
|
||||
env_config: Default::default(),
|
||||
nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
|
||||
ws_roots: RefCell::new(HashMap::new()),
|
||||
global_cache_tracker: LazyCell::new(),
|
||||
deferred_global_last_use: LazyCell::new(),
|
||||
ws_roots: Default::default(),
|
||||
global_cache_tracker: Default::default(),
|
||||
deferred_global_last_use: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,8 +407,22 @@ impl GlobalContext {
|
||||
}
|
||||
|
||||
/// Gets a reference to the shell, e.g., for writing error messages.
|
||||
pub fn shell(&self) -> RefMut<'_, Shell> {
|
||||
self.shell.borrow_mut()
|
||||
pub fn shell(&self) -> MutexGuard<'_, Shell> {
|
||||
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.
|
||||
@ -513,24 +526,20 @@ impl GlobalContext {
|
||||
}
|
||||
|
||||
/// Which package sources have been updated, used to ensure it is only done once.
|
||||
pub fn updated_sources(&self) -> RefMut<'_, HashSet<SourceId>> {
|
||||
self.updated_sources
|
||||
.borrow_with(|| RefCell::new(HashSet::new()))
|
||||
.borrow_mut()
|
||||
pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
|
||||
self.updated_sources.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Cached credentials from credential providers or configuration.
|
||||
pub fn credential_cache(&self) -> RefMut<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
|
||||
self.credential_cache
|
||||
.borrow_with(|| RefCell::new(HashMap::new()))
|
||||
.borrow_mut()
|
||||
pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
|
||||
self.credential_cache.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Cache of already parsed registries from the `[registries]` table.
|
||||
pub(crate) fn registry_config(&self) -> RefMut<'_, HashMap<SourceId, Option<RegistryConfig>>> {
|
||||
self.registry_config
|
||||
.borrow_with(|| RefCell::new(HashMap::new()))
|
||||
.borrow_mut()
|
||||
pub(crate) fn registry_config(
|
||||
&self,
|
||||
) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
|
||||
self.registry_config.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Gets all config values from disk.
|
||||
@ -550,18 +559,15 @@ impl GlobalContext {
|
||||
/// using this if possible.
|
||||
pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
|
||||
let _ = self.values()?;
|
||||
Ok(self
|
||||
.values
|
||||
.borrow_mut()
|
||||
.expect("already loaded config values"))
|
||||
Ok(self.values.get_mut().expect("already loaded config values"))
|
||||
}
|
||||
|
||||
// Note: this is used by RLS, not Cargo.
|
||||
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")
|
||||
}
|
||||
match self.values.fill(values) {
|
||||
match self.values.set(values.into()) {
|
||||
Ok(()) => Ok(()),
|
||||
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
|
||||
/// a variant that supports environment variables.
|
||||
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)?;
|
||||
if val.is_some() {
|
||||
return Ok(val);
|
||||
}
|
||||
}
|
||||
self.get_cv_helper(key, self.values()?)
|
||||
self.get_cv_helper(key, &*self.values()?)
|
||||
}
|
||||
|
||||
fn get_cv_helper(
|
||||
@ -1791,7 +1797,7 @@ impl GlobalContext {
|
||||
}
|
||||
}
|
||||
self.credential_values
|
||||
.fill(credential_values)
|
||||
.set(credential_values)
|
||||
.expect("was not filled at beginning of the function");
|
||||
Ok(())
|
||||
}
|
||||
@ -1883,12 +1889,12 @@ impl GlobalContext {
|
||||
self.jobserver.as_ref()
|
||||
}
|
||||
|
||||
pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
|
||||
pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
|
||||
let http = self
|
||||
.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();
|
||||
let timeout = configure_http_handle(self, &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
|
||||
/// 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(|| {
|
||||
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`].
|
||||
pub fn deferred_global_last_use(&self) -> CargoResult<RefMut<'_, DeferredGlobalLastUse>> {
|
||||
let deferred = self.deferred_global_last_use.try_borrow_with(|| {
|
||||
Ok::<_, anyhow::Error>(RefCell::new(DeferredGlobalLastUse::new()))
|
||||
})?;
|
||||
Ok(deferred.borrow_mut())
|
||||
pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
|
||||
let deferred = self
|
||||
.deferred_global_last_use
|
||||
.try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
|
||||
Ok(deferred.lock().unwrap())
|
||||
}
|
||||
|
||||
/// Get the global [`WarningHandling`] configuration.
|
||||
@ -2122,6 +2128,10 @@ impl GlobalContext {
|
||||
Ok(WarningHandling::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
|
||||
self.ws_roots.lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal error for serde errors.
|
||||
@ -3192,4 +3202,10 @@ mod tests {
|
||||
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_block: &dyn Fn() -> io::Result<()>,
|
||||
) -> CargoResult<()> {
|
||||
if cfg!(debug_assertions) {
|
||||
// Force borrow to catch invalid borrows outside of contention situations
|
||||
gctx.shell().verbosity();
|
||||
}
|
||||
// Ensure `shell` is not already in use,
|
||||
// regardless of whether we hit contention or not
|
||||
gctx.debug_assert_shell_not_borrowed();
|
||||
if try_acquire(path, lock_try)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ pub use self::into_url::IntoUrl;
|
||||
pub use self::into_url_with_base::IntoUrlWithBase;
|
||||
pub(crate) use self::io::LimitErrorReader;
|
||||
pub use self::lockserver::{LockServer, LockServerClient, LockServerStarted};
|
||||
pub use self::once::OnceExt;
|
||||
pub use self::progress::{Progress, ProgressStyle};
|
||||
pub use self::queue::Queue;
|
||||
pub use self::rustc::Rustc;
|
||||
@ -56,6 +57,7 @@ pub mod lints;
|
||||
mod lockserver;
|
||||
pub mod machine_message;
|
||||
pub mod network;
|
||||
mod once;
|
||||
mod progress;
|
||||
mod queue;
|
||||
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)?;
|
||||
if let WorkspaceConfig::Root(ws_root_config) = &workspace_config {
|
||||
let package_root = path.parent().unwrap();
|
||||
gctx.ws_roots
|
||||
.borrow_mut()
|
||||
gctx.ws_roots()
|
||||
.insert(package_root.to_owned(), ws_root_config.clone());
|
||||
}
|
||||
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
|
||||
// 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());
|
||||
};
|
||||
|
||||
@ -1004,9 +1003,7 @@ fn inheritable_from_path(
|
||||
let man = read_manifest(&workspace_path, source_id, gctx)?;
|
||||
match man.workspace_config() {
|
||||
WorkspaceConfig::Root(root) => {
|
||||
gctx.ws_roots
|
||||
.borrow_mut()
|
||||
.insert(workspace_path, root.clone());
|
||||
gctx.ws_roots().insert(workspace_path, root.clone());
|
||||
Ok(root.inheritable().clone())
|
||||
}
|
||||
_ => bail!(
|
||||
|
Loading…
x
Reference in New Issue
Block a user