grafbase_sdk/host_io/
cache.rs

1//! A key-value cache shared with all instances of the extension.
2
3use std::{borrow::Cow, time::Duration};
4
5use crate::wit;
6
7/// The cache is a key-value store shared across Wasm instances. As Wasm is single threaded, the
8/// gateway uses a pool of Wasm instances to execute extensions. Cache with the same name will be
9/// the same across those instances and share the same data.
10pub struct Cache {
11    inner: wit::Cache,
12    timeout: Duration,
13}
14
15/// The builder for the cache. It allows to set the name, size, ttl and timeout of the cache.
16pub struct CacheBuilder {
17    name: Cow<'static, str>,
18    size: usize,
19    ttl: Option<Duration>,
20    timeout: Duration,
21}
22
23impl CacheBuilder {
24    /// Time to live for cached entries.
25    pub fn time_to_live(mut self, ttl: Option<Duration>) -> Self {
26        self.ttl = ttl;
27        self
28    }
29
30    /// The default timeout to use when retrieving data from the cache.
31    pub fn timeout(mut self, timeout: Duration) -> Self {
32        self.timeout = timeout;
33        self
34    }
35
36    /// Builds the cache
37    pub fn build(self) -> Cache {
38        let Self {
39            name,
40            size,
41            ttl,
42            timeout,
43        } = self;
44        Cache {
45            inner: wit::Cache::init(&name, size as u32, ttl.map(|d| d.as_millis() as u64)),
46            timeout,
47        }
48    }
49}
50
51impl Cache {
52    /// Creates a new cache builder with the given name and size.
53    /// Caches are unique for a given name and extension.
54    pub fn builder(name: impl Into<Cow<'static, str>>, size: usize) -> CacheBuilder {
55        CacheBuilder {
56            name: name.into(),
57            size,
58            ttl: None,
59            timeout: Duration::from_secs(5),
60        }
61    }
62
63    /// Retrieves a value from the cache by key or initialize it with the provided function using
64    /// the default timeout. See [get_or_init_with_timeout](Cache::get_or_init_with_timeout) for more details
65    pub fn get_or_insert<T, E>(
66        &self,
67        key: &str,
68        f: impl FnOnce() -> Result<(T, Vec<u8>), E>,
69    ) -> Result<(Option<T>, Vec<u8>), E> {
70        self.get_or_insert_with_timeout(key, self.timeout, f)
71    }
72
73    /// Retrieves a value from the cache by key or initialize it with the provided function.
74    /// If there is no existing value in the cache, the callback function will be immediately
75    /// called to fill the cache. All further calls during the callback execution will wait for
76    /// the value to be computed. As the callback might crash, a timeout limits how long this
77    /// function will wait. Unfortunately it does result in a thundering herd problem where all
78    /// Wasm instances will try to compute the value at the same time.
79    pub fn get_or_insert_with_timeout<T, E>(
80        &self,
81        key: &str,
82        timeout: Duration,
83        f: impl FnOnce() -> Result<(T, Vec<u8>), E>,
84    ) -> Result<(Option<T>, Vec<u8>), E> {
85        if let Some(value) = self.inner.get_or_reserve(key, timeout.as_millis() as u64) {
86            return Ok((None, value));
87        }
88        let (value, bytes) = f()?;
89        self.inner.insert(key, &bytes);
90        Ok((Some(value), bytes))
91    }
92}