use std::thread::JoinHandle;
use crate::prelude::*;
use cargo::util::cache_lock::{CacheLockMode, CacheLocker};
use cargo_test_support::paths;
use cargo_test_support::{retry, thread_wait_timeout, threaded_timeout};
use crate::config::GlobalContextBuilder;
fn verify_lock_is_ok(mode: CacheLockMode) {
let root = paths::root();
threaded_timeout(100, move || {
let gctx = GlobalContextBuilder::new().root(root).build();
let locker = CacheLocker::new();
let _lock = locker.lock(&gctx, mode).unwrap();
assert!(locker.is_locked(mode));
});
}
fn a_b_nested(a: CacheLockMode, b: CacheLockMode) {
let gctx = GlobalContextBuilder::new().build();
let locker = CacheLocker::new();
let lock1 = locker.lock(&gctx, a).unwrap();
assert!(locker.is_locked(a));
let lock2 = locker.lock(&gctx, b).unwrap();
assert!(locker.is_locked(b));
drop(lock2);
drop(lock1);
verify_lock_is_ok(CacheLockMode::Shared);
verify_lock_is_ok(CacheLockMode::DownloadExclusive);
verify_lock_is_ok(CacheLockMode::MutateExclusive);
}
fn a_then_b_separate_not_blocked(a: CacheLockMode, b: CacheLockMode, verify: CacheLockMode) {
let gctx = GlobalContextBuilder::new().build();
let locker1 = CacheLocker::new();
let lock1 = locker1.lock(&gctx, a).unwrap();
assert!(locker1.is_locked(a));
let locker2 = CacheLocker::new();
let lock2 = locker2.lock(&gctx, b).unwrap();
assert!(locker2.is_locked(b));
let thread = verify_lock_would_block(verify);
drop(lock1);
drop(lock2);
thread_wait_timeout::<()>(100, thread);
}
fn a_then_b_separate_blocked(a: CacheLockMode, b: CacheLockMode) {
let gctx = GlobalContextBuilder::new().build();
let locker = CacheLocker::new();
let lock = locker.lock(&gctx, a).unwrap();
assert!(locker.is_locked(a));
let thread = verify_lock_would_block(b);
drop(lock);
thread_wait_timeout::<()>(100, thread);
}
#[must_use]
fn verify_lock_would_block(mode: CacheLockMode) -> JoinHandle<()> {
let root = paths::root();
let thread = std::thread::spawn(move || {
let gctx = GlobalContextBuilder::new().root(root).build();
let locker2 = CacheLocker::new();
let lock2 = locker2.lock(&gctx, mode).unwrap();
assert!(locker2.is_locked(mode));
drop(lock2);
});
retry(100, || {
if let Ok(s) = std::fs::read_to_string(paths::root().join("shell.out")) {
if s.trim().starts_with("Blocking waiting for file lock on") {
return Some(());
} else {
eprintln!("unexpected output: {s}");
}
}
None
});
thread
}
#[test]
fn new_is_unlocked() {
let locker = CacheLocker::new();
assert!(!locker.is_locked(CacheLockMode::Shared));
assert!(!locker.is_locked(CacheLockMode::DownloadExclusive));
assert!(!locker.is_locked(CacheLockMode::MutateExclusive));
}
#[cargo_test]
fn multiple_shared() {
a_b_nested(CacheLockMode::Shared, CacheLockMode::Shared);
}
#[cfg_attr(
target_os = "aix",
ignore = "Test fails on AIX due to unsupported flock behaviour"
)]
#[cargo_test]
fn multiple_shared_separate() {
a_then_b_separate_not_blocked(
CacheLockMode::Shared,
CacheLockMode::Shared,
CacheLockMode::MutateExclusive,
);
}
#[cargo_test]
fn multiple_download() {
a_b_nested(
CacheLockMode::DownloadExclusive,
CacheLockMode::DownloadExclusive,
);
}
#[cargo_test]
fn multiple_mutate() {
a_b_nested(
CacheLockMode::MutateExclusive,
CacheLockMode::MutateExclusive,
);
}
#[cargo_test]
#[should_panic(expected = "lock is not allowed")]
fn download_then_shared() {
a_b_nested(CacheLockMode::DownloadExclusive, CacheLockMode::Shared);
}
#[cargo_test]
#[should_panic(expected = "lock upgrade from shared to exclusive not supported")]
fn shared_then_mutate() {
a_b_nested(CacheLockMode::Shared, CacheLockMode::MutateExclusive);
}
#[cargo_test]
fn shared_then_download() {
a_b_nested(CacheLockMode::Shared, CacheLockMode::DownloadExclusive);
verify_lock_is_ok(CacheLockMode::DownloadExclusive);
verify_lock_is_ok(CacheLockMode::MutateExclusive);
}
#[cargo_test]
fn mutate_then_shared() {
a_b_nested(CacheLockMode::MutateExclusive, CacheLockMode::Shared);
verify_lock_is_ok(CacheLockMode::MutateExclusive);
}
#[cargo_test]
fn download_then_mutate() {
a_b_nested(
CacheLockMode::DownloadExclusive,
CacheLockMode::MutateExclusive,
);
verify_lock_is_ok(CacheLockMode::DownloadExclusive);
verify_lock_is_ok(CacheLockMode::MutateExclusive);
}
#[cargo_test]
fn mutate_then_download() {
a_b_nested(
CacheLockMode::MutateExclusive,
CacheLockMode::DownloadExclusive,
);
verify_lock_is_ok(CacheLockMode::MutateExclusive);
verify_lock_is_ok(CacheLockMode::DownloadExclusive);
}
#[cargo_test]
fn readonly() {
let cargo_home = paths::home().join(".cargo");
std::fs::create_dir_all(&cargo_home).unwrap();
let mut perms = std::fs::metadata(&cargo_home).unwrap().permissions();
perms.set_readonly(true);
std::fs::set_permissions(&cargo_home, perms).unwrap();
let gctx = GlobalContextBuilder::new().build();
let locker = CacheLocker::new();
for mode in [
CacheLockMode::Shared,
CacheLockMode::DownloadExclusive,
CacheLockMode::MutateExclusive,
] {
let _lock1 = locker.lock(&gctx, mode).unwrap();
let _lock2 = locker.lock(&gctx, mode).unwrap();
}
}
#[cfg_attr(
target_os = "aix",
ignore = "Test fails on AIX due to unsupported flock behaviour"
)]
#[cargo_test]
fn download_then_shared_separate() {
a_then_b_separate_not_blocked(
CacheLockMode::DownloadExclusive,
CacheLockMode::Shared,
CacheLockMode::MutateExclusive,
);
}
#[cfg_attr(
target_os = "aix",
ignore = "Test fails on AIX due to unsupported flock behaviour"
)]
#[cargo_test]
fn shared_then_download_separate() {
a_then_b_separate_not_blocked(
CacheLockMode::Shared,
CacheLockMode::DownloadExclusive,
CacheLockMode::MutateExclusive,
);
}
#[cfg_attr(
target_os = "aix",
ignore = "Test fails on AIX due to unsupported flock behaviour"
)]
#[cargo_test]
fn multiple_download_separate() {
a_then_b_separate_blocked(
CacheLockMode::DownloadExclusive,
CacheLockMode::DownloadExclusive,
);
}
#[cfg_attr(
target_os = "aix",
ignore = "Test fails on AIX due to unsupported flock behaviour"
)]
#[cargo_test]
fn multiple_mutate_separate() {
a_then_b_separate_blocked(
CacheLockMode::MutateExclusive,
CacheLockMode::MutateExclusive,
);
}
#[cfg_attr(
target_os = "aix",
ignore = "Test fails on AIX due to unsupported flock behaviour"
)]
#[cargo_test]
fn shared_then_mutate_separate() {
a_then_b_separate_blocked(CacheLockMode::Shared, CacheLockMode::MutateExclusive);
}
#[cfg_attr(
target_os = "aix",
ignore = "Test fails on AIX due to unsupported flock behaviour"
)]
#[cargo_test]
fn download_then_mutate_separate() {
a_then_b_separate_blocked(
CacheLockMode::DownloadExclusive,
CacheLockMode::MutateExclusive,
);
}
#[cfg_attr(
target_os = "aix",
ignore = "Test fails on AIX due to unsupported flock behaviour"
)]
#[cargo_test]
fn mutate_then_download_separate() {
a_then_b_separate_blocked(
CacheLockMode::MutateExclusive,
CacheLockMode::DownloadExclusive,
);
}
#[cfg_attr(
target_os = "aix",
ignore = "Test fails on AIX due to unsupported flock behaviour"
)]
#[cargo_test]
fn mutate_then_shared_separate() {
a_then_b_separate_blocked(CacheLockMode::MutateExclusive, CacheLockMode::Shared);
}
#[cargo_test(ignore_windows = "no method to prevent creating or locking a file")]
fn mutate_err_is_atomic() {
let gctx = GlobalContextBuilder::new().build();
let locker = CacheLocker::new();
let cargo_home = gctx.home().as_path_unlocked();
let cache_path = cargo_home.join(".package-cache");
cache_path.mkdir_p();
match locker.lock(&gctx, CacheLockMode::MutateExclusive) {
Ok(_) => panic!("did not expect lock to succeed"),
Err(e) => {
let msg = format!("{e:?}");
assert!(msg.contains("failed to open:"), "{msg}");
}
}
assert!(!locker.is_locked(CacheLockMode::MutateExclusive));
assert!(!locker.is_locked(CacheLockMode::DownloadExclusive));
assert!(!locker.is_locked(CacheLockMode::Shared));
cache_path.rm_rf();
verify_lock_is_ok(CacheLockMode::DownloadExclusive);
verify_lock_is_ok(CacheLockMode::Shared);
verify_lock_is_ok(CacheLockMode::MutateExclusive);
}