feat: Add lint for global use of `hint-mostly-unused` (#15995)

This PR adds a lint that checks for global use of `hint-mostly-unused`.
This also adds a "workspace lint pass", since we only want to lint on
the workspace's `[profile]`, as all other `[profile]` are ignored. This
new pass uses `[workspace.lints.cargo]` for configuration if
`[workspace]` exists, and `[lints]` if it doesn't[^1].

Note: This lint will get emitted if `-Zprofile-hint-mostly-unused` is
set, regardless of whether `-Zcargo-lints` is set.

CC @joshtriplett

[^1]: We can change the behavior in the future if it is not correct
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 30f301c..3845469 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -103,7 +103,7 @@
       - name: Install cargo-semver-checks
         run: |
           mkdir installed-bins
-          curl -Lf https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.43.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz \
+          curl -Lf https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.44.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz \
             | tar -xz --directory=./installed-bins
           echo `pwd`/installed-bins >> $GITHUB_PATH
       - run: ci/validate-version-bump.sh
diff --git a/Cargo.toml b/Cargo.toml
index d7e1cf0..132360d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,7 @@
 ]
 
 [workspace.package]
-rust-version = "1.87"  # MSRV:3
+rust-version = "1.88"  # MSRV:3
 edition = "2024"
 license = "MIT OR Apache-2.0"
 homepage = "https://github.com/rust-lang/cargo"
