1use std::{mem::offset_of, sync::Arc};
16
17use foyer_common::{
18 code::{Key, Value},
19 error::{Error, ErrorKind, Result},
20 properties::{Hint, Properties},
21 strict_assert,
22};
23use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListAtomicLink};
24use serde::{Deserialize, Serialize};
25
26use super::{Eviction, Op};
27use crate::record::Record;
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct LruConfig {
32 pub high_priority_pool_ratio: f64,
41}
42
43impl Default for LruConfig {
44 fn default() -> Self {
45 Self {
46 high_priority_pool_ratio: 0.9,
47 }
48 }
49}
50
51#[derive(Debug, Default)]
53pub struct LruState {
54 link: LinkedListAtomicLink,
55 in_high_priority_pool: bool,
56 is_pinned: bool,
57}
58
59intrusive_adapter! { Adapter<K, V, P> = Arc<Record<Lru<K, V, P>>>: Record<Lru<K, V, P>> { ?offset = Record::<Lru<K, V, P>>::STATE_OFFSET + offset_of!(LruState, link) => LinkedListAtomicLink } where K: Key, V: Value, P: Properties }
60
61pub struct Lru<K, V, P>
62where
63 K: Key,
64 V: Value,
65 P: Properties,
66{
67 high_priority_list: LinkedList<Adapter<K, V, P>>,
68 list: LinkedList<Adapter<K, V, P>>,
69 pin_list: LinkedList<Adapter<K, V, P>>,
70
71 high_priority_weight: usize,
73 high_priority_weight_capacity: usize,
74
75 config: LruConfig,
76}
77
78impl<K, V, P> Lru<K, V, P>
79where
80 K: Key,
81 V: Value,
82 P: Properties,
83{
84 fn may_overflow_high_priority_pool(&mut self) {
85 while self.high_priority_weight > self.high_priority_weight_capacity {
86 strict_assert!(!self.high_priority_list.is_empty());
87
88 let record = self.high_priority_list.pop_front().unwrap();
90 let state = unsafe { &mut *record.state().get() };
91 strict_assert!(state.in_high_priority_pool);
92 state.in_high_priority_pool = false;
93 self.high_priority_weight -= record.weight();
94 self.list.push_back(record);
95 }
96 }
97}
98
99impl<K, V, P> Eviction for Lru<K, V, P>
100where
101 K: Key,
102 V: Value,
103 P: Properties,
104{
105 type Config = LruConfig;
106 type Key = K;
107 type Value = V;
108 type Properties = P;
109 type State = LruState;
110
111 fn new(capacity: usize, config: &Self::Config) -> Self
112 where
113 Self: Sized,
114 {
115 assert!(
116 (0.0..=1.0).contains(&config.high_priority_pool_ratio),
117 "high_priority_pool_ratio_percentage must be in 0.0..=1.0, given: {}",
118 config.high_priority_pool_ratio
119 );
120
121 let config = config.clone();
122
123 let high_priority_weight_capacity = (capacity as f64 * config.high_priority_pool_ratio) as usize;
124
125 Self {
126 high_priority_list: LinkedList::new(Adapter::new()),
127 list: LinkedList::new(Adapter::new()),
128 pin_list: LinkedList::new(Adapter::new()),
129 high_priority_weight: 0,
130 high_priority_weight_capacity,
131 config,
132 }
133 }
134
135 fn update(&mut self, capacity: usize, config: Option<&Self::Config>) -> Result<()> {
136 if let Some(config) = config {
137 if !(0.0..=1.0).contains(&config.high_priority_pool_ratio) {
138 return Err(
139 Error::new(ErrorKind::Config, "update LRU config failed")
140 .with_context("reason", format!(
141 "high_priority_pool_ratio_percentage must be in 0.0..=1.0, given: {}, new configuration ignored",
142 config.high_priority_pool_ratio
143 ))
144 );
145 }
146 self.config = config.clone();
147 }
148
149 let high_priority_weight_capacity = (capacity as f64 * self.config.high_priority_pool_ratio) as usize;
150 self.high_priority_weight_capacity = high_priority_weight_capacity;
151
152 self.may_overflow_high_priority_pool();
153
154 Ok(())
155 }
156
157 fn push(&mut self, record: Arc<Record<Self>>) {
158 let state = unsafe { &mut *record.state().get() };
159
160 strict_assert!(!state.link.is_linked());
161
162 record.set_in_eviction(true);
163
164 match record.properties().hint().unwrap_or_default() {
165 Hint::Normal => {
166 state.in_high_priority_pool = true;
167 self.high_priority_weight += record.weight();
168 self.high_priority_list.push_back(record);
169
170 self.may_overflow_high_priority_pool();
171 }
172 Hint::Low => {
173 state.in_high_priority_pool = false;
174 self.list.push_back(record);
175 }
176 }
177 }
178
179 fn pop(&mut self) -> Option<Arc<Record<Self>>> {
180 let record = self.list.pop_front().or_else(|| self.high_priority_list.pop_front())?;
181
182 let state = unsafe { &mut *record.state().get() };
183
184 strict_assert!(!state.link.is_linked());
185
186 if state.in_high_priority_pool {
187 self.high_priority_weight -= record.weight();
188 state.in_high_priority_pool = false;
189 }
190
191 record.set_in_eviction(false);
192
193 Some(record)
194 }
195
196 fn remove(&mut self, record: &Arc<Record<Self>>) {
197 let state = unsafe { &mut *record.state().get() };
198
199 strict_assert!(state.link.is_linked());
200
201 match (state.is_pinned, state.in_high_priority_pool) {
202 (true, false) => unsafe { self.pin_list.remove_from_ptr(Arc::as_ptr(record)) },
203 (true, true) => unsafe {
204 state.in_high_priority_pool = false;
205 self.pin_list.remove_from_ptr(Arc::as_ptr(record))
206 },
207 (false, true) => {
208 self.high_priority_weight -= record.weight();
209 state.in_high_priority_pool = false;
210 unsafe { self.high_priority_list.remove_from_ptr(Arc::as_ptr(record)) }
211 }
212 (false, false) => unsafe { self.list.remove_from_ptr(Arc::as_ptr(record)) },
213 };
214
215 strict_assert!(!state.link.is_linked());
216
217 record.set_in_eviction(false);
218 }
219
220 fn clear(&mut self) {
221 while self.pop().is_some() {}
222
223 while let Some(record) = self.pin_list.pop_front() {
225 let state = unsafe { &mut *record.state().get() };
226 strict_assert!(!state.link.is_linked());
227
228 if state.in_high_priority_pool {
229 state.in_high_priority_pool = false;
230 }
231
232 record.set_in_eviction(false);
233 }
234
235 assert!(self.list.is_empty());
236 assert!(self.high_priority_list.is_empty());
237 assert!(self.pin_list.is_empty());
238 assert_eq!(self.high_priority_weight, 0);
239 }
240
241 fn acquire() -> Op<Self> {
242 Op::mutable(|this: &mut Self, record| {
243 if !record.is_in_eviction() {
244 return;
245 }
246
247 let state = unsafe { &mut *record.state().get() };
248 assert!(state.link.is_linked());
249
250 if state.is_pinned {
251 return;
252 }
253
254 let r = if state.in_high_priority_pool {
257 unsafe { this.high_priority_list.remove_from_ptr(Arc::as_ptr(record)) }
258 } else {
259 unsafe { this.list.remove_from_ptr(Arc::as_ptr(record)) }
260 };
261
262 if state.in_high_priority_pool {
263 this.high_priority_weight -= record.weight();
264 }
265
266 this.pin_list.push_back(r);
267
268 state.is_pinned = true;
269 })
270 }
271
272 fn release() -> Op<Self> {
273 Op::mutable(|this: &mut Self, record| {
274 if !record.is_in_eviction() {
275 return;
276 }
277
278 let state = unsafe { &mut *record.state().get() };
279 assert!(state.link.is_linked());
280
281 if !state.is_pinned {
282 return;
283 }
284
285 unsafe { this.pin_list.remove_from_ptr(Arc::as_ptr(record)) };
288
289 if state.in_high_priority_pool {
290 this.high_priority_weight += record.weight();
291 this.high_priority_list.push_back(record.clone());
292 this.may_overflow_high_priority_pool();
293 } else {
294 this.list.push_back(record.clone());
295 }
296
297 state.is_pinned = false;
298 })
299 }
300}
301
302#[cfg(test)]
303pub mod tests {
304
305 use itertools::Itertools;
306
307 use super::*;
308 use crate::{
309 eviction::test_utils::{assert_ptr_eq, assert_ptr_vec_vec_eq, Dump, OpExt, TestProperties},
310 record::Data,
311 };
312
313 impl<K, V> Dump for Lru<K, V, TestProperties>
314 where
315 K: Key + Clone,
316 V: Value + Clone,
317 {
318 type Output = Vec<Vec<Arc<Record<Self>>>>;
319 fn dump(&self) -> Self::Output {
320 let mut low = vec![];
321 let mut high = vec![];
322 let mut pin = vec![];
323
324 let mut cursor = self.list.cursor();
325 loop {
326 cursor.move_next();
327 match cursor.clone_pointer() {
328 Some(record) => low.push(record),
329 None => break,
330 }
331 }
332
333 let mut cursor = self.high_priority_list.cursor();
334 loop {
335 cursor.move_next();
336 match cursor.clone_pointer() {
337 Some(record) => high.push(record),
338 None => break,
339 }
340 }
341
342 let mut cursor = self.pin_list.cursor();
343 loop {
344 cursor.move_next();
345 match cursor.clone_pointer() {
346 Some(record) => pin.push(record),
347 None => break,
348 }
349 }
350
351 vec![low, high, pin]
352 }
353 }
354
355 type TestLru = Lru<u64, u64, TestProperties>;
356
357 #[test]
358 fn test_lru() {
359 let rs = (0..20)
360 .map(|i| {
361 Arc::new(Record::new(Data {
362 key: i,
363 value: i,
364 properties: if i < 10 {
365 TestProperties::default().with_hint(Hint::Normal)
366 } else {
367 TestProperties::default().with_hint(Hint::Low)
368 },
369 hash: i,
370 weight: 1,
371 }))
372 })
373 .collect_vec();
374 let r = |i: usize| rs[i].clone();
375
376 let config = LruConfig {
377 high_priority_pool_ratio: 0.5,
378 };
379 let mut lru = TestLru::new(8, &config);
380
381 assert_eq!(lru.high_priority_weight_capacity, 4);
382
383 lru.push(r(0));
385 lru.push(r(1));
386 lru.push(r(2));
387 lru.push(r(3));
388 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![r(0), r(1), r(2), r(3)], vec![]]);
389
390 lru.push(r(4));
392 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(0)], vec![r(1), r(2), r(3), r(4)], vec![]]);
393
394 lru.push(r(10));
396 assert_ptr_vec_vec_eq(
397 lru.dump(),
398 vec![vec![r(0), r(10)], vec![r(1), r(2), r(3), r(4)], vec![]],
399 );
400
401 let r0 = lru.pop().unwrap();
403 assert_ptr_eq(&r(0), &r0);
404 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1), r(2), r(3), r(4)], vec![]]);
405
406 lru.remove(&rs[2]);
408 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1), r(3), r(4)], vec![]]);
409
410 lru.push(r(11));
412 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10), r(11)], vec![r(1), r(3), r(4)], vec![]]);
413
414 lru.push(r(5));
416 lru.push(r(6));
417 assert_ptr_vec_vec_eq(
418 lru.dump(),
419 vec![vec![r(10), r(11), r(1)], vec![r(3), r(4), r(5), r(6)], vec![]],
420 );
421
422 lru.push(r(0));
424 assert_ptr_vec_vec_eq(
425 lru.dump(),
426 vec![vec![r(10), r(11), r(1), r(3)], vec![r(4), r(5), r(6), r(0)], vec![]],
427 );
428
429 lru.clear();
430 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![], vec![]]);
431 }
432
433 #[test]
434 fn test_lru_pin() {
435 let rs = (0..20)
436 .map(|i| {
437 Arc::new(Record::new(Data {
438 key: i,
439 value: i,
440 properties: if i < 10 {
441 TestProperties::default().with_hint(Hint::Normal)
442 } else {
443 TestProperties::default().with_hint(Hint::Low)
444 },
445 hash: i,
446 weight: 1,
447 }))
448 })
449 .collect_vec();
450 let r = |i: usize| rs[i].clone();
451
452 let config = LruConfig {
453 high_priority_pool_ratio: 0.5,
454 };
455 let mut lru = TestLru::new(8, &config);
456
457 assert_eq!(lru.high_priority_weight_capacity, 4);
458
459 lru.push(r(0));
461 lru.push(r(1));
462 lru.push(r(10));
463 lru.push(r(11));
464 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10), r(11)], vec![r(0), r(1)], vec![]]);
465
466 lru.acquire_mutable(&rs[0]);
469 lru.acquire_mutable(&rs[10]);
470 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(11)], vec![r(1)], vec![r(0), r(10)]]);
471
472 lru.release_mutable(&rs[0]);
474 lru.release_mutable(&rs[10]);
475 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(11), r(10)], vec![r(1), r(0)], vec![]]);
476
477 lru.acquire_mutable(&rs[0]);
481 lru.acquire_mutable(&rs[11]);
482 lru.acquire_mutable(&rs[0]);
483 lru.acquire_mutable(&rs[11]);
484 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0), r(11)]]);
485
486 lru.remove(&rs[11]);
490 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0)]]);
491
492 lru.push(r(2));
497 lru.acquire_mutable(&rs[2]);
498 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0), r(2)]]);
499 lru.remove(&rs[2]);
503 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0)]]);
504
505 lru.release_mutable(&rs[11]);
509 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0)]]);
510
511 lru.release_mutable(&rs[0]);
514 lru.release_mutable(&rs[0]);
515 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1), r(0)], vec![]]);
516
517 lru.acquire_mutable(&rs[1]);
521 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(0)], vec![r(1)]]);
522
523 lru.clear();
524 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![], vec![]]);
525 }
526
527 #[test]
528 fn test_lru_pin_resize_no_panic() {
529 let record = Arc::new(Record::new(Data {
530 key: 0u64,
531 value: 0u64,
532 properties: TestProperties::default().with_hint(Hint::Normal),
533 hash: 0,
534 weight: 1,
535 }));
536
537 let config = LruConfig {
538 high_priority_pool_ratio: 1.0,
539 };
540 let mut lru = TestLru::new(1, &config);
541
542 lru.push(record.clone());
543 lru.acquire_mutable(&record);
544
545 lru.update(0, None).unwrap();
546
547 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![], vec![record]]);
548 }
549}