From 2aa9d4d3a923954e50a9944892cb6d13fa6ff7a4 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Mon, 9 Dec 2024 00:14:44 +1100 Subject: [PATCH] tests: ensure Root::mkdir_all races don't return EEXIST Signed-off-by: Aleksa Sarai --- src/tests/test_root_ops.rs | 97 +++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/src/tests/test_root_ops.rs b/src/tests/test_root_ops.rs index e90ad194..b14533cf 100644 --- a/src/tests/test_root_ops.rs +++ b/src/tests/test_root_ops.rs @@ -259,15 +259,66 @@ macro_rules! root_op_tests { } }; + + ($(#[cfg($ignore_meta:meta)])* @impl mkdir_all_racing [#$num_threads:expr] $test_name:ident ($path:expr, $mode:expr) => $expected_result:expr) => { + paste::paste! { + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + fn [<$test_name _ $num_threads threads>](root) { + utils::check_root_mkdir_all_racing($num_threads, &root, $path, Permissions::from_mode($mode), $expected_result) + } + } + } + }; + + ($(#[cfg($ignore_meta:meta)])* @impl mkdir_all_racing $test_name:ident ( $($args:tt)* ) => $expected_result:expr) => { + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + @impl mkdir_all_racing [#2] $test_name( $($args)* ) => $expected_result + } + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + @impl mkdir_all_racing [#4] $test_name( $($args)* ) => $expected_result + } + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + @impl mkdir_all_racing [#8] $test_name( $($args)* ) => $expected_result + } + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + @impl mkdir_all_racing [#16] $test_name( $($args)* ) => $expected_result + } + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + @impl mkdir_all_racing [#32] $test_name( $($args)* ) => $expected_result + } + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + @impl mkdir_all_racing [#64] $test_name( $($args)* ) => $expected_result + } + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + @impl mkdir_all_racing [#128] $test_name( $($args)* ) => $expected_result + } + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + @impl mkdir_all_racing [#256] $test_name( $($args)* ) => $expected_result + } + root_op_tests! { + $(#[cfg_attr(not($ignore_meta), ignore)])* + @impl mkdir_all_racing [#512] $test_name( $($args)* ) => $expected_result + } + }; + // root_tests!{ // ... // } - ($($(#[cfg($ignore_meta:meta)])* $test_name:ident: $file_type:ident ( $($args:expr),* ) => $expected_result:expr );+ $(;)?) => { + ($($(#[cfg($ignore_meta:meta)])* $test_name:ident: $file_type:ident ( $($args:tt)* ) => $expected_result:expr );+ $(;)?) => { paste::paste! { $( root_op_tests!{ $(#[cfg($ignore_meta)])* - @impl $file_type [<$file_type _ $test_name>]( $($args),* ) => $expected_result + @impl $file_type [<$file_type _ $test_name>]( $($args)* ) => $expected_result } )* } @@ -437,6 +488,10 @@ root_op_tests! { setgid_selfdir: mkdir_all("setgid-self/a/b/c/d", 0o711) => Ok(()); #[cfg(feature = "_test_as_root")] setgid_otherdir: mkdir_all("setgid-other/a/b/c/d", 0o711) => Ok(()); + + // Check that multiple mkdir_alls racing against each other will not result + // in a spurious error. + plain: mkdir_all_racing("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z", 0o711) => Ok(()); } mod utils { @@ -460,6 +515,8 @@ mod utils { io::{AsFd, OwnedFd}, }, path::{Path, PathBuf}, + sync::{Arc, Barrier}, + thread, }; use anyhow::{Context, Error}; @@ -920,4 +977,40 @@ mod utils { Ok(()) } + + pub(super) fn check_root_mkdir_all_racing>( + num_threads: usize, + root: R, + unsafe_path: P, + perm: Permissions, + expected_result: Result<(), ErrorKind>, + ) -> Result<(), Error> { + let root = &root; + let unsafe_path = unsafe_path.as_ref(); + + // Do lots of runs to try to catch any possible races. + let num_retries = if num_threads > 32 { + 2_000 / (num_threads >> 5) + } else { + 20_000 / num_threads + }; + for _ in 0..num_retries { + thread::scope(|s| { + let start_barrier = Arc::new(Barrier::new(num_threads)); + for _ in 0..num_threads { + let barrier = Arc::clone(&start_barrier); + let perm = perm.clone(); + s.spawn(move || { + barrier.wait(); + let res = root + .mkdir_all(unsafe_path, &perm) + .with_wrap(|| format!("mkdir_all {unsafe_path:?}")); + tests_common::check_err(&res, &expected_result).expect("unexpected result"); + }); + } + }); + } + + Ok(()) + } }