blob: 4e7c59df809e2327a62ab7cfdc6b0268ea3802ba [file] [log] [blame]
use lazy_static::lazy_static;
use std::collections::BTreeMap;
use std::env;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, MutexGuard};
// Ensures we don't race on the env vars. This is only also important in tests,
// where we have multiple copies of the RLS running in the same process.
lazy_static! {
static ref ENV_LOCK: Arc<EnvironmentLock> = Arc::new(EnvironmentLock::new());
}
/// An RAII helper to set and reset the env vars.
/// Requires supplying an external lock guard to guarantee env var consistency across multiple threads.
pub struct Environment<'a> {
old_vars: BTreeMap<String, Option<OsString>>,
old_cwd: PathBuf,
_guard: MutexGuard<'a, ()>,
}
impl<'a> Environment<'a> {
pub fn push_with_lock(
envs: &BTreeMap<String, Option<OsString>>,
cwd: Option<&Path>,
lock: MutexGuard<'a, ()>,
) -> Environment<'a> {
let mut result = Environment {
old_vars: BTreeMap::new(),
old_cwd: env::current_dir().expect("failed to read cwd"),
_guard: lock,
};
if let Some(cwd) = cwd {
env::set_current_dir(cwd).expect("failed to change cwd");
}
for (k, v) in envs {
result.push_var(k, v);
}
result
}
pub fn push_var(&mut self, key: &str, value: &Option<OsString>) {
self.old_vars.insert(key.to_owned(), env::var_os(key));
match *value {
Some(ref v) => env::set_var(key, v),
None => env::remove_var(key),
}
}
pub fn get_old_cwd(&self) -> &Path {
&self.old_cwd
}
}
impl<'a> Drop for Environment<'a> {
fn drop(&mut self) {
drop(env::set_current_dir(&self.old_cwd));
for (k, v) in &self.old_vars {
match *v {
Some(ref v) => env::set_var(k, v),
None => env::remove_var(k),
}
}
}
}
/// Implements a double mutex with a not-so-strict lock order guarantee, that can be used to guard
/// environment variables and guarantee consistency across multiple threads. Since environment
/// is a global, shared resource with a static lifetime, the `EnvironmentLock` is effectively
/// a singleton - a global, static instance.
///
/// It uses two locks instead of one, because RLS, while executing a Cargo build routine, not only
/// needs to guarantee consistent env vars across the Cargo invocation, but also, while holding it,
/// it needs to provide a more fine-grained way to synchronize env vars across different inner
/// compiler invocations, for which Cargo sets specific env vars.
/// To enforce proper env var guarantees, regular rustc and Cargo build routines must first acquire
/// the first, outer lock. Only then, if needed, nested rustc calls inside Cargo routine can
/// acquire the second, inner lock.
/// We're using linked Cargo and rustc to optimize serialization and IPC overhead, which means
/// we don't spawn different processes, hence why we share a single environment and need to provide
/// synchronized access to it.
pub struct EnvironmentLock {
outer: Mutex<()>,
inner: Mutex<()>,
}
/// Helper type that provides a unified way to access both outer and inner types of
/// `EnvironmentLock` lock interfaces.
pub enum EnvironmentLockFacade {
Outer(Arc<EnvironmentLock>),
Inner(InnerLock),
}
impl<'a> EnvironmentLockFacade {
/// Retrieves access to an underlying, corresponding `Mutex` lock of `EnvironmentLock` and
/// additionally returns `InnerLock` if the underlying lock is an `OuterLock`.
pub fn lock(&self) -> (MutexGuard<'a, ()>, Option<InnerLock>) {
match *self {
EnvironmentLockFacade::Outer(ref lock) => {
let (guard, inner) = lock.lock();
(guard, Some(inner))
}
EnvironmentLockFacade::Inner(ref lock) => (lock.lock(), None),
}
}
}
impl<'a> EnvironmentLock {
fn new() -> EnvironmentLock {
EnvironmentLock { outer: Mutex::new(()), inner: Mutex::new(()) }
}
/// Retrieves a pointer to the single, static instance of an `EnvironmentLock`.
pub fn get() -> Arc<EnvironmentLock> {
ENV_LOCK.clone()
}
/// Acquires the first, outer lock and additionally return `InnerLock` interface, through which
/// user can access the second, inner lock. Does not enforce any guarantees regarding order of
/// locking, since `InnerLock` can be copied outside 'a lifetime and locked there.
pub fn lock(&self) -> (MutexGuard<'a, ()>, InnerLock) {
(ENV_LOCK.outer.lock().unwrap(), InnerLock {})
}
/// Constructs a corresponding `EnvironmentLockFacade` value, erasing specific type of the lock.
pub fn as_facade(&self) -> EnvironmentLockFacade {
EnvironmentLockFacade::Outer(ENV_LOCK.clone())
}
}
/// Acts as an interface through which user can acquire the second, inner lock of `EnvironmentLock`.
pub struct InnerLock;
impl<'a> InnerLock {
/// Acquires the second, inner environment lock.
pub fn lock(&self) -> MutexGuard<'a, ()> {
ENV_LOCK.inner.lock().unwrap()
}
/// Constructs a corresponding `EnvironmentLockFacade` value, erasing specific type of the lock.
pub fn as_facade(&self) -> EnvironmentLockFacade {
EnvironmentLockFacade::Inner(Self {})
}
}