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