Skip to main content

gproxy_cache/
memory.rs

1use crate::*;
2use std::{
3    collections::HashMap,
4    sync::{Arc, Mutex, MutexGuard},
5};
6use tokio::sync::broadcast;
7use web_time::Instant;
8
9#[derive(Clone, Debug)]
10pub struct MemoryOptions {
11    /// Live keys across KV, counter and permit domains. A full cache sheds
12    /// live values, then counters, soonest to expire first; never a permit.
13    pub max_keys: usize,
14    pub max_topics: usize,
15    pub limits: Limits,
16}
17impl Default for MemoryOptions {
18    fn default() -> Self {
19        Self {
20            max_keys: 100_000,
21            max_topics: 256,
22            limits: Limits::default(),
23        }
24    }
25}
26struct Timed<T> {
27    value: T,
28    expires: Instant,
29}
30#[derive(Default)]
31struct State {
32    values: HashMap<String, Timed<Entry>>,
33    counters: HashMap<String, Timed<Counter>>,
34    permits: HashMap<String, HashMap<Version, Instant>>,
35    topics: HashMap<String, broadcast::Sender<Vec<u8>>>,
36}
37impl State {
38    fn keys(&self) -> usize {
39        self.values.len() + self.counters.len() + self.permits.len()
40    }
41    fn purge(&mut self, now: Instant) -> usize {
42        let before = self.keys();
43        self.values.retain(|_, v| v.expires > now);
44        self.counters.retain(|_, v| v.expires > now);
45        self.permits.retain(|_, holders| {
46            holders.retain(|_, expires| *expires > now);
47            !holders.is_empty()
48        });
49        self.topics.retain(|_, sender| sender.receiver_count() > 0);
50        before - self.keys()
51    }
52    /// Make room for one more key.
53    ///
54    /// Expired keys go first. A cache still full after that sheds live soft
55    /// state rather than refusing the write: values, then counters, soonest to
56    /// expire first. Refusing would fail every request that writes a new key —
57    /// an affinity pin after a paid upstream answer, a fresh rate-limit
58    /// window — once enough distinct sessions have passed through. Permits are
59    /// never shed: a lease is a promise of exclusion, and dropping one would
60    /// let a second holder in.
61    ///
62    /// Eviction takes an eighth of the capacity at once, so a cache that stays
63    /// full scans its keys once per `max / 8` inserts rather than on each.
64    fn room(&mut self, max: usize, now: Instant) -> Result<()> {
65        if self.keys() < max {
66            return Ok(());
67        }
68        self.purge(now);
69        let batch = (max / 8).max(1);
70        if self.keys() >= max {
71            evict_soonest(&mut self.values, batch);
72        }
73        if self.keys() >= max {
74            evict_soonest(&mut self.counters, batch);
75        }
76        if self.keys() >= max {
77            return Err(CacheError::Capacity);
78        }
79        Ok(())
80    }
81    fn expire_key(&mut self, key: &str, now: Instant) {
82        if self.values.get(key).is_some_and(|v| v.expires <= now) {
83            self.values.remove(key);
84        }
85        if self.counters.get(key).is_some_and(|v| v.expires <= now) {
86            self.counters.remove(key);
87        }
88        if let Some(holders) = self.permits.get_mut(key) {
89            holders.retain(|_, expires| *expires > now);
90            if holders.is_empty() {
91                self.permits.remove(key);
92            }
93        }
94    }
95}
96/// Drop the `count` entries of `map` that expire soonest, or all of them.
97fn evict_soonest<T>(map: &mut HashMap<String, Timed<T>>, count: usize) {
98    if map.len() <= count {
99        map.clear();
100        return;
101    }
102    let mut expiries: Vec<Instant> = map.values().map(|v| v.expires).collect();
103    let (_, cutoff, _) = expiries.select_nth_unstable(count - 1);
104    let cutoff = *cutoff;
105    map.retain(|_, v| v.expires > cutoff);
106}
107struct Inner {
108    options: MemoryOptions,
109    state: Mutex<State>,
110}
111/// Clone shares one process-local cache. Separate new() calls are isolated.
112#[derive(Clone)]
113pub struct MemoryCache(Arc<Inner>);
114impl Default for MemoryCache {
115    fn default() -> Self {
116        Self::new(MemoryOptions::default()).expect("valid defaults")
117    }
118}
119impl MemoryCache {
120    pub fn new(options: MemoryOptions) -> Result<Self> {
121        options.limits.validate()?;
122        if options.max_keys == 0 || options.max_topics == 0 {
123            return Err(CacheError::Invalid("memory capacities must be positive"));
124        }
125        Ok(Self(Arc::new(Inner {
126            options,
127            state: Mutex::new(State::default()),
128        })))
129    }
130    fn state(&self) -> Result<MutexGuard<'_, State>> {
131        self.0.state.lock().map_err(|_| CacheError::Poisoned)
132    }
133    /// Optional maintenance; expiry is always enforced on access. No background task.
134    pub fn purge_expired(&self) -> Result<usize> {
135        Ok(self.state()?.purge(Instant::now()))
136    }
137    fn expiry(ttl: Duration) -> Result<Instant> {
138        Instant::now()
139            .checked_add(Duration::from_millis(ttl_ms(ttl)?))
140            .ok_or(CacheError::Invalid("TTL exceeds clock range"))
141    }
142}
143#[async_trait::async_trait]
144impl Cache for MemoryCache {
145    async fn get(&self, key: &str) -> Result<Option<Entry>> {
146        self.0.options.limits.key(key)?;
147        let mut state = self.state()?;
148        state.expire_key(key, Instant::now());
149        Ok(state.values.get(key).map(|v| v.value.clone()))
150    }
151    async fn get_many(&self, keys: &[String]) -> Result<Vec<Option<Entry>>> {
152        for key in keys {
153            self.0.options.limits.key(key)?;
154        }
155        let now = Instant::now();
156        let mut state = self.state()?;
157        Ok(keys
158            .iter()
159            .map(|key| {
160                state.expire_key(key, now);
161                state.values.get(key.as_str()).map(|v| v.value.clone())
162            })
163            .collect())
164    }
165    async fn put(&self, key: &str, value: Vec<u8>, ttl: Duration) -> Result<Version> {
166        self.0.options.limits.key(key)?;
167        self.0.options.limits.value(&value)?;
168        let expires = Self::expiry(ttl)?;
169        let version = Version::fresh()?;
170        let mut state = self.state()?;
171        state.expire_key(key, Instant::now());
172        if !state.values.contains_key(key) {
173            state.room(self.0.options.max_keys, Instant::now())?;
174        }
175        state.values.insert(
176            key.into(),
177            Timed {
178                value: Entry { value, version },
179                expires,
180            },
181        );
182        Ok(version)
183    }
184    async fn delete(&self, key: &str) -> Result<bool> {
185        self.0.options.limits.key(key)?;
186        let mut state = self.state()?;
187        state.expire_key(key, Instant::now());
188        Ok(state.values.remove(key).is_some())
189    }
190    async fn compare_exchange(
191        &self,
192        key: &str,
193        expected: Option<Version>,
194        replacement: Option<Replacement>,
195    ) -> Result<CasOutcome> {
196        self.0.options.limits.key(key)?;
197        let next = replacement
198            .map(|r| -> Result<_> {
199                self.0.options.limits.value(&r.value)?;
200                Ok(Timed {
201                    value: Entry {
202                        value: r.value,
203                        version: Version::fresh()?,
204                    },
205                    expires: Self::expiry(r.ttl)?,
206                })
207            })
208            .transpose()?;
209        let mut state = self.state()?;
210        state.expire_key(key, Instant::now());
211        if state.values.get(key).map(|v| v.value.version) != expected {
212            return Ok(CasOutcome::Conflict);
213        }
214        let version = next.as_ref().map(|v| v.value.version);
215        if let Some(next) = next {
216            if !state.values.contains_key(key) {
217                state.room(self.0.options.max_keys, Instant::now())?;
218            }
219            state.values.insert(key.into(), next);
220        } else {
221            state.values.remove(key);
222        }
223        Ok(CasOutcome::Applied(version))
224    }
225    async fn counter(&self, key: &str) -> Result<Option<Counter>> {
226        self.0.options.limits.key(key)?;
227        let mut state = self.state()?;
228        state.expire_key(key, Instant::now());
229        Ok(state.counters.get(key).map(|v| v.value))
230    }
231    async fn increment(
232        &self,
233        key: &str,
234        amount: u64,
235        limit: u64,
236        ttl: Duration,
237    ) -> Result<IncrementOutcome> {
238        self.0.options.limits.key(key)?;
239        counter_args(amount, limit)?;
240        let expires = Self::expiry(ttl)?;
241        let mut state = self.state()?;
242        state.expire_key(key, Instant::now());
243        let current = state.counters.get(key).map_or(0, |v| v.value.value);
244        if amount > limit.saturating_sub(current) {
245            return Ok(IncrementOutcome::Limited { current });
246        }
247        if let Some(entry) = state.counters.get_mut(key) {
248            entry.value.value += amount;
249            return Ok(IncrementOutcome::Applied(entry.value));
250        }
251        state.room(self.0.options.max_keys, Instant::now())?;
252        let counter = Counter {
253            value: amount,
254            generation: Version::fresh()?,
255        };
256        state.counters.insert(
257            key.into(),
258            Timed {
259                value: counter,
260                expires,
261            },
262        );
263        Ok(IncrementOutcome::Applied(counter))
264    }
265    async fn decrement(
266        &self,
267        key: &str,
268        generation: Version,
269        amount: u64,
270    ) -> Result<Option<Counter>> {
271        self.0.options.limits.key(key)?;
272        counter_args(amount, i64::MAX as u64)?;
273        let mut state = self.state()?;
274        state.expire_key(key, Instant::now());
275        let Some(entry) = state
276            .counters
277            .get_mut(key)
278            .filter(|v| v.value.generation == generation)
279        else {
280            return Ok(None);
281        };
282        if entry.value.value < amount {
283            return Err(CacheError::Underflow);
284        }
285        entry.value.value -= amount;
286        Ok(Some(entry.value))
287    }
288    async fn acquire_permit(
289        &self,
290        key: &str,
291        limit: u32,
292        ttl: Duration,
293    ) -> Result<Option<Version>> {
294        self.0.options.limits.key(key)?;
295        self.0.options.limits.permits(limit)?;
296        let expires = Self::expiry(ttl)?;
297        let mut state = self.state()?;
298        state.expire_key(key, Instant::now());
299        if state
300            .permits
301            .get(key)
302            .is_some_and(|v| v.len() >= limit as usize)
303        {
304            return Ok(None);
305        }
306        if !state.permits.contains_key(key) {
307            state.room(self.0.options.max_keys, Instant::now())?;
308        }
309        let owner = Version::fresh()?;
310        state
311            .permits
312            .entry(key.into())
313            .or_default()
314            .insert(owner, expires);
315        Ok(Some(owner))
316    }
317    async fn renew_permit(&self, key: &str, owner: Version, ttl: Duration) -> Result<bool> {
318        self.0.options.limits.key(key)?;
319        let expires = Self::expiry(ttl)?;
320        let mut state = self.state()?;
321        state.expire_key(key, Instant::now());
322        let Some(deadline) = state.permits.get_mut(key).and_then(|v| v.get_mut(&owner)) else {
323            return Ok(false);
324        };
325        *deadline = expires;
326        Ok(true)
327    }
328    async fn release_permit(&self, key: &str, owner: Version) -> Result<bool> {
329        self.0.options.limits.key(key)?;
330        let mut state = self.state()?;
331        state.expire_key(key, Instant::now());
332        let released = state
333            .permits
334            .get_mut(key)
335            .is_some_and(|v| v.remove(&owner).is_some());
336        if state.permits.get(key).is_some_and(HashMap::is_empty) {
337            state.permits.remove(key);
338        }
339        Ok(released)
340    }
341    async fn publish(&self, topic: &str, payload: Vec<u8>) -> Result<()> {
342        self.0.options.limits.key(topic)?;
343        self.0.options.limits.value(&payload)?;
344        if let Some(sender) = self.state()?.topics.get(topic) {
345            let _ = sender.send(payload);
346        }
347        Ok(())
348    }
349    async fn subscribe(&self, topic: &str) -> Result<Box<dyn Subscription>> {
350        self.0.options.limits.key(topic)?;
351        let mut state = self.state()?;
352        state.topics.retain(|_, sender| sender.receiver_count() > 0);
353        if !state.topics.contains_key(topic) && state.topics.len() >= self.0.options.max_topics {
354            return Err(CacheError::Capacity);
355        }
356        let sender = state
357            .topics
358            .entry(topic.into())
359            .or_insert_with(|| broadcast::channel(self.0.options.limits.notification_capacity).0);
360        Ok(Box::new(MemorySubscription {
361            receiver: sender.subscribe(),
362            initial: true,
363        }))
364    }
365}
366struct MemorySubscription {
367    receiver: broadcast::Receiver<Vec<u8>>,
368    initial: bool,
369}
370#[async_trait::async_trait]
371impl Subscription for MemorySubscription {
372    async fn recv(&mut self) -> Result<Notification> {
373        if self.initial {
374            self.initial = false;
375            return Ok(Notification::ResyncRequired);
376        }
377        match self.receiver.recv().await {
378            Ok(value) => Ok(Notification::Message(value)),
379            Err(broadcast::error::RecvError::Lagged(_)) => Ok(Notification::ResyncRequired),
380            Err(broadcast::error::RecvError::Closed) => Err(CacheError::Closed),
381        }
382    }
383}