treestate: add tests about concurrent writes

Summary:
Treestate can be corrupted by concurrent writes.
This is used to verify the fix in a later diff.

Reviewed By: zzl0

Differential Revision: D42492671

fbshipit-source-id: b5ede9f66e85cef4b5bfd7ae1732ab04f258b13b
This commit is contained in:
Jun Wu 2023-01-17 10:54:03 -08:00 committed by Facebook GitHub Bot
parent 20253392b0
commit 611dcf2ad7
2 changed files with 67 additions and 0 deletions

View File

@ -52,6 +52,7 @@ bitflags! {
/// | removed | either one is yes | no | ? |
/// | untracked | no | no | no | no |
/// | ignored | no | no | no | yes |
#[cfg_attr(test, derive(Default))]
pub struct StateFlags: u16 {
/// Exist in the first working parent.
const EXIST_P1 = 1;
@ -91,6 +92,7 @@ impl StateFlags {
/// Unlike V1, the `state` field is no longer a char defined by Mercurial,
/// but a bitflag. It also has a `copied` field.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(test, derive(Default))]
pub struct FileStateV2 {
/// Mode (permissions) mask for the file.
pub mode: u32,

View File

@ -903,4 +903,69 @@ mod tests {
[p1, p3].to_vec()
);
}
#[test]
#[should_panic] // BUG
fn test_concurrent_writes() {
check_concurrent_writes(&[b"a"], &[b"b"]);
check_concurrent_writes(&[b"a/1"], &[b"a/2"]);
let paths1 = SAMPLE_PATHS.into_iter().take(10).collect::<Vec<_>>();
let paths2 = SAMPLE_PATHS.into_iter().rev().take(1).collect::<Vec<_>>();
check_concurrent_writes(&paths1, &paths2);
let paths2 = SAMPLE_PATHS.into_iter().rev().take(10).collect::<Vec<_>>();
check_concurrent_writes(&paths1, &paths2);
}
// Test appending paths1, and paths2 "concurrently".
// Paths should not overlap.
fn check_concurrent_writes(paths1: &[&[u8]], paths2: &[&[u8]]) {
let dir = tempdir().unwrap();
let dir_path = dir.path();
// Prepare initial state.
let (path, root_id) = {
let mut state = new_treestate(dir_path);
let root_id = state.flush().unwrap();
(dir_path.join(state.file_name().unwrap()), root_id)
};
// Concurrent writes.
let mut state1 = TreeState::open(&path, root_id, true).unwrap();
let mut state2 = TreeState::open(&path, root_id, true).unwrap();
let file_state1: FileStateV2 = Default::default();
let file_state2 = FileStateV2 {
size: file_state1.size + 1,
..file_state1.clone()
};
for p in paths1 {
state1.insert(p, &file_state1).unwrap();
}
for p in paths2 {
state2.insert(p, &file_state2).unwrap();
}
let root_id1 = state1.flush().unwrap();
// Panic (debug build) at
// debug_assert!(self.position == file.seek(SeekFrom::End(0))?);
// in filestore.rs
//
// Might error out with (release build): "invalid store id: ..."
let root_id2 = state2.flush().unwrap();
// Check that things can be read properly (aka. no "invalid store id: ..." errors),
// and are written properly (file_state1 and file_state2).
let mut state1 = TreeState::open(&path, root_id1, true).unwrap();
let mut state2 = TreeState::open(&path, root_id2, true).unwrap();
for p in paths1 {
let got = state1.get(p).unwrap().unwrap();
assert_eq!(got, &file_state1);
}
for p in paths2 {
let got = state2.get(p).unwrap().unwrap();
assert_eq!(got, &file_state2);
}
}
}