Skip to content

Commit 6f6d7c8

Browse files
committed
Protect against concurrent access to Cargo.lock
CI tests seen to be spurious failing on OSX due to this, I believe it's because one process is concurrently writing out Cargo.lock while the other is trying to read it in, so one of them gets a corrupt version. This would ideally be fixed from `pkg.root()` returning a `Filesystem`, but that was unfortunately such an invasive change that it seemed untenable. Additionally we generally don't write files into the source tree, so if this is the only instance it's perhaps not so bad trying to be robust in to the future.
1 parent a9fd1c2 commit 6f6d7c8

File tree

7 files changed

+49
-40
lines changed

7 files changed

+49
-40
lines changed

src/cargo/ops/cargo_generate_lockfile.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub fn generate_lockfile(manifest_path: &Path, config: &Config)
2323
let resolve = try!(ops::resolve_with_previous(&mut registry, &package,
2424
Method::Everything,
2525
None, None));
26-
try!(ops::write_pkg_lockfile(&package, &resolve));
26+
try!(ops::write_pkg_lockfile(&package, &resolve, config));
2727
Ok(())
2828
}
2929

@@ -105,7 +105,7 @@ pub fn update_lockfile(manifest_path: &Path,
105105
}
106106
}
107107

108-
try!(ops::write_pkg_lockfile(&package, &resolve));
108+
try!(ops::write_pkg_lockfile(&package, &resolve, opts.config));
109109
return Ok(());
110110

111111
fn fill_with_deps<'a>(resolve: &'a Resolve, dep: &'a PackageId,

src/cargo/ops/cargo_pkgid.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ pub fn pkgid(manifest_path: &Path,
88
spec: Option<&str>,
99
config: &Config) -> CargoResult<PackageIdSpec> {
1010
let package = try!(Package::for_path(manifest_path, config));
11-
12-
let lockfile = package.root().join("Cargo.lock");
13-
let resolve = match try!(ops::load_lockfile(&lockfile, &package, config)) {
11+
let resolve = match try!(ops::load_pkg_lockfile(&package, config)) {
1412
Some(resolve) => resolve,
1513
None => bail!("a Cargo.lock must exist for this command"),
1614
};

src/cargo/ops/lockfile.rs

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,39 @@
1-
use std::fs::File;
21
use std::io::prelude::*;
3-
use std::path::Path;
42

53
use rustc_serialize::{Encodable, Decodable};
64
use toml::{self, Encoder, Value};
75

86
use core::{Resolve, resolver, Package};
9-
use util::{CargoResult, ChainError, human, paths, Config};
7+
use util::{CargoResult, ChainError, human, Config, Filesystem};
108
use util::toml as cargo_toml;
119

1210
pub fn load_pkg_lockfile(pkg: &Package, config: &Config)
1311
-> CargoResult<Option<Resolve>> {
14-
let lockfile = pkg.root().join("Cargo.lock");
15-
load_lockfile(&lockfile, pkg, config).chain_error(|| {
16-
human(format!("failed to parse lock file at: {}", lockfile.display()))
17-
})
18-
}
12+
if !pkg.root().join("Cargo.lock").exists() {
13+
return Ok(None)
14+
}
1915

20-
pub fn load_lockfile(path: &Path, pkg: &Package, config: &Config)
21-
-> CargoResult<Option<Resolve>> {
22-
// If there is no lockfile, return none.
23-
let mut f = match File::open(path) {
24-
Ok(f) => f,
25-
Err(_) => return Ok(None)
26-
};
16+
let root = Filesystem::new(pkg.root().to_path_buf());
17+
let mut f = try!(root.open_ro("Cargo.lock", config, "Cargo.lock file"));
2718

2819
let mut s = String::new();
29-
try!(f.read_to_string(&mut s));
30-
31-
let table = toml::Value::Table(try!(cargo_toml::parse(&s, path)));
32-
let mut d = toml::Decoder::new(table);
33-
let v: resolver::EncodableResolve = try!(Decodable::decode(&mut d));
34-
Ok(Some(try!(v.to_resolve(pkg, config))))
35-
}
36-
37-
pub fn write_pkg_lockfile(pkg: &Package, resolve: &Resolve) -> CargoResult<()> {
38-
let loc = pkg.root().join("Cargo.lock");
39-
write_lockfile(&loc, resolve)
20+
try!(f.read_to_string(&mut s).chain_error(|| {
21+
human(format!("failed to read file: {}", f.path().display()))
22+
}));
23+
24+
(|| {
25+
let table = toml::Value::Table(try!(cargo_toml::parse(&s, f.path())));
26+
let mut d = toml::Decoder::new(table);
27+
let v: resolver::EncodableResolve = try!(Decodable::decode(&mut d));
28+
Ok(Some(try!(v.to_resolve(pkg, config))))
29+
}).chain_error(|| {
30+
human(format!("failed to parse lock file at: {}", f.path().display()))
31+
})
4032
}
4133

42-
pub fn write_lockfile(dst: &Path, resolve: &Resolve) -> CargoResult<()> {
34+
pub fn write_pkg_lockfile(pkg: &Package,
35+
resolve: &Resolve,
36+
config: &Config) -> CargoResult<()> {
4337
let mut e = Encoder::new();
4438
resolve.encode(&mut e).unwrap();
4539

@@ -69,20 +63,36 @@ pub fn write_lockfile(dst: &Path, resolve: &Resolve) -> CargoResult<()> {
6963
None => {}
7064
}
7165

66+
let root = Filesystem::new(pkg.root().to_path_buf());
67+
7268
// Load the original lockfile if it exists.
73-
if let Ok(orig) = paths::read(dst) {
69+
//
70+
// If the lockfile contents haven't changed so don't rewrite it. This is
71+
// helpful on read-only filesystems.
72+
let orig = root.open_ro("Cargo.lock", config, "Cargo.lock file");
73+
let orig = orig.and_then(|mut f| {
74+
let mut s = String::new();
75+
try!(f.read_to_string(&mut s));
76+
Ok(s)
77+
});
78+
if let Ok(orig) = orig {
7479
if has_crlf_line_endings(&orig) {
7580
out = out.replace("\n", "\r\n");
7681
}
7782
if out == orig {
78-
// The lockfile contents haven't changed so don't rewrite it.
79-
// This is helpful on read-only filesystems.
8083
return Ok(())
8184
}
8285
}
8386

84-
try!(paths::write(dst, out.as_bytes()));
85-
Ok(())
87+
// Ok, if that didn't work just write it out
88+
root.open_rw("Cargo.lock", config, "Cargo.lock file").and_then(|mut f| {
89+
try!(f.file().set_len(0));
90+
try!(f.write_all(out.as_bytes()));
91+
Ok(())
92+
}).chain_error(|| {
93+
human(format!("failed to write {}",
94+
pkg.root().join("Cargo.lock").display()))
95+
})
8696
}
8797

8898
fn has_crlf_line_endings(s: &str) -> bool {

src/cargo/ops/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ pub use self::cargo_doc::{doc, DocOptions};
1313
pub use self::cargo_generate_lockfile::{generate_lockfile};
1414
pub use self::cargo_generate_lockfile::{update_lockfile};
1515
pub use self::cargo_generate_lockfile::UpdateOptions;
16-
pub use self::lockfile::{load_lockfile, load_pkg_lockfile};
17-
pub use self::lockfile::{write_lockfile, write_pkg_lockfile};
16+
pub use self::lockfile::{load_pkg_lockfile, write_pkg_lockfile};
1817
pub use self::cargo_test::{run_tests, run_benches, TestOptions};
1918
pub use self::cargo_package::package;
2019
pub use self::registry::{publish, registry_configuration, RegistryConfig};

src/cargo/ops/resolve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub fn resolve_pkg(registry: &mut PackageRegistry,
2020
Method::Everything,
2121
prev.as_ref(), None));
2222
if package.package_id().source_id().is_path() {
23-
try!(ops::write_pkg_lockfile(package, &resolve));
23+
try!(ops::write_pkg_lockfile(package, &resolve, config));
2424
}
2525
Ok(resolve)
2626
}

tests/test_cargo_generate_lockfile.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ test!(adding_and_removing_packages {
6262
assert!(lock2 != lock3);
6363

6464
// remove the dep
65+
println!("lock4");
6566
File::create(&toml).unwrap().write_all(br#"
6667
[package]
6768
name = "foo"

tests/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ fn process<T: AsRef<OsStr>>(t: T) -> cargo::util::ProcessBuilder {
9393
p.cwd(&support::paths::root())
9494
.env("HOME", &support::paths::home())
9595
.env_remove("CARGO_HOME")
96+
.env_remove("RUSTC")
9697
.env_remove("XDG_CONFIG_HOME") // see #2345
9798
.env("GIT_CONFIG_NOSYSTEM", "1") // keep trying to sandbox ourselves
9899
.env_remove("CARGO_TARGET_DIR") // we assume 'target'

0 commit comments

Comments
 (0)