quill_sql/utils/cache/
clock_lru.rs1use crate::buffer::FrameId;
2use crate::error::{QuillSQLError, QuillSQLResult};
3use crate::utils::cache::Replacer;
4use std::collections::HashMap;
5
6#[derive(Debug)]
9pub struct ClockLRUReplacer {
10 current_size: usize,
12 capacity: usize,
14 clock_hand: usize,
16 frames: Vec<Option<FrameEntry>>,
18 frame_map: HashMap<FrameId, usize>,
20}
21
22#[derive(Debug, Clone)]
24struct FrameEntry {
25 frame_id: FrameId,
27 referenced: bool,
29 is_evictable: bool,
31}
32
33impl FrameEntry {
34 fn new(frame_id: FrameId) -> Self {
35 Self {
36 frame_id,
37 referenced: false, is_evictable: false,
39 }
40 }
41}
42
43impl Replacer for ClockLRUReplacer {
44 fn new(capacity: usize) -> Self {
46 Self {
47 current_size: 0,
48 capacity,
49 clock_hand: 0,
50 frames: vec![None; capacity],
51 frame_map: HashMap::with_capacity(capacity),
52 }
53 }
54
55 fn record_access(&mut self, frame_id: FrameId) -> QuillSQLResult<()> {
59 if let Some(&index) = self.frame_map.get(&frame_id) {
60 if let Some(entry) = &mut self.frames[index] {
62 entry.referenced = true;
63 }
64 } else {
65 if self.frame_map.len() >= self.capacity {
67 return Err(QuillSQLError::Internal(
68 "frame size exceeds capacity".to_string(),
69 ));
70 }
71
72 let mut slot_index = None;
74 for (i, slot) in self.frames.iter().enumerate() {
75 if slot.is_none() {
76 slot_index = Some(i);
77 break;
78 }
79 }
80
81 let index = slot_index.unwrap_or_else(|| {
82 panic!("No available slot found, this should not happen");
85 });
86
87 let entry = FrameEntry::new(frame_id); self.frames[index] = Some(entry);
90 self.frame_map.insert(frame_id, index);
91 }
92
93 Ok(())
94 }
95
96 fn evict(&mut self) -> Option<FrameId> {
99 if self.current_size == 0 {
101 return None;
102 }
103
104 let start_clock_hand = self.clock_hand;
106 let mut first_evictable = None;
107 let mut first_evictable_pos = None;
108
109 loop {
111 if let Some(entry) = &mut self.frames[self.clock_hand] {
112 if entry.is_evictable {
113 if first_evictable.is_none() {
114 first_evictable = Some(entry.frame_id);
116 first_evictable_pos = Some(self.clock_hand);
117 }
118
119 if !entry.referenced {
120 let frame_id = entry.frame_id;
122 let current_pos = self.clock_hand;
123
124 self.clock_hand = (self.clock_hand + 1) % self.capacity;
126
127 self.frames[current_pos] = None;
129 self.frame_map.remove(&frame_id);
130 self.current_size -= 1;
131
132 return Some(frame_id);
133 } else {
134 entry.referenced = false;
136 }
137 }
138 }
139
140 self.clock_hand = (self.clock_hand + 1) % self.capacity;
142
143 if self.clock_hand == start_clock_hand {
145 break;
146 }
147 }
148
149 if let Some(frame_id) = first_evictable {
151 if let Some(pos) = first_evictable_pos {
152 self.frames[pos] = None;
154 self.frame_map.remove(&frame_id);
155 self.current_size -= 1;
156
157 self.clock_hand = (pos + 1) % self.capacity;
159
160 return Some(frame_id);
161 }
162 }
163
164 None
165 }
166
167 fn set_evictable(&mut self, frame_id: FrameId, set_evictable: bool) -> QuillSQLResult<()> {
169 if let Some(&index) = self.frame_map.get(&frame_id) {
170 if let Some(entry) = &mut self.frames[index] {
171 let old_evictable = entry.is_evictable;
173 entry.is_evictable = set_evictable;
174
175 if !old_evictable && set_evictable {
177 self.current_size += 1;
178 } else if old_evictable && !set_evictable {
179 self.current_size -= 1;
180 }
181
182 return Ok(());
183 }
184 }
185
186 Err(QuillSQLError::Internal(format!(
187 "frame {} not found",
188 frame_id
189 )))
190 }
191
192 fn remove(&mut self, frame_id: FrameId) {
194 if let Some(index) = self.frame_map.remove(&frame_id) {
195 if let Some(entry) = &self.frames[index] {
196 if entry.is_evictable {
197 self.current_size -= 1;
198 }
199 }
200 self.frames[index] = None;
201 }
202 }
203
204 fn size(&self) -> usize {
206 self.current_size
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_clock_lru_basic() {
216 let mut replacer = ClockLRUReplacer::new(3);
217
218 replacer.record_access(1).unwrap();
220 replacer.record_access(2).unwrap();
221 replacer.record_access(3).unwrap();
222
223 replacer.set_evictable(1, true).unwrap();
225 replacer.set_evictable(2, true).unwrap();
226 replacer.set_evictable(3, true).unwrap();
227
228 assert_eq!(replacer.size(), 3);
229
230 let evicted = replacer.evict();
232 assert_eq!(evicted, Some(1));
233 assert_eq!(replacer.size(), 2);
234 }
235
236 #[test]
237 fn test_clock_lru_reference_bit() {
238 let mut replacer = ClockLRUReplacer::new(3);
239
240 replacer.record_access(1).unwrap();
242 replacer.record_access(2).unwrap();
243 replacer.record_access(3).unwrap();
244
245 replacer.set_evictable(1, true).unwrap();
247 replacer.set_evictable(2, true).unwrap();
248 replacer.set_evictable(3, true).unwrap();
249
250 replacer.record_access(1).unwrap();
252
253 let evicted = replacer.evict();
257 assert_eq!(evicted, Some(2));
258
259 let evicted = replacer.evict();
263 assert_eq!(evicted, Some(3));
264
265 assert_eq!(replacer.size(), 1);
267 }
268
269 #[test]
270 fn test_clock_lru_remove() {
271 let mut replacer = ClockLRUReplacer::new(3);
272
273 replacer.record_access(1).unwrap();
275 replacer.record_access(2).unwrap();
276 replacer.record_access(3).unwrap();
277
278 replacer.set_evictable(1, true).unwrap();
280 replacer.set_evictable(2, true).unwrap();
281 replacer.set_evictable(3, true).unwrap();
282
283 assert_eq!(replacer.size(), 3);
284
285 replacer.remove(2);
287
288 assert_eq!(replacer.size(), 2);
289
290 let evicted = replacer.evict();
292 assert_eq!(evicted, Some(1));
293
294 assert_eq!(replacer.size(), 1);
296 }
297
298 #[test]
299 fn test_clock_lru_complex() {
300 let mut replacer = ClockLRUReplacer::new(5);
301
302 for i in 1..=5 {
304 replacer.record_access(i).unwrap();
305 replacer.set_evictable(i, true).unwrap();
306 }
307
308 assert_eq!(replacer.size(), 5);
309
310 replacer.record_access(2).unwrap();
312 replacer.record_access(4).unwrap();
313
314 let evicted = replacer.evict();
316 assert_eq!(evicted, Some(1));
317
318 let evicted = replacer.evict();
321 assert_eq!(evicted, Some(3));
322
323 let evicted = replacer.evict();
326 assert_eq!(evicted, Some(5));
327
328 assert_eq!(replacer.size(), 2);
330
331 replacer.record_access(2).unwrap();
333
334 let evicted = replacer.evict();
336 assert_eq!(evicted, Some(4));
337
338 assert_eq!(replacer.size(), 1);
340 }
341}