diff --git a/crates/cargo-util-schemas/lockfile.schema.json b/crates/cargo-util-schemas/lockfile.schema.json
index 5d7149d..5c54abe 100644
--- a/crates/cargo-util-schemas/lockfile.schema.json
+++ b/crates/cargo-util-schemas/lockfile.schema.json
@@ -1,10 +1,11 @@
 {
   "$schema": "https://json-schema.org/draft/2020-12/schema",
   "title": "TomlLockfile",
-  "description": "The `Cargo.lock` structure.",
+  "description": "Serialization of `Cargo.lock`",
   "type": "object",
   "properties": {
     "version": {
+      "description": "The lockfile format version (`version =` field).\n\nThis field is optional for backward compatibility. Older lockfiles, i.e. V1 and V2, does\nnot have the version field serialized.",
       "type": [
         "integer",
         "null"
@@ -13,6 +14,7 @@
       "minimum": 0
     },
     "package": {
+      "description": "The list of `[[package]]` entries describing each resolved dependency.",
       "type": [
         "array",
         "null"
@@ -22,7 +24,7 @@
       }
     },
     "root": {
-      "description": "`root` is optional to allow backward compatibility.",
+      "description": "The `[root]` table describing the root package.\n\nThis field is optional for backward compatibility. Older lockfiles have the root package\nseparated, whereas newer lockfiles have the root package as part of `[[package]]`.",
       "anyOf": [
         {
           "$ref": "#/$defs/TomlLockfileDependency"
@@ -33,6 +35,7 @@
       ]
     },
     "metadata": {
+      "description": "The `[metadata]` table\n\n\nIn older lockfile versions, dependency checksums were stored here instead of alongside each\npackage entry.",
       "type": [
         "object",
         "null"
@@ -42,32 +45,43 @@
       }
     },
     "patch": {
+      "description": "The `[patch]` table describing unused patches.\n\nThe lockfile stores them as a list of `[[patch.unused]]` entries.",
       "$ref": "#/$defs/TomlLockfilePatch"
     }
   },
   "$defs": {
     "TomlLockfileDependency": {
+      "description": "Serialization of lockfiles dependencies",
       "type": "object",
       "properties": {
         "name": {
+          "description": "The name of the dependency.",
           "type": "string"
         },
         "version": {
+          "description": "The version of the dependency.",
           "type": "string"
         },
         "source": {
-          "type": [
-            "string",
-            "null"
+          "description": "The source of the dependency.\n\nCargo does not serialize path dependencies.",
+          "anyOf": [
+            {
+              "$ref": "#/$defs/TomlLockfileSourceId"
+            },
+            {
+              "type": "null"
+            }
           ]
         },
         "checksum": {
+          "description": "The checksum of the dependency.\n\nIn older lockfiles, checksums were not stored here and instead on a separate `[metadata]`\ntable (see [`TomlLockfileMetadata`]).",
           "type": [
             "string",
             "null"
           ]
         },
         "dependencies": {
+          "description": "The transitive dependencies used by this dependency.",
           "type": [
             "array",
             "null"
@@ -77,6 +91,7 @@
           }
         },
         "replace": {
+          "description": "The replace of the dependency.",
           "anyOf": [
             {
               "$ref": "#/$defs/TomlLockfilePackageId"
@@ -92,7 +107,12 @@
         "version"
       ]
     },
+    "TomlLockfileSourceId": {
+      "description": "Serialization of dependency's source",
+      "type": "string"
+    },
     "TomlLockfilePackageId": {
+      "description": "Serialization of package IDs.\n\nThe version and source are only included when necessary to disambiguate between packages:\n- If multiple packages share the same name, the version is included.\n- If multiple packages share the same name and version, the source is included.",
       "type": "object",
       "properties": {
         "name": {
@@ -105,9 +125,13 @@
           ]
         },
         "source": {
-          "type": [
-            "string",
-            "null"
+          "anyOf": [
+            {
+              "$ref": "#/$defs/TomlLockfileSourceId"
+            },
+            {
+              "type": "null"
+            }
           ]
         }
       },
@@ -116,9 +140,11 @@
       ]
     },
     "TomlLockfilePatch": {
+      "description": "Serialization of unused patches\n\nCargo stores patches that were declared but not used during resolution.",
       "type": "object",
       "properties": {
         "unused": {
+          "description": "The list of unused dependency patches.",
           "type": "array",
           "items": {
             "$ref": "#/$defs/TomlLockfileDependency"
diff --git a/crates/cargo-util-schemas/src/lockfile.rs b/crates/cargo-util-schemas/src/lockfile.rs
index 8e949d0..c953c79 100644
--- a/crates/cargo-util-schemas/src/lockfile.rs
+++ b/crates/cargo-util-schemas/src/lockfile.rs
@@ -1,3 +1,5 @@
+//! `Cargo.lock` / Lockfile schema definition
+
 use std::collections::BTreeMap;
 use std::fmt;
 use std::{cmp::Ordering, str::FromStr};
@@ -7,44 +9,80 @@
 
 use crate::core::{GitReference, SourceKind};
 
-/// The `Cargo.lock` structure.
+/// Serialization of `Cargo.lock`
 #[derive(Serialize, Deserialize, Debug)]
 #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
 pub struct TomlLockfile {
+    /// The lockfile format version (`version =` field).
+    ///
+    /// This field is optional for backward compatibility. Older lockfiles, i.e. V1 and V2, does
+    /// not have the version field serialized.
     pub version: Option<u32>,
+    /// The list of `[[package]]` entries describing each resolved dependency.
     pub package: Option<Vec<TomlLockfileDependency>>,
-    /// `root` is optional to allow backward compatibility.
+    /// The `[root]` table describing the root package.
+    ///
+    /// This field is optional for backward compatibility. Older lockfiles have the root package
+    /// separated, whereas newer lockfiles have the root package as part of `[[package]]`.
     pub root: Option<TomlLockfileDependency>,
+    /// The `[metadata]` table
+    ///
+    ///
+    /// In older lockfile versions, dependency checksums were stored here instead of alongside each
+    /// package entry.
     pub metadata: Option<TomlLockfileMetadata>,
+    /// The `[patch]` table describing unused patches.
+    ///
+    /// The lockfile stores them as a list of `[[patch.unused]]` entries.
     #[serde(default, skip_serializing_if = "TomlLockfilePatch::is_empty")]
     pub patch: TomlLockfilePatch,
 }
 
+/// Serialization of lockfiles metadata
+///
+/// Older versions of lockfiles have their dependencies' checksums on this `[metadata]` table.
+pub type TomlLockfileMetadata = BTreeMap<String, String>;
+
+/// Serialization of unused patches
+///
+/// Cargo stores patches that were declared but not used during resolution.
 #[derive(Serialize, Deserialize, Debug, Default)]
 #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
 pub struct TomlLockfilePatch {
+    /// The list of unused dependency patches.
     pub unused: Vec<TomlLockfileDependency>,
 }
 
-pub type TomlLockfileMetadata = BTreeMap<String, String>;
-
 impl TomlLockfilePatch {
     fn is_empty(&self) -> bool {
         self.unused.is_empty()
     }
 }
 
+/// Serialization of lockfiles dependencies
 #[derive(Serialize, Deserialize, Debug, PartialOrd, Ord, PartialEq, Eq)]
 #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
 pub struct TomlLockfileDependency {
+    /// The name of the dependency.
     pub name: String,
+    /// The version of the dependency.
     pub version: String,
+    /// The source of the dependency.
+    ///
+    /// Cargo does not serialize path dependencies.
     pub source: Option<TomlLockfileSourceId>,
+    /// The checksum of the dependency.
+    ///
+    /// In older lockfiles, checksums were not stored here and instead on a separate `[metadata]`
+    /// table (see [`TomlLockfileMetadata`]).
     pub checksum: Option<String>,
+    /// The transitive dependencies used by this dependency.
     pub dependencies: Option<Vec<TomlLockfilePackageId>>,
+    /// The replace of the dependency.
     pub replace: Option<TomlLockfilePackageId>,
 }
 
+/// Serialization of dependency's source
 #[derive(Debug, Clone)]
 #[cfg_attr(
     feature = "unstable-schema",
@@ -52,11 +90,15 @@
     schemars(with = "String")
 )]
 pub struct TomlLockfileSourceId {
-    /// Full string of the source
+    /// The string representation of the source as it appears in the lockfile.
     source_str: String,
-    /// Used for sources ordering
+    /// The parsed source type, e.g. `git`, `registry`.
+    ///
+    /// Used for sources ordering.
     kind: SourceKind,
-    /// Used for sources ordering
+    /// The parsed URL of the source.
+    ///
+    /// Used for sources ordering.
     url: Url,
 }
 
@@ -157,6 +199,11 @@
     }
 }
 
+/// Serialization of package IDs.
+///
+/// The version and source are only included when necessary to disambiguate between packages:
+/// - If multiple packages share the same name, the version is included.
+/// - If multiple packages share the same name and version, the source is included.
 #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)]
 #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
 pub struct TomlLockfilePackageId {
diff --git a/src/cargo/core/registry.rs b/src/cargo/core/registry.rs
index d116c1e..9bc4d82 100644
--- a/src/cargo/core/registry.rs
+++ b/src/cargo/core/registry.rs
@@ -800,11 +800,9 @@
 
     #[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()
diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs
index 8ac6e6f..ed1bbf7 100644
--- a/src/cargo/core/shell.rs
+++ b/src/cargo/core/shell.rs
@@ -65,7 +65,7 @@
     }
 
     /// 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 @@
 /// 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>,
diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs
index f2e8397..fc20aa2 100644
--- a/src/cargo/core/workspace.rs
+++ b/src/cargo/core/workspace.rs
@@ -2119,7 +2119,7 @@
 ) -> 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) {
diff --git a/src/cargo/sources/git/utils.rs b/src/cargo/sources/git/utils.rs
index e0346aa..e9224df 100644
--- a/src/cargo/sources/git/utils.rs
+++ b/src/cargo/sources/git/utils.rs
@@ -1528,7 +1528,7 @@
         "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)?;
diff --git a/src/cargo/util/cache_lock.rs b/src/cargo/util/cache_lock.rs
index afd06c9..068e1bb 100644
--- a/src/cargo/util/cache_lock.rs
+++ b/src/cargo/util/cache_lock.rs
@@ -91,8 +91,8 @@
 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 @@
 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 @@
     ///
     /// [`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 @@
         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 @@
     /// `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,
diff --git a/src/cargo/util/context/mod.rs b/src/cargo/util/context/mod.rs
index 62b0411..01ba990 100644
--- a/src/cargo/util/context/mod.rs
+++ b/src/cargo/util/context/mod.rs
@@ -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::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::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_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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
 
         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 @@
             },
             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 @@
     }
 
     /// 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 @@
     }
 
     /// 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 @@
     /// 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 @@
     /// 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 @@
             }
         }
         self.credential_values
-            .fill(credential_values)
+            .set(credential_values)
             .expect("was not filled at beginning of the function");
         Ok(())
     }
