This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-lfs / tests / race.rs
4.5 kB 127 lines
1use std::sync::atomic::{AtomicBool, Ordering}; 2use std::time::{Duration, SystemTime}; 3 4mod common; 5 6use common::{oid_of, repo}; 7use knot_lfs::{ClaimedSize, DiskStore, LfsOid, LfsSize, LfsStore, LfsStorePath, Reclaimed}; 8 9const ROUNDS: usize = 400; 10const GRACE: Duration = Duration::from_secs(14 * 86_400); 11const BACKDATE: Duration = Duration::from_secs(60 * 86_400); 12 13#[derive(Clone, Copy)] 14enum Bias { 15 TouchFirst, 16 SweepFirst, 17 Simultaneous, 18} 19 20impl Bias { 21 fn of_round(round: usize) -> Self { 22 match round % 3 { 23 0 => Self::TouchFirst, 24 1 => Self::SweepFirst, 25 _ => Self::Simultaneous, 26 } 27 } 28} 29 30fn seed_expired(store: &DiskStore, round: usize) -> LfsOid { 31 let body = format!("past-grace media, round {round}").into_bytes(); 32 let oid = oid_of(&body); 33 store 34 .put( 35 &repo(), 36 &oid, 37 ClaimedSize::new(body.len() as u64), 38 &mut &body[..], 39 ) 40 .unwrap(); 41 let path = store.object_file(&repo(), &oid).unwrap().unwrap().1; 42 std::fs::OpenOptions::new() 43 .write(true) 44 .open(path) 45 .unwrap() 46 .set_modified(SystemTime::now() - BACKDATE) 47 .unwrap(); 48 oid 49} 50 51#[test] 52fn a_mention_concurrent_with_the_sweep_never_yields_a_dangling_pointer() { 53 let dir = tempfile::tempdir().unwrap(); 54 let store = DiskStore::open(LfsStorePath::new(dir.path())).unwrap(); 55 56 let outcomes: Vec<(Option<LfsSize>, Reclaimed)> = (0..ROUNDS) 57 .map(|round| { 58 let oid = seed_expired(&store, round); 59 let bias = Bias::of_round(round); 60 let go = AtomicBool::new(false); 61 let touch_started = AtomicBool::new(false); 62 let sweep_started = AtomicBool::new(false); 63 let (vouched, reclaimed) = std::thread::scope(|scope| { 64 let toucher = scope.spawn(|| { 65 while !go.load(Ordering::Acquire) { 66 std::hint::spin_loop(); 67 } 68 if matches!(bias, Bias::SweepFirst) { 69 while !sweep_started.load(Ordering::Acquire) { 70 std::hint::spin_loop(); 71 } 72 } 73 touch_started.store(true, Ordering::Release); 74 store.touch(&repo(), &oid).unwrap() 75 }); 76 let sweeper = scope.spawn(|| { 77 while !go.load(Ordering::Acquire) { 78 std::hint::spin_loop(); 79 } 80 if matches!(bias, Bias::TouchFirst) { 81 while !touch_started.load(Ordering::Acquire) { 82 std::hint::spin_loop(); 83 } 84 } 85 sweep_started.store(true, Ordering::Release); 86 store 87 .collect_expired(&repo(), &oid, GRACE, SystemTime::now()) 88 .unwrap() 89 }); 90 go.store(true, Ordering::Release); 91 (toucher.join().unwrap(), sweeper.join().unwrap()) 92 }); 93 94 if let Some(size) = vouched { 95 assert!( 96 matches!(reclaimed, Reclaimed::Spared), 97 "round {round}: the sweep deleted an object the server just reported stored" 98 ); 99 assert_eq!( 100 store.probe(&repo(), &oid).unwrap(), 101 Some(size), 102 "round {round}: an object reported stored must remain readable" 103 ); 104 } else { 105 assert!( 106 matches!(reclaimed, Reclaimed::Swept(_)), 107 "round {round}: a touch that reports missing means the sweeper unlinked first" 108 ); 109 assert_eq!( 110 store.probe(&repo(), &oid).unwrap(), 111 None, 112 "round {round}: a swept object reports missing" 113 ); 114 } 115 (vouched, reclaimed) 116 }) 117 .collect(); 118 119 assert!( 120 outcomes.iter().any(|(vouched, _)| vouched.is_some()), 121 "some round must complete the touch first, or the interleaving never varied" 122 ); 123 assert!( 124 outcomes.iter().any(|(vouched, _)| vouched.is_none()), 125 "some round must complete the sweep first, or the interleaving never varied" 126 ); 127}