Skip to content

Instantly share code, notes, and snippets.

@abonander
Last active February 16, 2024 23:26
Show Gist options
  • Save abonander/d083971d90e197c31711e06acf021461 to your computer and use it in GitHub Desktop.
Save abonander/d083971d90e197c31711e06acf021461 to your computer and use it in GitHub Desktop.
use std::{fs, io};
use std::io::Write;
use std::path::Path;
use std::sync::{Arc, Barrier};
use std::time::Duration;
const PATH: &str = "test-concurrent-deletion.txt";
fn main() {
// Generate a Cartesian product of `[0, 1, 2, 3]` with itself.
for (wait_a, wait_b) in (0 .. 4).flat_map(|a| (0..4).map(move |b| (a, b))) {
let barrier = Arc::new(Barrier::new(2));
println!("Test: {wait_a}, {wait_b}");
let mut thread_a = Some(std::thread::spawn({
let barrier = barrier.clone();
move || {
try_write_file(&barrier, wait_a);
}
}));
let mut thread_b = Some(std::thread::spawn({
let barrier = barrier.clone();
move || {
try_write_file(&barrier, wait_b);
}
}));
// Concurrently wait for either `thread_a` or `thread_b` to exit.
while thread_a.is_some() || thread_b.is_some() {
std::thread::sleep(Duration::from_millis(50));
if let Some(t) = thread_a.take() {
if t.is_finished() {
if let Err(e) = t.join() {
println!("thread_a panicked");
std::panic::resume_unwind(e);
}
println!("thread_a exited successfully");
} else {
thread_a = Some(t);
}
}
if let Some(t) = thread_b.take() {
if t.is_finished() {
if let Err(e) = t.join() {
println!("thread_b panicked");
std::panic::resume_unwind(e);
}
println!("thread_b exited successfully");
} else {
thread_b = Some(t);
}
}
}
assert!(Path::new(PATH).exists(), "{PATH} does not exist after test");
println!();
}
}
fn try_write_file(barrier: &Barrier, wait_point: usize) {
if wait_point == 0 { barrier.wait(); }
match fs::remove_file(PATH) {
Ok(()) => (),
Err(e) if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied) => {},
Err(e) => panic!("Error deleting file: {e:?}"),
}
if wait_point == 1 { barrier.wait(); }
let res = fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(PATH);
let mut file = match res {
Ok(file) => file,
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
// The other thread will deadlock if we exit early without hitting these waits.
if wait_point == 2 { barrier.wait(); }
if wait_point == 3 { barrier.wait(); }
return;
},
Err(e) => panic!("Error opening file: {e:?}"),
};
if wait_point == 2 { barrier.wait(); }
writeln!(file, "File was successfully written!").expect("failed to write to file");
if wait_point == 3 { barrier.wait(); }
// Fails if the file was deleted concurrently.
// file.sync_all().expect("failed to sync_all to file");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment