Skip to main content

shared_framework/data/cache/
mod.rs

1//! Key-value caching with in-memory and Redis backends.
2//!
3//! Provides the [`StorageAccess`] trait for get/put/invalidate/clear, the
4//! [`InMemoryStorageAccess`] process-local map, the [`RedisStorage`] async
5//! Redis helper, the region-scoped [`RedisStorageAccess`] JSON cache with TTL,
6//! and the [`InMemoryRegionFactory`]/[`RedisRegionFactory`] region registries.
7//!
8//! Use the in-memory backend for single-process caches and the Redis backend
9//! for shared caches; use region factories when separate namespaces are needed.
10
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13
14/// Selects which cache backend is configured.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum CachingStrategy {
17    /// Caching is explicitly disabled.
18    Disabled,
19    /// Use the Redis backend.
20    Redis,
21    /// Use the process-local in-memory backend.
22    InMemory,
23    /// No backend selected.
24    None,
25}
26
27/// Synchronous key-value cache interface.
28///
29/// `K` is the key type; `V` is the value type.
30pub trait StorageAccess<K, V>: Send + Sync
31where
32    K: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
33    V: Clone + Send + Sync + 'static,
34{
35    /// Returns the value for `key`, or `None` when absent.
36    fn get(&self, key: &K) -> Option<V>;
37    /// Stores `value` under `key`, overwriting any existing entry.
38    fn put(&self, key: K, value: V);
39    /// Removes the entry for `key`, if present.
40    fn invalidate(&self, key: &K);
41    /// Returns true when a value is present for `key`.
42    fn contains(&self, key: &K) -> bool {
43        self.get(key).is_some()
44    }
45    /// Removes all entries.
46    fn clear(&self);
47}
48
49// ── InMemory ─────────────────────────────────────────────────────────────────
50
51/// Process-local cache backed by a locked hash map.
52///
53/// `K` is the key type; `V` is the value type. When the map reaches capacity,
54/// inserting a new key first removes one arbitrary existing entry.
55pub struct InMemoryStorageAccess<K, V>
56where
57    K: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
58    V: Clone + Send + Sync + 'static,
59{
60    cache: Arc<Mutex<HashMap<K, V>>>,
61    max_capacity: usize,
62}
63
64impl<K, V> InMemoryStorageAccess<K, V>
65where
66    K: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
67    V: Clone + Send + Sync + 'static,
68{
69    /// Creates an empty cache holding up to `max_capacity` entries.
70    pub fn new(max_capacity: u64) -> Self {
71        Self {
72            cache: Arc::new(Mutex::new(HashMap::new())),
73            max_capacity: max_capacity as usize,
74        }
75    }
76
77    /// Creates an empty cache with capacity 1024; the region name is ignored.
78    pub fn for_region(_region: &str) -> Self {
79        Self::new(1024)
80    }
81}
82
83impl<K, V> StorageAccess<K, V> for InMemoryStorageAccess<K, V>
84where
85    K: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
86    V: Clone + Send + Sync + 'static,
87{
88    fn get(&self, key: &K) -> Option<V> {
89        self.cache.lock().unwrap().get(key).cloned()
90    }
91    fn put(&self, key: K, value: V) {
92        let mut map = self.cache.lock().unwrap();
93        if map.len() >= self.max_capacity {
94            if let Some(k) = map.keys().next().cloned() {
95                map.remove(&k);
96            }
97        }
98        map.insert(key, value);
99    }
100    fn invalidate(&self, key: &K) {
101        self.cache.lock().unwrap().remove(key);
102    }
103    fn clear(&self) {
104        self.cache.lock().unwrap().clear();
105    }
106}
107
108// ── Redis high-level helper ────────────────────────────────────────────────
109
110use redis::{AsyncCommands, Client, RedisResult};
111
112/// Async Redis helper for strings, hashes, lists, sets, and sorted sets.
113///
114/// All operations open a multiplexed connection and return Redis errors to the caller.
115#[derive(Clone)]
116pub struct RedisStorage {
117    client: Client,
118}
119
120impl RedisStorage {
121    /// Connects to the Redis server at `url`.
122    pub fn new(url: &str) -> RedisResult<Self> {
123        Ok(Self {
124            client: Client::open(url)?,
125        })
126    }
127
128    /// Connects using `REDIS_URL` (falling back to `REDIS_URI`, then localhost).
129    pub fn from_env() -> RedisResult<Self> {
130        let url = std::env::var("REDIS_URL")
131            .or_else(|_| std::env::var("REDIS_URI"))
132            .unwrap_or_else(|_| "redis://127.0.0.1:6379/".to_string());
133        Self::new(&url)
134    }
135
136    /// Returns the underlying Redis client.
137    pub fn client(&self) -> &Client {
138        &self.client
139    }
140
141    async fn conn(&self) -> RedisResult<redis::aio::MultiplexedConnection> {
142        self.client.get_multiplexed_async_connection().await
143    }
144
145    // ── Key operations ────────────────────────────────────────────────────
146
147    /// Gets the string value for `key`, or `None` when absent.
148    pub async fn get_value(&self, key: &str) -> RedisResult<Option<String>> {
149        let mut conn = self.conn().await?;
150        conn.get(key).await
151    }
152
153    /// Sets the string value for `key`.
154    pub async fn set_value(&self, key: &str, value: &str) -> RedisResult<()> {
155        let mut conn = self.conn().await?;
156        conn.set::<_, _, ()>(key, value).await
157    }
158
159    /// Sets the string value for `key` with a TTL of `seconds`.
160    pub async fn set_value_with_expiration(&self, key: &str, value: &str, seconds: u64) -> RedisResult<()> {
161        let mut conn = self.conn().await?;
162        conn.set_ex::<_, _, ()>(key, value, seconds).await
163    }
164
165    /// Sets `key` only when absent; returns true when the key was set.
166    pub async fn set_value_if_absent(&self, key: &str, value: &str) -> RedisResult<bool> {
167        let mut conn = self.conn().await?;
168        // SET key value NX — returns OK or nil
169        let res: Option<String> = redis::cmd("SET")
170            .arg(key)
171            .arg(value)
172            .arg("NX")
173            .query_async(&mut conn)
174            .await?;
175        Ok(res.is_some())
176    }
177
178    /// Deletes `key` and returns the number of keys removed.
179    pub async fn delete_key(&self, key: &str) -> RedisResult<i64> {
180        let mut conn = self.conn().await?;
181        conn.del(key).await
182    }
183
184    /// Lazily frees `key` with `UNLINK` and returns the number of keys removed.
185    pub async fn unlink_key(&self, key: &str) -> RedisResult<i64> {
186        let mut conn = self.conn().await?;
187        redis::cmd("UNLINK").arg(key).query_async(&mut conn).await
188    }
189
190    /// Deletes all given keys and returns the number removed; returns 0 for an empty list.
191    pub async fn delete_keys(&self, keys: &[String]) -> RedisResult<i64> {
192        if keys.is_empty() {
193            return Ok(0);
194        }
195        let mut conn = self.conn().await?;
196        conn.del(keys).await
197    }
198
199    /// Returns true when `key` exists.
200    pub async fn key_exists(&self, key: &str) -> RedisResult<bool> {
201        let mut conn = self.conn().await?;
202        let v: i64 = conn.exists(key).await?;
203        Ok(v == 1)
204    }
205
206    /// Increments the integer at `key` by 1 and returns the new value.
207    pub async fn increment_value(&self, key: &str) -> RedisResult<i64> {
208        let mut conn = self.conn().await?;
209        conn.incr(key, 1i64).await
210    }
211
212    /// Increments the integer at `key` by `delta` and returns the new value.
213    pub async fn increment_value_by(&self, key: &str, delta: i64) -> RedisResult<i64> {
214        let mut conn = self.conn().await?;
215        conn.incr(key, delta).await
216    }
217
218    /// Decrements the integer at `key` by 1 and returns the new value.
219    pub async fn decrement_value(&self, key: &str) -> RedisResult<i64> {
220        let mut conn = self.conn().await?;
221        conn.decr(key, 1i64).await
222    }
223
224    /// Decrements the integer at `key` by `delta` and returns the new value.
225    pub async fn decrement_value_by(&self, key: &str, delta: i64) -> RedisResult<i64> {
226        let mut conn = self.conn().await?;
227        conn.decr(key, delta).await
228    }
229
230    /// Returns seconds since `key` was last accessed (`OBJECT IDLETIME`).
231    pub async fn idle_time(&self, key: &str) -> RedisResult<i64> {
232        let mut conn = self.conn().await?;
233        redis::cmd("OBJECT")
234            .arg("IDLETIME")
235            .arg(key)
236            .query_async(&mut conn)
237            .await
238    }
239
240    /// Sets the TTL of `key` to `seconds`; returns true when the timeout was set.
241    pub async fn set_expiration(&self, key: &str, seconds: u64) -> RedisResult<bool> {
242        let mut conn = self.conn().await?;
243        let v: i64 = conn.expire(key, seconds as i64).await?;
244        Ok(v == 1)
245    }
246
247    /// Returns the TTL of `key` in seconds.
248    pub async fn get_time_to_live(&self, key: &str) -> RedisResult<i64> {
249        let mut conn = self.conn().await?;
250        conn.ttl(key).await
251    }
252
253    /// Gets the values for several keys at once; missing keys yield `None`. Empty input returns empty output.
254    pub async fn get_multiple_values(&self, keys: &[String]) -> RedisResult<Vec<Option<String>>> {
255        if keys.is_empty() {
256            return Ok(vec![]);
257        }
258        let mut conn = self.conn().await?;
259        conn.mget(keys).await
260    }
261
262    /// Sets several key-value pairs at once with `MSET`; does nothing for empty input.
263    pub async fn set_multiple_values(&self, kv: &HashMap<String, String>) -> RedisResult<()> {
264        if kv.is_empty() {
265            return Ok(());
266        }
267        let mut conn = self.conn().await?;
268        // MSET expects flat list of key, value, key, value
269        let mut args: Vec<String> = Vec::with_capacity(kv.len() * 2);
270        for (k, v) in kv {
271            args.push(k.clone());
272            args.push(v.clone());
273        }
274        // Use pipe for MSET
275        redis::cmd("MSET").arg(args).query_async::<()>(&mut conn).await?;
276        Ok(())
277    }
278
279    // ── Hash operations ───────────────────────────────────────────────────
280
281    /// Gets a hash field value, or `None` when the key or field is absent.
282    pub async fn get_hash_value(&self, key: &str, field: &str) -> RedisResult<Option<String>> {
283        let mut conn = self.conn().await?;
284        conn.hget(key, field).await
285    }
286
287    /// Sets a hash field value.
288    pub async fn set_hash_value(&self, key: &str, field: &str, value: &str) -> RedisResult<()> {
289        let mut conn = self.conn().await?;
290        conn.hset::<_, _, _, ()>(key, field, value).await
291    }
292
293    /// Deletes a hash field and returns the number of fields removed.
294    pub async fn delete_hash_field(&self, key: &str, field: &str) -> RedisResult<i64> {
295        let mut conn = self.conn().await?;
296        conn.hdel(key, field).await
297    }
298
299    /// Sets several hash fields at once; does nothing for empty input.
300    pub async fn set_hash_values(&self, key: &str, field_values: &HashMap<String, String>) -> RedisResult<()> {
301        if field_values.is_empty() {
302            return Ok(());
303        }
304        let mut conn = self.conn().await?;
305        // HSET key field value [field value ...]
306        let mut cmd = redis::cmd("HSET");
307        cmd.arg(key);
308        for (f, v) in field_values {
309            cmd.arg(f).arg(v);
310        }
311        cmd.query_async::<()>(&mut conn).await?;
312        Ok(())
313    }
314
315    /// Sets a hash field only when absent; returns true when the field was set.
316    pub async fn set_hash_value_if_absent(&self, key: &str, field: &str, value: &str) -> RedisResult<bool> {
317        let mut conn = self.conn().await?;
318        let v: i64 = conn.hset_nx(key, field, value).await?;
319        Ok(v == 1)
320    }
321
322    /// Increments a hash integer field by `delta` and returns the new value.
323    pub async fn increment_hash_field(&self, key: &str, field: &str, delta: i64) -> RedisResult<i64> {
324        let mut conn = self.conn().await?;
325        conn.hincr(key, field, delta).await
326    }
327
328    /// Decrements a hash integer field by `delta` and returns the new value.
329    pub async fn decrement_hash_field(&self, key: &str, field: &str, delta: i64) -> RedisResult<i64> {
330        self.increment_hash_field(key, field, -delta).await
331    }
332
333    /// Returns all fields and values of a hash.
334    pub async fn get_all_hash_fields(&self, key: &str) -> RedisResult<HashMap<String, String>> {
335        let mut conn = self.conn().await?;
336        conn.hgetall(key).await
337    }
338
339    /// Returns all field names of a hash.
340    pub async fn get_hash_keys(&self, key: &str) -> RedisResult<Vec<String>> {
341        let mut conn = self.conn().await?;
342        conn.hkeys(key).await
343    }
344
345    /// Returns all values of a hash.
346    pub async fn get_hash_values(&self, key: &str) -> RedisResult<Vec<String>> {
347        let mut conn = self.conn().await?;
348        conn.hvals(key).await
349    }
350
351    /// Returns true when a hash field exists.
352    pub async fn hash_field_exists(&self, key: &str, field: &str) -> RedisResult<bool> {
353        let mut conn = self.conn().await?;
354        let v: bool = conn.hexists(key, field).await?;
355        Ok(v)
356    }
357
358    // ── List operations ───────────────────────────────────────────────────
359
360    /// Prepends `value` to the list at `key` and returns the new length.
361    pub async fn push_to_list_start(&self, key: &str, value: &str) -> RedisResult<i64> {
362        let mut conn = self.conn().await?;
363        conn.lpush(key, value).await
364    }
365
366    /// Appends `value` to the list at `key` and returns the new length.
367    pub async fn push_to_list_end(&self, key: &str, value: &str) -> RedisResult<i64> {
368        let mut conn = self.conn().await?;
369        conn.rpush(key, value).await
370    }
371
372    /// Removes and returns the first element of the list, or `None` when empty.
373    pub async fn pop_from_list_start(&self, key: &str) -> RedisResult<Option<String>> {
374        let mut conn = self.conn().await?;
375        conn.lpop(key, None).await
376    }
377
378    /// Removes and returns the last element of the list, or `None` when empty.
379    pub async fn pop_from_list_end(&self, key: &str) -> RedisResult<Option<String>> {
380        let mut conn = self.conn().await?;
381        conn.rpop(key, None).await
382    }
383
384    /// Returns the length of the list at `key`.
385    pub async fn get_list_length(&self, key: &str) -> RedisResult<i64> {
386        let mut conn = self.conn().await?;
387        conn.llen(key).await
388    }
389
390    /// Returns list elements from `start` to `stop` inclusive.
391    pub async fn get_list_range(&self, key: &str, start: i64, stop: i64) -> RedisResult<Vec<String>> {
392        let mut conn = self.conn().await?;
393        conn.lrange(key, start as isize, stop as isize).await
394    }
395
396    // ── Set operations ────────────────────────────────────────────────────
397
398    /// Adds `member` to the set and returns the number of members added.
399    pub async fn add_to_set(&self, key: &str, member: &str) -> RedisResult<i64> {
400        let mut conn = self.conn().await?;
401        conn.sadd(key, member).await
402    }
403
404    /// Removes `member` from the set and returns the number of members removed.
405    pub async fn remove_from_set(&self, key: &str, member: &str) -> RedisResult<i64> {
406        let mut conn = self.conn().await?;
407        conn.srem(key, member).await
408    }
409
410    /// Returns all members of the set.
411    pub async fn get_set_members(&self, key: &str) -> RedisResult<Vec<String>> {
412        let mut conn = self.conn().await?;
413        conn.smembers(key).await
414    }
415
416    /// Returns true when `member` belongs to the set.
417    pub async fn is_set_member(&self, key: &str, member: &str) -> RedisResult<bool> {
418        let mut conn = self.conn().await?;
419        conn.sismember(key, member).await
420    }
421
422    /// Returns the number of members in the set.
423    pub async fn get_set_size(&self, key: &str) -> RedisResult<i64> {
424        let mut conn = self.conn().await?;
425        conn.scard(key).await
426    }
427
428    // ── Sorted set operations ─────────────────────────────────────────────
429
430    /// Adds `member` with `score` to the sorted set and returns the number of members added.
431    pub async fn add_to_sorted_set(&self, key: &str, score: f64, member: &str) -> RedisResult<i64> {
432        let mut conn = self.conn().await?;
433        conn.zadd(key, member, score).await
434    }
435
436    /// Returns sorted-set members from `start` to `stop` inclusive, by ascending score.
437    pub async fn get_sorted_set_range(&self, key: &str, start: i64, stop: i64) -> RedisResult<Vec<String>> {
438        let mut conn = self.conn().await?;
439        conn.zrange(key, start as isize, stop as isize).await
440    }
441
442    /// Removes `member` from the sorted set and returns the number of members removed.
443    pub async fn remove_from_sorted_set(&self, key: &str, member: &str) -> RedisResult<i64> {
444        let mut conn = self.conn().await?;
445        conn.zrem(key, member).await
446    }
447
448    /// Returns the score of `member`, or `None` when absent.
449    pub async fn get_sorted_set_score(&self, key: &str, member: &str) -> RedisResult<Option<f64>> {
450        let mut conn = self.conn().await?;
451        conn.zscore(key, member).await
452    }
453
454    /// Returns the number of members in the sorted set.
455    pub async fn get_sorted_set_size(&self, key: &str) -> RedisResult<i64> {
456        let mut conn = self.conn().await?;
457        conn.zcard(key).await
458    }
459
460    // ── Other key operations ──────────────────────────────────────────────
461
462    /// Removes the expiration from `key`; returns true when a timeout was removed.
463    pub async fn remove_expiration(&self, key: &str) -> RedisResult<bool> {
464        let mut conn = self.conn().await?;
465        let v: i64 = redis::cmd("PERSIST").arg(key).query_async(&mut conn).await?;
466        Ok(v == 1)
467    }
468
469    /// Renames `old_key` to `new_key`, overwriting any existing destination.
470    pub async fn rename_key(&self, old_key: &str, new_key: &str) -> RedisResult<()> {
471        let mut conn = self.conn().await?;
472        redis::cmd("RENAME").arg(old_key).arg(new_key).query_async::<()>(&mut conn).await?;
473        Ok(())
474    }
475
476    /// Collects keys matching `pattern` with a non-blocking incremental scan.
477    ///
478    /// `count` is the `COUNT` hint for each scan round.
479    pub async fn scan_keys(&self, pattern: &str, count: usize) -> RedisResult<Vec<String>> {
480        let mut conn = self.conn().await?;
481        let mut cursor: u64 = 0;
482        let mut all = Vec::new();
483        loop {
484            let (next_cursor, keys): (u64, Vec<String>) = redis::cmd("SCAN")
485                .arg(cursor)
486                .arg("MATCH")
487                .arg(pattern)
488                .arg("COUNT")
489                .arg(count)
490                .query_async(&mut conn)
491                .await?;
492            all.extend(keys);
493            if next_cursor == 0 {
494                break;
495            }
496            cursor = next_cursor;
497        }
498        Ok(all)
499    }
500
501    /// Scans keys matching `pattern` with a count hint of 250.
502    pub async fn scan_keys_default(&self, pattern: &str) -> RedisResult<Vec<String>> {
503        self.scan_keys(pattern, 250).await
504    }
505
506    /// Deprecated alias for [`RedisStorage::scan_keys_default`]; prefer `scan_keys` to avoid blocking Redis.
507    #[deprecated(note = "Use scan_keys instead to avoid blocking Redis")]
508    pub async fn find_keys(&self, pattern: &str) -> RedisResult<Vec<String>> {
509        self.scan_keys_default(pattern).await
510    }
511}
512
513// ── Redis second-level cache ───────────────────────────────────────────────
514
515/// Region-scoped Redis cache storing JSON-serialized values with a TTL.
516///
517/// Each region namespaces its keys with a per-region prefix; entries expire
518/// after the configured TTL (default 3600 seconds).
519pub struct RedisStorageAccess {
520    storage: RedisStorage,
521    prefix: String,
522    ttl_seconds: u64,
523}
524
525impl RedisStorageAccess {
526    /// Creates a region cache over the given storage with the default 3600-second TTL.
527    pub fn new(storage: RedisStorage, region_name: &str) -> Self {
528        Self {
529            storage,
530            prefix: format!("hibernate:cache:{}:", region_name),
531            ttl_seconds: 3600,
532        }
533    }
534
535    /// Creates a region cache from a Redis URL and region name.
536    pub fn from_url(url: &str, region_name: &str) -> RedisResult<Self> {
537        Ok(Self::new(RedisStorage::new(url)?, region_name))
538    }
539
540    /// Sets the TTL applied to written entries.
541    pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
542        self.ttl_seconds = ttl_seconds;
543        self
544    }
545
546    fn build_key<K: ToString>(&self, key: &K) -> String {
547        format!("{}{}", self.prefix, key.to_string())
548    }
549
550    fn serialize<T: serde::Serialize>(value: &T) -> Result<Vec<u8>, serde_json::Error> {
551        // JSON bytes for portability.
552        serde_json::to_vec(value)
553    }
554
555    fn deserialize<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T, serde_json::Error> {
556        serde_json::from_slice(bytes)
557    }
558
559    // ── async API (primary) ────────────────────────────────────────────
560
561    /// Returns true when a cached entry exists for `key`.
562    pub async fn contains_async<K: ToString + Send + Sync>(&self, key: &K) -> RedisResult<bool> {
563        self.storage.key_exists(&self.build_key(key)).await
564    }
565
566    /// Reads and JSON-decodes the entry for `key`; returns `None` when absent or undecodable.
567    ///
568    /// `K` is the key type; `V` is the value type.
569    pub async fn get_from_cache_async<K, V>(&self, key: &K) -> RedisResult<Option<V>>
570    where
571        K: ToString + Send + Sync,
572        V: serde::de::DeserializeOwned,
573    {
574        let raw: Option<Vec<u8>> = {
575            let mut conn = self.storage.conn().await?;
576            let k = self.build_key(key);
577            conn.get(k).await?
578        };
579        match raw {
580            None => Ok(None),
581            Some(bytes) => match Self::deserialize::<V>(&bytes) {
582                Ok(v) => Ok(Some(v)),
583                Err(_) => Ok(None),
584            },
585        }
586    }
587
588    /// JSON-encodes `value` and stores it under `key` with the region TTL.
589    ///
590    /// `K` is the key type; `V` is the value type. Returns a Redis error when serialization fails.
591    pub async fn put_into_cache_async<K, V>(&self, key: &K, value: &V) -> RedisResult<()>
592    where
593        K: ToString + Send + Sync,
594        V: serde::Serialize,
595    {
596        let bytes = Self::serialize(value).map_err(|e| {
597            redis::RedisError::from((
598                redis::ErrorKind::Io,
599                "serialization failed",
600                e.to_string(),
601            ))
602        })?;
603        let mut conn = self.storage.conn().await?;
604        let k = self.build_key(key);
605        // Use SETEX via `set_ex`
606        conn.set_ex::<_, _, ()>(k, bytes, self.ttl_seconds).await
607    }
608
609    /// Removes the entry for `key`, if present.
610    pub async fn remove_from_cache_async<K: ToString + Send + Sync>(&self, key: &K) -> RedisResult<()> {
611        self.storage.unlink_key(&self.build_key(key)).await.map(|_| ())
612    }
613
614    /// Removes all entries in this region via scan plus unlink.
615    pub async fn clear_cache_async(&self) -> RedisResult<()> {
616        let pattern = format!("{}*", self.prefix);
617        let keys = self.storage.scan_keys(&pattern, 750).await?;
618        if keys.is_empty() {
619            return Ok(());
620        }
621        let mut conn = self.storage.conn().await?;
622        for key in keys {
623            let _: () = redis::cmd("UNLINK").arg(key).query_async(&mut conn).await?;
624        }
625        Ok(())
626    }
627
628    // ── sync wrappers for `StorageAccess` trait (blocking) ─────────────
629
630    fn block_on<F: Future>(fut: F) -> F::Output {
631        // If we're inside a tokio runtime, block_in_place; otherwise block_on a new runtime.
632        if let Ok(handle) = tokio::runtime::Handle::try_current() {
633            tokio::task::block_in_place(|| handle.block_on(fut))
634        } else {
635            tokio::runtime::Builder::new_current_thread()
636                .enable_all()
637                .build()
638                .unwrap()
639                .block_on(fut)
640        }
641    }
642}
643
644impl<K, V> StorageAccess<K, V> for RedisStorageAccess
645where
646    K: std::hash::Hash + Eq + Clone + ToString + Send + Sync + 'static,
647    V: Clone + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static,
648{
649    fn get(&self, key: &K) -> Option<V> {
650        Self::block_on(self.get_from_cache_async(key)).ok().flatten()
651    }
652
653    fn put(&self, key: K, value: V) {
654        let _ = Self::block_on(self.put_into_cache_async(&key, &value));
655    }
656
657    fn invalidate(&self, key: &K) {
658        let _ = Self::block_on(self.remove_from_cache_async(key));
659    }
660
661    fn clear(&self) {
662        let _ = Self::block_on(self.clear_cache_async());
663    }
664}
665
666// ── Region factory ─────────────────────────────────────────────────────────
667
668use std::collections::hash_map::Entry;
669
670/// Registry of named in-memory regions sharing one factory.
671///
672/// Each region is a separate [`InMemoryStorageAccess`] over serialized bytes.
673pub struct InMemoryRegionFactory {
674    regions: Mutex<HashMap<String, Arc<InMemoryStorageAccess<String, Vec<u8>>>>>,
675}
676
677impl InMemoryRegionFactory {
678    /// Creates an empty region registry.
679    pub fn new() -> Self {
680        Self {
681            regions: Mutex::new(HashMap::new()),
682        }
683    }
684
685    /// Returns the region for `region_name`, creating it on first use.
686    pub fn get_or_create(&self, region_name: &str) -> Arc<InMemoryStorageAccess<String, Vec<u8>>> {
687        let mut map = self.regions.lock().unwrap();
688        match map.entry(region_name.to_string()) {
689            Entry::Occupied(o) => o.get().clone(),
690            Entry::Vacant(v) => {
691                let access = Arc::new(InMemoryStorageAccess::for_region(region_name));
692                v.insert(access.clone());
693                access
694            }
695        }
696    }
697
698    /// Clears every region in the registry.
699    pub fn clear_all(&self) {
700        let map = self.regions.lock().unwrap();
701        for access in map.values() {
702            access.clear();
703        }
704    }
705}
706
707impl Default for InMemoryRegionFactory {
708    fn default() -> Self {
709        Self::new()
710    }
711}
712
713/// Registry of named Redis regions sharing one [`RedisStorage`].
714pub struct RedisRegionFactory {
715    storage: RedisStorage,
716    regions: Mutex<HashMap<String, Arc<RedisStorageAccess>>>,
717}
718
719impl RedisRegionFactory {
720    /// Creates a region registry over the given storage.
721    pub fn new(storage: RedisStorage) -> Self {
722        Self {
723            storage,
724            regions: Mutex::new(HashMap::new()),
725        }
726    }
727
728    /// Creates a region registry from a Redis URL.
729    pub fn from_url(url: &str) -> RedisResult<Self> {
730        Ok(Self::new(RedisStorage::new(url)?))
731    }
732
733    /// Returns the region for `region_name`, creating it on first use.
734    pub fn get_or_create(&self, region_name: &str) -> Arc<RedisStorageAccess> {
735        let mut map = self.regions.lock().unwrap();
736        match map.entry(region_name.to_string()) {
737            Entry::Occupied(o) => o.get().clone(),
738            Entry::Vacant(v) => {
739                let access = Arc::new(RedisStorageAccess::new(self.storage.clone(), region_name));
740                v.insert(access.clone());
741                access
742            }
743        }
744    }
745}