lazy_link/
cache.rs

1use core::{
2    cell::UnsafeCell,
3    ptr::{
4        self,
5        NonNull,
6    },
7    sync::atomic::{
8        AtomicBool,
9        AtomicPtr,
10        Ordering,
11    },
12};
13
14/// A trait representing different caching strategies for resolved function pointers.
15///
16/// This trait is implemented by various cache modes, including `"none"`, `"static"`, and `"static-atomic"`.
17/// Each implementation defines how the resolved function pointer is stored and accessed, with
18/// considerations for race conditions and performance.
19pub trait Cache {
20    fn resolve(&self, _: impl FnOnce() -> NonNull<()>) -> NonNull<()>;
21}
22
23/// Implementation of the `"static"` cache mode.
24///
25/// This caches the resolved function pointer in a static variable without handling race conditions.
26/// As a result, the resolver may be called multiple times in concurrent scenarios.
27pub struct StaticCache {
28    value: UnsafeCell<Option<NonNull<()>>>,
29}
30
31/// As outlined within the considerations when using this cache,
32/// race conditions are deliberately allowed.
33unsafe impl Sync for StaticCache {}
34
35impl StaticCache {
36    pub const fn new() -> Self {
37        Self {
38            value: UnsafeCell::new(None),
39        }
40    }
41}
42
43impl Default for StaticCache {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49impl Cache for StaticCache {
50    fn resolve(&self, resolver: impl FnOnce() -> NonNull<()>) -> NonNull<()> {
51        let value = unsafe { &mut *self.value.get() };
52        *value.get_or_insert_with(resolver)
53    }
54}
55
56/// Implementation of the `"static-atomic"` cache mode.
57///
58/// This caches the resolved function pointer in an atomic variable, addressing race conditions.
59/// This ensures that the resolver will be called only once, providing thread-safe access to the
60/// cached function pointer.
61pub struct StaticAtomicCache {
62    value: AtomicPtr<()>,
63    resolve_lock: AtomicBool,
64}
65
66impl StaticAtomicCache {
67    pub const fn new() -> Self {
68        Self {
69            value: AtomicPtr::new(ptr::null_mut()),
70            resolve_lock: AtomicBool::new(false),
71        }
72    }
73}
74
75impl Default for StaticAtomicCache {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81impl Cache for StaticAtomicCache {
82    fn resolve(&self, resolver: impl FnOnce() -> NonNull<()>) -> NonNull<()> {
83        loop {
84            let value = self.value.load(Ordering::Relaxed);
85            if let Some(value) = NonNull::new(value) {
86                return value;
87            }
88
89            if self
90                .resolve_lock
91                .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
92                .is_err()
93            {
94                /* The value is already getting resolved. */
95                continue;
96            }
97
98            let result = resolver();
99            self.value.store(result.as_ptr(), Ordering::Relaxed);
100            return result;
101        }
102    }
103}
104
105/// Do not cache the resolved value.
106///
107/// The resolver will be called every time the function is accessed,
108/// ensuring the most current value is retrieved without any caching.
109pub struct NoCache;
110
111impl NoCache {
112    pub const fn new() -> Self {
113        Self
114    }
115}
116
117impl Default for NoCache {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123impl Cache for NoCache {
124    fn resolve(&self, resolver: impl FnOnce() -> NonNull<()>) -> NonNull<()> {
125        resolver()
126    }
127}