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,
72 high_priority_weight_capacity: usize,
73
74 config: LruConfig,
75}
76
77impl<K, V, P> Lru<K, V, P>
78where
79 K: Key,
80 V: Value,
81 P: Properties,
82{
83 fn may_overflow_high_priority_pool(&mut self) {
84 while self.high_priority_weight > self.high_priority_weight_capacity {
85 strict_assert!(!self.high_priority_list.is_empty());
86
87 let record = self.high_priority_list.pop_front().unwrap();
89 let state = unsafe { &mut *record.state().get() };
90 strict_assert!(state.in_high_priority_pool);
91 state.in_high_priority_pool = false;
92 self.high_priority_weight -= record.weight();
93 self.list.push_back(record);
94 }
95 }
96}
97
98impl<K, V, P> Eviction for Lru<K, V, P>
99where
100 K: Key,
101 V: Value,
102 P: Properties,
103{
104 type Config = LruConfig;
105 type Key = K;
106 type Value = V;
107 type Properties = P;
108 type State = LruState;
109
110 fn new(capacity: usize, config: &Self::Config) -> Self
111 where
112 Self: Sized,
113 {
114 assert!(
115 (0.0..=1.0).contains(&config.high_priority_pool_ratio),
116 "high_priority_pool_ratio_percentage must be in 0.0..=1.0, given: {}",
117 config.high_priority_pool_ratio
118 );
119
120 let config = config.clone();
121
122 let high_priority_weight_capacity = (capacity as f64 * config.high_priority_pool_ratio) as usize;
123
124 Self {
125 high_priority_list: LinkedList::new(Adapter::new()),
126 list: LinkedList::new(Adapter::new()),
127 pin_list: LinkedList::new(Adapter::new()),
128 high_priority_weight: 0,
129 high_priority_weight_capacity,
130 config,
131 }
132 }
133
134 fn update(&mut self, capacity: usize, config: Option<&Self::Config>) -> Result<()> {
135 if let Some(config) = config {
136 if !(0.0..=1.0).contains(&config.high_priority_pool_ratio) {
137 return Err(
138 Error::new(ErrorKind::Config, "update LRU config failed")
139 .with_context("reason", format!(
140 "high_priority_pool_ratio_percentage must be in 0.0..=1.0, given: {}, new configuration ignored",
141 config.high_priority_pool_ratio
142 ))
143 );
144 }
145 self.config = config.clone();
146 }
147
148 let high_priority_weight_capacity = (capacity as f64 * self.config.high_priority_pool_ratio) as usize;
149 self.high_priority_weight_capacity = high_priority_weight_capacity;
150
151 self.may_overflow_high_priority_pool();
152
153 Ok(())
154 }
155
156 fn push(&mut self, record: Arc<Record<Self>>) {
157 let state = unsafe { &mut *record.state().get() };
158
159 strict_assert!(!state.link.is_linked());
160
161 record.set_in_eviction(true);
162
163 match record.properties().hint().unwrap_or_default() {
164 Hint::Normal => {
165 state.in_high_priority_pool = true;
166 self.high_priority_weight += record.weight();
167 self.high_priority_list.push_back(record);
168
169 self.may_overflow_high_priority_pool();
170 }
171 Hint::Low => {
172 state.in_high_priority_pool = false;
173 self.list.push_back(record);
174 }
175 }
176 }
177
178 fn pop(&mut self) -> Option<Arc<Record<Self>>> {
179 let record = self.list.pop_front().or_else(|| self.high_priority_list.pop_front())?;
180
181 let state = unsafe { &mut *record.state().get() };
182
183 strict_assert!(!state.link.is_linked());
184
185 if state.in_high_priority_pool {
186 self.high_priority_weight -= record.weight();
187 state.in_high_priority_pool = false;
188 }
189
190 record.set_in_eviction(false);
191
192 Some(record)
193 }
194
195 fn remove(&mut self, record: &Arc<Record<Self>>) {
196 let state = unsafe { &mut *record.state().get() };
197
198 strict_assert!(state.link.is_linked());
199
200 match (state.is_pinned, state.in_high_priority_pool) {
201 (true, false) => unsafe { self.pin_list.remove_from_ptr(Arc::as_ptr(record)) },
202 (true, true) => unsafe {
203 self.high_priority_weight -= record.weight();
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 self.high_priority_weight -= record.weight();
230 state.in_high_priority_pool = false;
231 }
232
233 record.set_in_eviction(false);
234 }
235
236 assert!(self.list.is_empty());
237 assert!(self.high_priority_list.is_empty());
238 assert!(self.pin_list.is_empty());
239 assert_eq!(self.high_priority_weight, 0);
240 }
241
242 fn acquire() -> Op<Self> {
243 Op::mutable(|this: &mut Self, record| {
244 if !record.is_in_eviction() {
245 return;
246 }
247
248 let state = unsafe { &mut *record.state().get() };
249 assert!(state.link.is_linked());
250
251 if state.is_pinned {
252 return;
253 }
254
255 let r = if state.in_high_priority_pool {
258 unsafe { this.high_priority_list.remove_from_ptr(Arc::as_ptr(record)) }
259 } else {
260 unsafe { this.list.remove_from_ptr(Arc::as_ptr(record)) }
261 };
262
263 this.pin_list.push_back(r);
264
265 state.is_pinned = true;
266 })
267 }
268
269 fn release() -> Op<Self> {
270 Op::mutable(|this: &mut Self, record| {
271 if !record.is_in_eviction() {
272 return;
273 }
274
275 let state = unsafe { &mut *record.state().get() };
276 assert!(state.link.is_linked());
277
278 if !state.is_pinned {
279 return;
280 }
281
282 unsafe { this.pin_list.remove_from_ptr(Arc::as_ptr(record)) };
285
286 if state.in_high_priority_pool {
287 this.high_priority_list.push_back(record.clone());
288 } else {
289 this.list.push_back(record.clone());
290 }
291
292 state.is_pinned = false;
293 })
294 }
295}
296
297#[cfg(test)]
298pub mod tests {
299
300 use itertools::Itertools;
301
302 use super::*;
303 use crate::{
304 eviction::test_utils::{assert_ptr_eq, assert_ptr_vec_vec_eq, Dump, OpExt, TestProperties},
305 record::Data,
306 };
307
308 impl<K, V> Dump for Lru<K, V, TestProperties>
309 where
310 K: Key + Clone,
311 V: Value + Clone,
312 {
313 type Output = Vec<Vec<Arc<Record<Self>>>>;
314 fn dump(&self) -> Self::Output {
315 let mut low = vec![];
316 let mut high = vec![];
317 let mut pin = vec![];
318
319 let mut cursor = self.list.cursor();
320 loop {
321 cursor.move_next();
322 match cursor.clone_pointer() {
323 Some(record) => low.push(record),
324 None => break,
325 }
326 }
327
328 let mut cursor = self.high_priority_list.cursor();
329 loop {
330 cursor.move_next();
331 match cursor.clone_pointer() {
332 Some(record) => high.push(record),
333 None => break,
334 }
335 }
336
337 let mut cursor = self.pin_list.cursor();
338 loop {
339 cursor.move_next();
340 match cursor.clone_pointer() {
341 Some(record) => pin.push(record),
342 None => break,
343 }
344 }
345
346 vec![low, high, pin]
347 }
348 }
349
350 type TestLru = Lru<u64, u64, TestProperties>;
351
352 #[test]
353 fn test_lru() {
354 let rs = (0..20)
355 .map(|i| {
356 Arc::new(Record::new(Data {
357 key: i,
358 value: i,
359 properties: if i < 10 {
360 TestProperties::default().with_hint(Hint::Normal)
361 } else {
362 TestProperties::default().with_hint(Hint::Low)
363 },
364 hash: i,
365 weight: 1,
366 }))
367 })
368 .collect_vec();
369 let r = |i: usize| rs[i].clone();
370
371 let config = LruConfig {
372 high_priority_pool_ratio: 0.5,
373 };
374 let mut lru = TestLru::new(8, &config);
375
376 assert_eq!(lru.high_priority_weight_capacity, 4);
377
378 lru.push(r(0));
380 lru.push(r(1));
381 lru.push(r(2));
382 lru.push(r(3));
383 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![r(0), r(1), r(2), r(3)], vec![]]);
384
385 lru.push(r(4));
387 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(0)], vec![r(1), r(2), r(3), r(4)], vec![]]);
388
389 lru.push(r(10));
391 assert_ptr_vec_vec_eq(
392 lru.dump(),
393 vec![vec![r(0), r(10)], vec![r(1), r(2), r(3), r(4)], vec![]],
394 );
395
396 let r0 = lru.pop().unwrap();
398 assert_ptr_eq(&r(0), &r0);
399 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1), r(2), r(3), r(4)], vec![]]);
400
401 lru.remove(&rs[2]);
403 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1), r(3), r(4)], vec![]]);
404
405 lru.push(r(11));
407 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10), r(11)], vec![r(1), r(3), r(4)], vec![]]);
408
409 lru.push(r(5));
411 lru.push(r(6));
412 assert_ptr_vec_vec_eq(
413 lru.dump(),
414 vec![vec![r(10), r(11), r(1)], vec![r(3), r(4), r(5), r(6)], vec![]],
415 );
416
417 lru.push(r(0));
419 assert_ptr_vec_vec_eq(
420 lru.dump(),
421 vec![vec![r(10), r(11), r(1), r(3)], vec![r(4), r(5), r(6), r(0)], vec![]],
422 );
423
424 lru.clear();
425 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![], vec![]]);
426 }
427
428 #[test]
429 fn test_lru_pin() {
430 let rs = (0..20)
431 .map(|i| {
432 Arc::new(Record::new(Data {
433 key: i,
434 value: i,
435 properties: if i < 10 {
436 TestProperties::default().with_hint(Hint::Normal)
437 } else {
438 TestProperties::default().with_hint(Hint::Low)
439 },
440 hash: i,
441 weight: 1,
442 }))
443 })
444 .collect_vec();
445 let r = |i: usize| rs[i].clone();
446
447 let config = LruConfig {
448 high_priority_pool_ratio: 0.5,
449 };
450 let mut lru = TestLru::new(8, &config);
451
452 assert_eq!(lru.high_priority_weight_capacity, 4);
453
454 lru.push(r(0));
456 lru.push(r(1));
457 lru.push(r(10));
458 lru.push(r(11));
459 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10), r(11)], vec![r(0), r(1)], vec![]]);
460
461 lru.acquire_mutable(&rs[0]);
464 lru.acquire_mutable(&rs[10]);
465 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(11)], vec![r(1)], vec![r(0), r(10)]]);
466
467 lru.release_mutable(&rs[0]);
469 lru.release_mutable(&rs[10]);
470 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(11), r(10)], vec![r(1), r(0)], vec![]]);
471
472 lru.acquire_mutable(&rs[0]);
476 lru.acquire_mutable(&rs[11]);
477 lru.acquire_mutable(&rs[0]);
478 lru.acquire_mutable(&rs[11]);
479 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0), r(11)]]);
480
481 lru.remove(&rs[11]);
485 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0)]]);
486
487 lru.push(r(2));
492 lru.acquire_mutable(&rs[2]);
493 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0), r(2)]]);
494 lru.remove(&rs[2]);
498 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0)]]);
499
500 lru.release_mutable(&rs[11]);
504 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0)]]);
505
506 lru.release_mutable(&rs[0]);
509 lru.release_mutable(&rs[0]);
510 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1), r(0)], vec![]]);
511
512 lru.acquire_mutable(&rs[1]);
516 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(0)], vec![r(1)]]);
517
518 lru.clear();
519 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![], vec![]]);
520 }
521}