Last active
February 16, 2024 23:26
-
-
Save abonander/d083971d90e197c31711e06acf021461 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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