Skip to content

Commit bec648d

Browse files
committed
fix: multi-process safe parallel filesystem capabilities probing (#1373)
This is achieved by making filenames unique so they won't clash.
1 parent 00a1c47 commit bec648d

File tree

4 files changed

+49
-14
lines changed

4 files changed

+49
-14
lines changed

Cargo.lock

+12-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-fs/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,9 @@ gix-features = { version = "^0.38.1", path = "../gix-features", features = ["fs-
2121
gix-utils = { version = "^0.1.12", path = "../gix-utils" }
2222
serde = { version = "1.0.114", optional = true, default-features = false, features = ["std", "derive"] }
2323

24+
# For `Capabilities` to assure parallel operation works.
25+
fastrand = { version = "2.1.0", default-features = false, features = ["std"] }
26+
2427
[dev-dependencies]
2528
tempfile = "3.5.0"
29+
crossbeam-channel = "0.5.0"

gix-fs/src/capabilities.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ impl Capabilities {
6060
use std::os::unix::fs::{MetadataExt, OpenOptionsExt};
6161

6262
// test it exactly as we typically create executable files, not using chmod.
63-
let test_path = root.join("_test_executable_bit");
63+
let rand = fastrand::usize(..);
64+
let test_path = root.join(format!("_test_executable_bit{rand}"));
6465
let res = std::fs::OpenOptions::new()
6566
.create_new(true)
6667
.write(true)
@@ -87,8 +88,9 @@ impl Capabilities {
8788
}
8889

8990
fn probe_precompose_unicode(root: &Path) -> std::io::Result<bool> {
90-
let precomposed = "ä";
91-
let decomposed = "a\u{308}";
91+
let rand = fastrand::usize(..);
92+
let precomposed = format!("ä{rand}");
93+
let decomposed = format!("a\u{308}{rand}");
9294

9395
let precomposed = root.join(precomposed);
9496
std::fs::OpenOptions::new()
@@ -101,7 +103,8 @@ impl Capabilities {
101103
}
102104

103105
fn probe_symlink(root: &Path) -> std::io::Result<bool> {
104-
let link_path = root.join("__file_link");
106+
let rand = fastrand::usize(..);
107+
let link_path = root.join(format!("__file_link{rand}"));
105108
if crate::symlink::create("dangling".as_ref(), &link_path).is_err() {
106109
return Ok(false);
107110
}

gix-fs/tests/capabilities/mod.rs

+26
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,29 @@ fn probe() {
2020
assert!(caps.executable_bit, "Unix should always honor executable bits");
2121
}
2222
}
23+
24+
#[test]
25+
fn parallel_probe() {
26+
let dir = tempfile::tempdir().unwrap();
27+
std::fs::File::create(dir.path().join("config")).unwrap();
28+
let baseline = gix_fs::Capabilities::probe(dir.path());
29+
30+
let (tx, rx) = crossbeam_channel::unbounded::<()>();
31+
let threads: Vec<_> = (0..10)
32+
.map(|_id| {
33+
std::thread::spawn({
34+
let dir = dir.path().to_owned();
35+
let rx = rx.clone();
36+
move || {
37+
for _ in rx {}
38+
let actual = gix_fs::Capabilities::probe(&dir);
39+
assert_eq!(actual, baseline);
40+
}
41+
})
42+
})
43+
.collect();
44+
drop((rx, tx));
45+
for thread in threads {
46+
thread.join().expect("no panic");
47+
}
48+
}

0 commit comments

Comments
 (0)