moka/common/concurrent/
entry_info.rs

1use std::sync::atomic::{self, AtomicBool, AtomicU16, AtomicU32, Ordering};
2
3use super::{AccessTime, KeyHash};
4use crate::common::time::{AtomicInstant, Instant};
5
6#[derive(Debug)]
7pub(crate) struct EntryInfo<K> {
8    key_hash: KeyHash<K>,
9    /// `is_admitted` indicates that the entry has been admitted to the cache. When
10    /// `false`, it means the entry is _temporary_ admitted to the cache or evicted
11    /// from the cache (so it should not have LRU nodes).
12    is_admitted: AtomicBool,
13    /// `entry_gen` (entry generation) is incremented every time the entry is updated
14    /// in the concurrent hash table.
15    entry_gen: AtomicU16,
16    /// `policy_gen` (policy generation) is incremented every time entry's `WriteOp`
17    /// is applied to the cache policies including the access-order queue (the LRU
18    /// deque).
19    policy_gen: AtomicU16,
20    last_accessed: AtomicInstant,
21    last_modified: AtomicInstant,
22    expiration_time: AtomicInstant,
23    policy_weight: AtomicU32,
24}
25
26impl<K> EntryInfo<K> {
27    #[inline]
28    pub(crate) fn new(key_hash: KeyHash<K>, timestamp: Instant, policy_weight: u32) -> Self {
29        #[cfg(feature = "unstable-debug-counters")]
30        super::debug_counters::InternalGlobalDebugCounters::entry_info_created();
31
32        Self {
33            key_hash,
34            is_admitted: AtomicBool::default(),
35            // `entry_gen` starts at 1 and `policy_gen` start at 0.
36            entry_gen: AtomicU16::new(1),
37            policy_gen: AtomicU16::new(0),
38            last_accessed: AtomicInstant::new(timestamp),
39            last_modified: AtomicInstant::new(timestamp),
40            expiration_time: AtomicInstant::default(),
41            policy_weight: AtomicU32::new(policy_weight),
42        }
43    }
44
45    #[inline]
46    pub(crate) fn key_hash(&self) -> &KeyHash<K> {
47        &self.key_hash
48    }
49
50    #[inline]
51    pub(crate) fn is_admitted(&self) -> bool {
52        self.is_admitted.load(Ordering::Acquire)
53    }
54
55    #[inline]
56    pub(crate) fn set_admitted(&self, value: bool) {
57        self.is_admitted.store(value, Ordering::Release);
58    }
59
60    /// Returns `true` if the `ValueEntry` having this `EntryInfo` is dirty.
61    ///
62    /// Dirty means that the entry has been updated in the concurrent hash table but
63    /// not yet in the cache policies such as access-order queue.
64    #[inline]
65    pub(crate) fn is_dirty(&self) -> bool {
66        let result =
67            self.entry_gen.load(Ordering::Relaxed) != self.policy_gen.load(Ordering::Relaxed);
68        atomic::fence(Ordering::Acquire);
69        result
70    }
71
72    #[inline]
73    pub(crate) fn entry_gen(&self) -> u16 {
74        self.entry_gen.load(Ordering::Acquire)
75    }
76
77    /// Increments the entry generation and returns the new value.
78    #[inline]
79    pub(crate) fn incr_entry_gen(&self) -> u16 {
80        // NOTE: This operation wraps around on overflow.
81        let prev = self.entry_gen.fetch_add(1, Ordering::AcqRel);
82        // Need to add `1` to the previous value to get the current value.
83        prev.wrapping_add(1)
84    }
85
86    /// Sets the policy generation to the given value.
87    #[inline]
88    pub(crate) fn set_policy_gen(&self, value: u16) {
89        let g = &self.policy_gen;
90        loop {
91            let current = g.load(Ordering::Acquire);
92
93            // Do not set the given value if it is smaller than the current value of
94            // `policy_gen`. Note that the current value may have been wrapped
95            // around. If the value is much larger than the current value, it is
96            // likely that the value of `policy_gen` has been wrapped around.
97            if current >= value || value.wrapping_sub(current) > u16::MAX / 2 {
98                break;
99            }
100
101            // Try to set the value.
102            if g.compare_exchange_weak(current, value, Ordering::AcqRel, Ordering::Acquire)
103                .is_ok()
104            {
105                break;
106            }
107        }
108    }
109
110    #[inline]
111    pub(crate) fn policy_weight(&self) -> u32 {
112        self.policy_weight.load(Ordering::Acquire)
113    }
114
115    pub(crate) fn set_policy_weight(&self, size: u32) {
116        self.policy_weight.store(size, Ordering::Release);
117    }
118
119    #[inline]
120    pub(crate) fn expiration_time(&self) -> Option<Instant> {
121        self.expiration_time.instant()
122    }
123
124    pub(crate) fn set_expiration_time(&self, time: Option<Instant>) {
125        if let Some(t) = time {
126            self.expiration_time.set_instant(t);
127        } else {
128            self.expiration_time.clear();
129        }
130    }
131}
132
133#[cfg(feature = "unstable-debug-counters")]
134impl<K> Drop for EntryInfo<K> {
135    fn drop(&mut self) {
136        super::debug_counters::InternalGlobalDebugCounters::entry_info_dropped();
137    }
138}
139
140impl<K> AccessTime for EntryInfo<K> {
141    #[inline]
142    fn last_accessed(&self) -> Option<Instant> {
143        self.last_accessed.instant()
144    }
145
146    #[inline]
147    fn set_last_accessed(&self, timestamp: Instant) {
148        self.last_accessed.set_instant(timestamp);
149    }
150
151    #[inline]
152    fn last_modified(&self) -> Option<Instant> {
153        self.last_modified.instant()
154    }
155
156    #[inline]
157    fn set_last_modified(&self, timestamp: Instant) {
158        self.last_modified.set_instant(timestamp);
159    }
160}
161
162#[cfg(test)]
163mod test {
164    use super::EntryInfo;
165
166    // Run with:
167    //   RUSTFLAGS='--cfg rustver' cargo test --lib --features sync -- common::concurrent::entry_info::test --nocapture
168    //   RUSTFLAGS='--cfg rustver' cargo test --lib --no-default-features --features sync -- common::concurrent::entry_info::test --nocapture
169    //
170    // Note: the size of the struct may change in a future version of Rust.
171    #[cfg_attr(
172        not(all(rustver, any(target_os = "linux", target_os = "macos"))),
173        ignore
174    )]
175    #[test]
176    fn check_struct_size() {
177        use std::mem::size_of;
178
179        #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
180        enum TargetArch {
181            Linux64,
182            Linux32X86,
183            Linux32Arm,
184            Linux32Mips,
185            MacOS64,
186        }
187
188        use TargetArch::*;
189
190        #[allow(clippy::option_env_unwrap)]
191        // e.g. "1.64"
192        let ver =
193            option_env!("RUSTC_SEMVER").expect("RUSTC_SEMVER env var was not set at compile time");
194        let arch = if cfg!(target_os = "linux") {
195            if cfg!(target_pointer_width = "64") {
196                Linux64
197            } else if cfg!(target_pointer_width = "32") {
198                if cfg!(target_arch = "x86") {
199                    Linux32X86
200                } else if cfg!(target_arch = "arm") {
201                    Linux32Arm
202                } else if cfg!(target_arch = "mips") {
203                    Linux32Mips
204                } else {
205                    unimplemented!();
206                }
207            } else {
208                unimplemented!();
209            }
210        } else if cfg!(target_os = "macos") {
211            MacOS64
212        } else {
213            panic!("Unsupported target architecture");
214        };
215
216        let expected_sizes = match arch {
217            Linux64 | Linux32Arm | Linux32Mips => vec![("1.51", 56)],
218            Linux32X86 => vec![("1.51", 48)],
219            MacOS64 => vec![("1.62", 56)],
220        };
221
222        let mut expected = None;
223        for (ver_str, size) in expected_sizes {
224            expected = Some(size);
225            if ver >= ver_str {
226                break;
227            }
228        }
229
230        if let Some(size) = expected {
231            assert_eq!(size_of::<EntryInfo<()>>(), size);
232        } else {
233            panic!("No expected size for {arch:?} with Rust version {ver}");
234        }
235    }
236}