1use std::{mem::offset_of, sync::Arc};
16
17use foyer_common::{
18 code::{Key, Value},
19 properties::{Hint, Properties},
20 strict_assert,
21};
22use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListAtomicLink};
23use serde::{Deserialize, Serialize};
24
25use super::{Eviction, Op};
26use crate::{
27 error::{Error, Result},
28 record::Record,
29};
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct LruConfig {
34 pub high_priority_pool_ratio: f64,
43}
44
45impl Default for LruConfig {
46 fn default() -> Self {
47 Self {
48 high_priority_pool_ratio: 0.9,
49 }
50 }
51}
52
53#[derive(Debug, Default)]
55pub struct LruState {
56 link: LinkedListAtomicLink,
57 in_high_priority_pool: bool,
58 is_pinned: bool,
59}
60
61intrusive_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 }
62
63pub struct Lru<K, V, P>
64where
65 K: Key,
66 V: Value,
67 P: Properties,
68{
69 high_priority_list: LinkedList<Adapter<K, V, P>>,
70 list: LinkedList<Adapter<K, V, P>>,
71 pin_list: LinkedList<Adapter<K, V, P>>,
72
73 high_priority_weight: usize,
74 high_priority_weight_capacity: usize,
75
76 config: LruConfig,
77}
78
79impl<K, V, P> Lru<K, V, P>
80where
81 K: Key,
82 V: Value,
83 P: Properties,
84{
85 fn may_overflow_high_priority_pool(&mut self) {
86 while self.high_priority_weight > self.high_priority_weight_capacity {
87 strict_assert!(!self.high_priority_list.is_empty());
88
89 let record = self.high_priority_list.pop_front().unwrap();
91 let state = unsafe { &mut *record.state().get() };
92 strict_assert!(state.in_high_priority_pool);
93 state.in_high_priority_pool = false;
94 self.high_priority_weight -= record.weight();
95 self.list.push_back(record);
96 }
97 }
98}
99
100impl<K, V, P> Eviction for Lru<K, V, P>
101where
102 K: Key,
103 V: Value,
104 P: Properties,
105{
106 type Config = LruConfig;
107 type Key = K;
108 type Value = V;
109 type Properties = P;
110 type State = LruState;
111
112 fn new(capacity: usize, config: &Self::Config) -> Self
113 where
114 Self: Sized,
115 {
116 assert!(
117 (0.0..=1.0).contains(&config.high_priority_pool_ratio),
118 "high_priority_pool_ratio_percentage must be in 0.0..=1.0, given: {}",
119 config.high_priority_pool_ratio
120 );
121
122 let config = config.clone();
123
124 let high_priority_weight_capacity = (capacity as f64 * config.high_priority_pool_ratio) as usize;
125
126 Self {
127 high_priority_list: LinkedList::new(Adapter::new()),
128 list: LinkedList::new(Adapter::new()),
129 pin_list: LinkedList::new(Adapter::new()),
130 high_priority_weight: 0,
131 high_priority_weight_capacity,
132 config,
133 }
134 }
135
136 fn update(&mut self, capacity: usize, config: Option<&Self::Config>) -> Result<()> {
137 if let Some(config) = config {
138 if !(0.0..=1.0).contains(&config.high_priority_pool_ratio) {
139 return Err(Error::ConfigError(
140 format!(
141 "[lru]: 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 self.high_priority_weight -= record.weight();
205 state.in_high_priority_pool = false;
206 self.pin_list.remove_from_ptr(Arc::as_ptr(record))
207 },
208 (false, true) => {
209 self.high_priority_weight -= record.weight();
210 state.in_high_priority_pool = false;
211 unsafe { self.high_priority_list.remove_from_ptr(Arc::as_ptr(record)) }
212 }
213 (false, false) => unsafe { self.list.remove_from_ptr(Arc::as_ptr(record)) },
214 };
215
216 strict_assert!(!state.link.is_linked());
217
218 record.set_in_eviction(false);
219 }
220
221 fn clear(&mut self) {
222 while self.pop().is_some() {}
223
224 while let Some(record) = self.pin_list.pop_front() {
226 let state = unsafe { &mut *record.state().get() };
227 strict_assert!(!state.link.is_linked());
228
229 if state.in_high_priority_pool {
230 self.high_priority_weight -= record.weight();
231 state.in_high_priority_pool = false;
232 }
233
234 record.set_in_eviction(false);
235 }
236
237 assert!(self.list.is_empty());
238 assert!(self.high_priority_list.is_empty());
239 assert!(self.pin_list.is_empty());
240 assert_eq!(self.high_priority_weight, 0);
241 }
242
243 fn acquire() -> Op<Self> {
244 Op::mutable(|this: &mut Self, record| {
245 if !record.is_in_eviction() {
246 return;
247 }
248
249 let state = unsafe { &mut *record.state().get() };
250 assert!(state.link.is_linked());
251
252 if state.is_pinned {
253 return;
254 }
255
256 let r = if state.in_high_priority_pool {
259 unsafe { this.high_priority_list.remove_from_ptr(Arc::as_ptr(record)) }
260 } else {
261 unsafe { this.list.remove_from_ptr(Arc::as_ptr(record)) }
262 };
263
264 this.pin_list.push_back(r);
265
266 state.is_pinned = true;
267 })
268 }
269
270 fn release() -> 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 unsafe { this.pin_list.remove_from_ptr(Arc::as_ptr(record)) };
286
287 if state.in_high_priority_pool {
288 this.high_priority_list.push_back(record.clone());
289 } else {
290 this.list.push_back(record.clone());
291 }
292
293 state.is_pinned = false;
294 })
295 }
296}
297
298#[cfg(test)]
299pub mod tests {
300
301 use itertools::Itertools;
302
303 use super::*;
304 use crate::{
305 eviction::test_utils::{assert_ptr_eq, assert_ptr_vec_vec_eq, Dump, OpExt, TestProperties},
306 record::Data,
307 };
308
309 impl<K, V> Dump for Lru<K, V, TestProperties>
310 where
311 K: Key + Clone,
312 V: Value + Clone,
313 {
314 type Output = Vec<Vec<Arc<Record<Self>>>>;
315 fn dump(&self) -> Self::Output {
316 let mut low = vec![];
317 let mut high = vec![];
318 let mut pin = vec![];
319
320 let mut cursor = self.list.cursor();
321 loop {
322 cursor.move_next();
323 match cursor.clone_pointer() {
324 Some(record) => low.push(record),
325 None => break,
326 }
327 }
328
329 let mut cursor = self.high_priority_list.cursor();
330 loop {
331 cursor.move_next();
332 match cursor.clone_pointer() {
333 Some(record) => high.push(record),
334 None => break,
335 }
336 }
337
338 let mut cursor = self.pin_list.cursor();
339 loop {
340 cursor.move_next();
341 match cursor.clone_pointer() {
342 Some(record) => pin.push(record),
343 None => break,
344 }
345 }
346
347 vec![low, high, pin]
348 }
349 }
350
351 type TestLru = Lru<u64, u64, TestProperties>;
352
353 #[test]
354 fn test_lru() {
355 let rs = (0..20)
356 .map(|i| {
357 Arc::new(Record::new(Data {
358 key: i,
359 value: i,
360 properties: if i < 10 {
361 TestProperties::default().with_hint(Hint::Normal)
362 } else {
363 TestProperties::default().with_hint(Hint::Low)
364 },
365 hash: i,
366 weight: 1,
367 }))
368 })
369 .collect_vec();
370 let r = |i: usize| rs[i].clone();
371
372 let config = LruConfig {
373 high_priority_pool_ratio: 0.5,
374 };
375 let mut lru = TestLru::new(8, &config);
376
377 assert_eq!(lru.high_priority_weight_capacity, 4);
378
379 lru.push(r(0));
381 lru.push(r(1));
382 lru.push(r(2));
383 lru.push(r(3));
384 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![r(0), r(1), r(2), r(3)], vec![]]);
385
386 lru.push(r(4));
388 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(0)], vec![r(1), r(2), r(3), r(4)], vec![]]);
389
390 lru.push(r(10));
392 assert_ptr_vec_vec_eq(
393 lru.dump(),
394 vec![vec![r(0), r(10)], vec![r(1), r(2), r(3), r(4)], vec![]],
395 );
396
397 let r0 = lru.pop().unwrap();
399 assert_ptr_eq(&r(0), &r0);
400 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1), r(2), r(3), r(4)], vec![]]);
401
402 lru.remove(&rs[2]);
404 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1), r(3), r(4)], vec![]]);
405
406 lru.push(r(11));
408 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10), r(11)], vec![r(1), r(3), r(4)], vec![]]);
409
410 lru.push(r(5));
412 lru.push(r(6));
413 assert_ptr_vec_vec_eq(
414 lru.dump(),
415 vec![vec![r(10), r(11), r(1)], vec![r(3), r(4), r(5), r(6)], vec![]],
416 );
417
418 lru.push(r(0));
420 assert_ptr_vec_vec_eq(
421 lru.dump(),
422 vec![vec![r(10), r(11), r(1), r(3)], vec![r(4), r(5), r(6), r(0)], vec![]],
423 );
424
425 lru.clear();
426 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![], vec![]]);
427 }
428
429 #[test]
430 fn test_lru_pin() {
431 let rs = (0..20)
432 .map(|i| {
433 Arc::new(Record::new(Data {
434 key: i,
435 value: i,
436 properties: if i < 10 {
437 TestProperties::default().with_hint(Hint::Normal)
438 } else {
439 TestProperties::default().with_hint(Hint::Low)
440 },
441 hash: i,
442 weight: 1,
443 }))
444 })
445 .collect_vec();
446 let r = |i: usize| rs[i].clone();
447
448 let config = LruConfig {
449 high_priority_pool_ratio: 0.5,
450 };
451 let mut lru = TestLru::new(8, &config);
452
453 assert_eq!(lru.high_priority_weight_capacity, 4);
454
455 lru.push(r(0));
457 lru.push(r(1));
458 lru.push(r(10));
459 lru.push(r(11));
460 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10), r(11)], vec![r(0), r(1)], vec![]]);
461
462 lru.acquire_mutable(&rs[0]);
465 lru.acquire_mutable(&rs[10]);
466 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(11)], vec![r(1)], vec![r(0), r(10)]]);
467
468 lru.release_mutable(&rs[0]);
470 lru.release_mutable(&rs[10]);
471 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(11), r(10)], vec![r(1), r(0)], vec![]]);
472
473 lru.acquire_mutable(&rs[0]);
477 lru.acquire_mutable(&rs[11]);
478 lru.acquire_mutable(&rs[0]);
479 lru.acquire_mutable(&rs[11]);
480 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0), r(11)]]);
481
482 lru.remove(&rs[11]);
486 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0)]]);
487
488 lru.push(r(2));
493 lru.acquire_mutable(&rs[2]);
494 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0), r(2)]]);
495 lru.remove(&rs[2]);
499 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0)]]);
500
501 lru.release_mutable(&rs[11]);
505 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1)], vec![r(0)]]);
506
507 lru.release_mutable(&rs[0]);
510 lru.release_mutable(&rs[0]);
511 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(1), r(0)], vec![]]);
512
513 lru.acquire_mutable(&rs[1]);
517 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![r(10)], vec![r(0)], vec![r(1)]]);
518
519 lru.clear();
520 assert_ptr_vec_vec_eq(lru.dump(), vec![vec![], vec![], vec![]]);
521 }
522}