ecr_store/index/
freshness.rs1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
8use std::sync::Mutex;
9use std::time::{Duration, Instant};
10
11pub const REVALIDATE_AFTER: Duration = Duration::from_secs(2);
16
17pub struct Freshness {
18 window: Duration,
19 verified: Mutex<Option<Instant>>,
21 building: AtomicBool,
22 writes: AtomicU64,
25}
26
27impl Default for Freshness {
28 fn default() -> Self {
29 Self::new(REVALIDATE_AFTER)
30 }
31}
32
33impl Freshness {
34 pub fn new(window: Duration) -> Self {
35 Self {
36 window,
37 verified: Mutex::new(None),
38 building: AtomicBool::new(false),
39 writes: AtomicU64::new(0),
40 }
41 }
42
43 pub fn building(&self) -> bool {
50 self.building.load(Ordering::SeqCst)
51 }
52
53 pub fn begin_build(&self) {
54 self.building.store(true, Ordering::SeqCst);
55 }
56
57 pub fn end_build(&self) {
58 self.building.store(false, Ordering::SeqCst);
59 }
60
61 pub fn fresh(&self) -> bool {
63 self.verified
64 .lock()
65 .ok()
66 .and_then(|at| *at)
67 .is_some_and(|at| at.elapsed() < self.window)
68 }
69
70 pub fn generation(&self) -> u64 {
73 self.writes.load(Ordering::SeqCst)
74 }
75
76 pub fn vouch(&self, generation: u64) {
85 if self.generation() != generation {
86 return;
87 }
88 if let Ok(mut at) = self.verified.lock() {
89 *at = Some(Instant::now());
90 }
91 }
92
93 pub fn note_write(&self) {
96 self.writes.fetch_add(1, Ordering::SeqCst);
97 if let Ok(mut at) = self.verified.lock() {
98 *at = None;
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 fn freshness() -> Freshness {
108 Freshness::new(Duration::from_secs(60))
109 }
110
111 #[test]
112 fn nothing_is_trusted_before_a_refresh_has_vouched() {
113 assert!(!freshness().fresh());
114 }
115
116 #[test]
117 fn a_refresh_that_ran_alone_is_trusted() {
118 let f = freshness();
119 let generation = f.generation();
120 f.vouch(generation);
121
122 assert!(f.fresh());
123 }
124
125 #[test]
126 fn a_write_during_a_refresh_stops_it_vouching() {
127 let f = freshness();
128 let generation = f.generation();
129
130 f.note_write();
132 f.vouch(generation);
133
134 assert!(!f.fresh(), "the refresh vouched for mail it had not read");
135 }
136
137 #[test]
138 fn a_write_after_a_refresh_retracts_the_vouching() {
139 let f = freshness();
140 f.vouch(f.generation());
141 f.note_write();
142
143 assert!(!f.fresh());
144 }
145
146 #[test]
147 fn a_later_refresh_can_vouch_again_after_a_write() {
148 let f = freshness();
149 f.note_write();
150 f.vouch(f.generation());
151
152 assert!(f.fresh());
153 }
154
155 #[test]
156 fn the_window_expires() {
157 let f = Freshness::new(Duration::ZERO);
158 f.vouch(f.generation());
159
160 assert!(!f.fresh());
161 }
162
163 #[test]
164 fn building_is_reported_while_it_lasts() {
165 let f = freshness();
166 assert!(!f.building());
167
168 f.begin_build();
169 assert!(f.building());
170
171 f.end_build();
172 assert!(!f.building());
173 }
174}