@@ -1883,12 +1889,12 @@
         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 @@
     ///
     /// 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 @@
             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 @@
             assert_eq!(http.multiplexing, result);
         }
     }
+
+    #[test]
+    fn sync_context() {
+        fn assert_sync<S: Sync>() {}
+        assert_sync::<GlobalContext>();
+    }
 }
diff --git a/src/cargo/util/flock.rs b/src/cargo/util/flock.rs
index 9c929de..e82465e 100644
--- a/src/cargo/util/flock.rs
+++ b/src/cargo/util/flock.rs
@@ -389,10 +389,9 @@
     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(());
     }
diff --git a/src/cargo/util/frontmatter.rs b/src/cargo/util/frontmatter.rs
index 7eeb6ce..6d50878 100644
--- a/src/cargo/util/frontmatter.rs
+++ b/src/cargo/util/frontmatter.rs
@@ -86,7 +86,8 @@
             .push_visible_span(open_start..open_end));
         };
         let info = input.next_slice(info_nl.start);
-        let info = info.trim_matches(is_whitespace);
+        let info = info.strip_suffix('\r').unwrap_or(info); // already excludes `\n`
+        let info = info.trim_matches(is_horizontal_whitespace);
         if !info.is_empty() {
             let info_start = info.offset_from(&raw);
             let info_end = info_start + info.len();
@@ -147,7 +148,8 @@
             )
             .push_visible_span(open_start..open_end));
         } else {
-            let after_closing_fence = after_closing_fence.trim_matches(is_whitespace);
+            let after_closing_fence = strip_newline(after_closing_fence);
+            let after_closing_fence = after_closing_fence.trim_matches(is_horizontal_whitespace);
             if !after_closing_fence.is_empty() {
                 // extra characters beyond the original fence pattern
                 let after_start = after_closing_fence.offset_from(&raw);
@@ -261,8 +263,6 @@
 /// True if `c` is considered a whitespace according to Rust language definition.
 /// See [Rust language reference](https://doc.rust-lang.org/reference/whitespace.html)
 /// for definitions of these classes.
-///
-/// See rust-lang/rust's compiler/rustc_lexer/src/lib.rs `is_whitespace`
 fn is_whitespace(c: char) -> bool {
     // This is Pattern_White_Space.
     //
@@ -271,27 +271,46 @@
 
     matches!(
         c,
-        // Usual ASCII suspects
-        '\u{0009}'   // \t
-        | '\u{000A}' // \n
+        // End-of-line characters
+        | '\u{000A}' // line feed (\n)
         | '\u{000B}' // vertical tab
         | '\u{000C}' // form feed
-        | '\u{000D}' // \r
-        | '\u{0020}' // space
+        | '\u{000D}' // carriage return (\r)
+        | '\u{0085}' // next line (from latin1)
+        | '\u{2028}' // LINE SEPARATOR
+        | '\u{2029}' // PARAGRAPH SEPARATOR
 
-        // NEXT LINE from latin1
-        | '\u{0085}'
-
-        // Bidi markers
+        // `Default_Ignorable_Code_Point` characters
         | '\u{200E}' // LEFT-TO-RIGHT MARK
         | '\u{200F}' // RIGHT-TO-LEFT MARK
 
-        // Dedicated whitespace characters from Unicode
-        | '\u{2028}' // LINE SEPARATOR
-        | '\u{2029}' // PARAGRAPH SEPARATOR
+        // Horizontal space characters
+        | '\u{0009}'   // tab (\t)
+        | '\u{0020}' // space
     )
 }
 
+/// True if `c` is considered horizontal whitespace according to Rust language definition.
+fn is_horizontal_whitespace(c: char) -> bool {
+    // This is Pattern_White_Space.
+    //
+    // Note that this set is stable (ie, it doesn't change with different
+    // Unicode versions), so it's ok to just hard-code the values.
+
+    matches!(
+        c,
+        // Horizontal space characters
+        '\u{0009}'   // tab (\t)
+        | '\u{0020}' // space
+    )
+}
+
+fn strip_newline(text: &str) -> &str {
+    text.strip_suffix("\r\n")
+        .or_else(|| text.strip_suffix('\n'))
+        .unwrap_or(text)
+}
+
 #[derive(Debug)]
 pub struct FrontmatterError {
     message: String,
diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs
index a61fb5b..62f181f 100644
--- a/src/cargo/util/mod.rs
+++ b/src/cargo/util/mod.rs
@@ -18,6 +18,7 @@
 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 @@
 mod lockserver;
 pub mod machine_message;
 pub mod network;
+mod once;
 mod progress;
 mod queue;
 pub mod restricted_names;
diff --git a/src/cargo/util/once.rs b/src/cargo/util/once.rs
new file mode 100644
index 0000000..63f0647
--- /dev/null
+++ b/src/cargo/util/once.rs
@@ -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()
+    }
+}
diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs
index d0a7ced..cf20471 100644
--- a/src/cargo/util/toml/mod.rs
+++ b/src/cargo/util/toml/mod.rs
@@ -81,8 +81,7 @@
             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 @@
 
     // 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 @@
     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!(
diff --git a/src/doc/src/guide/build-performance.md b/src/doc/src/guide/build-performance.md
index a9e46f6..c321ee8 100644
--- a/src/doc/src/guide/build-performance.md
+++ b/src/doc/src/guide/build-performance.md
@@ -63,8 +63,29 @@
 
 Trade-offs:
 - ✅ Faster code generation (`cargo build`)
-- ❌ **Requires using nightly Rust and an unstable Cargo feature**
+- ❌ **Requires using nightly Rust and an [unstable Cargo feature][codegen-backend-feature]**
 - ❌ Worse runtime performance of the generated code
   - Speeds up build part of `cargo test`, but might increase its test execution part
 - ❌ Only available for [certain targets](https://github.com/rust-lang/rustc_codegen_cranelift?tab=readme-ov-file#platform-support)
 - ❌ Might not support all Rust features (e.g. unwinding)
+
+[codegen-backend-feature]: ../reference/unstable.md#codegen-backend
+
+### Enable the experimental parallel frontend
+
+Recommendation: Add to your `.cargo/config.toml`:
+
+```toml
+[build]
+rustflags = "-Zthreads=8"
+```
+
+This [`rustflags`][build.rustflags] will enable the [parallel frontend][parallel-frontend-blog] of the Rust compiler, and tell it to use `n` threads. The value of `n` should be chosen according to the number of cores available on your system, although there are diminishing returns. We recommend using at most `8` threads.
+
+Trade-offs:
+- ✅ Faster build times
+- ❌ **Requires using nightly Rust and an [unstable Rust feature][parallel-frontend-issue]**
+
+[parallel-frontend-blog]: https://blog.rust-lang.org/2023/11/09/parallel-rustc/
+[parallel-frontend-issue]: https://github.com/rust-lang/rust/issues/113349
+[build.rustflags]: ../reference/config.md#buildrustflags