Skip to main content

uv_once_map/
lib.rs

1mod registered;
2
3pub use registered::{RegisteredEntry, RegisteredOnceMap, Registration};
4
5use std::borrow::Borrow;
6use std::fmt::{Debug, Formatter};
7use std::hash::{BuildHasher, Hash, RandomState};
8use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
9
10use papaya::{HashMap, ResizeMode};
11use tokio::sync::Notify;
12
13/// Run tasks only once and store the results in a parallel hash map.
14///
15/// We often have jobs `Fn(K) -> V` that we only want to run once and memoize, e.g. network
16/// requests for metadata. When multiple tasks start the same query in parallel, e.g. through source
17/// dist builds, we want to wait until the other task is done and get a reference to the same
18/// result.
19///
20/// Note that this always clones the value out of the underlying map. Because
21/// of this, it's common to wrap the `V` in an `Arc<V>` to make cloning cheap.
22pub struct OnceMap<K, V, S = RandomState> {
23    items: HashMap<K, Value<V>, S>,
24}
25
26impl<K: Eq + Hash + Debug, V: Debug, S: BuildHasher + Clone> Debug for OnceMap<K, V, S> {
27    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28        Debug::fmt(&self.items, f)
29    }
30}
31
32impl<K: Eq + Hash + Clone, V: Clone, H: BuildHasher + Clone> OnceMap<K, V, H> {
33    /// Register that you want to start a job.
34    ///
35    /// If this method returns `true`, you need to start a job and call [`OnceMap::done`] eventually
36    /// or other tasks will hang. If it returns `false`, this job is already in progress and you
37    /// can call [`OnceMap::register_or_wait`] to wait for the result.
38    fn register(&self, key: K) -> bool {
39        self.items
40            .pin()
41            .try_insert_with(key, || Value::Waiting(Arc::new(Notify::new())))
42            .is_ok()
43    }
44
45    /// Register that you want to start a job, unless it was already started, then wait for its
46    /// result.
47    ///
48    /// Use this method for once-only operations.
49    ///
50    /// Returns `None` if the job needs to be started, otherwise returns the result of the job.
51    ///
52    ///  # Example
53    ///
54    /// ```rust,ignore
55    /// if let Some(response) = cache.register_or_wait(&id).await {
56    ///     response
57    /// } else {
58    ///     let response = fetch(&id).await;
59    ///     cache.done(id, response.clone());
60    ///     response
61    /// }
62    /// ```
63    pub async fn register_or_wait(&self, key: &K) -> Option<V> {
64        let notify = {
65            let items = self.items.pin();
66            match items.try_insert_with(key.clone(), || Value::Waiting(Arc::new(Notify::new()))) {
67                Ok(_) => return None,
68                Err(value) => match value {
69                    Value::Filled(_) => return value.get(),
70                    Value::Waiting(notify) => notify.clone(),
71                },
72            }
73        };
74
75        self.wait_pending(key, notify).await
76    }
77
78    /// Wait for an existing entry without registering a missing key.
79    async fn wait_registered(&self, key: &K) -> Option<V> {
80        let notify = {
81            let items = self.items.pin();
82            match items.get(key)? {
83                value @ Value::Filled(_) => return value.get(),
84                Value::Waiting(notify) => notify.clone(),
85            }
86        };
87        self.wait_pending(key, notify).await
88    }
89
90    async fn wait_pending(&self, key: &K, notify: Arc<Notify>) -> Option<V> {
91        // Register the waiter for calls to `notify_waiters`.
92        let notification = notify.notified();
93
94        // Make sure the value wasn't inserted in-between us checking the map and registering the waiter.
95        if let Some(value) = self.items.pin().get(key).expect("map is append-only").get() {
96            return Some(value);
97        }
98
99        // Wait until the value is inserted.
100        notification.await;
101
102        let items = self.items.pin();
103        let value = items.get(key).expect("map is append-only");
104        match value {
105            Value::Filled(_) => value.get(),
106            Value::Waiting(_) => unreachable!("notify was called"),
107        }
108    }
109
110    /// Submit the result of a job you registered.
111    pub fn done(&self, key: K, value: V) {
112        if let Some(Value::Waiting(notify)) = self.items.pin().insert(key, Value::filled(value)) {
113            notify.notify_waiters();
114        }
115    }
116
117    /// Return the result of a previous job, if any.
118    pub fn get<Q: ?Sized + Hash + Eq>(&self, key: &Q) -> Option<V>
119    where
120        K: Borrow<Q>,
121    {
122        let items = self.items.pin();
123        items.get(key)?.get()
124    }
125
126    /// Remove the result of a previous job, if any.
127    pub fn remove<Q: ?Sized + Hash + Eq>(&self, key: &Q) -> Option<V>
128    where
129        K: Borrow<Q>,
130    {
131        let items = self.items.pin();
132        items.remove(key)?.take()
133    }
134}
135
136impl<K: Eq + Hash + Clone, V, H: Default + BuildHasher + Clone> Default for OnceMap<K, V, H> {
137    fn default() -> Self {
138        Self {
139            items: HashMap::builder()
140                .hasher(H::default())
141                .resize_mode(ResizeMode::Blocking)
142                .build(),
143        }
144    }
145}
146
147impl<K, V, H> FromIterator<(K, V)> for OnceMap<K, V, H>
148where
149    K: Eq + Hash,
150    H: Default + Clone + BuildHasher,
151{
152    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
153        Self {
154            items: iter
155                .into_iter()
156                .map(|(k, v)| (k, Value::filled(v)))
157                .collect(),
158        }
159    }
160}
161
162#[derive(Debug)]
163enum Value<V> {
164    Waiting(Arc<Notify>),
165    /// The mutex is a workaround to papaya always returning borrowed instead of owned values.
166    Filled(Mutex<Option<V>>),
167}
168
169impl<V> Value<V> {
170    fn filled(value: V) -> Self {
171        Self::Filled(Mutex::new(Some(value)))
172    }
173
174    fn lock(value: &Mutex<Option<V>>) -> MutexGuard<'_, Option<V>> {
175        value.lock().unwrap_or_else(PoisonError::into_inner)
176    }
177
178    fn take(&self) -> Option<V> {
179        match self {
180            Self::Filled(value) => Self::lock(value).take(),
181            Self::Waiting(_) => None,
182        }
183    }
184}
185
186impl<V: Clone> Value<V> {
187    fn get(&self) -> Option<V> {
188        match self {
189            Self::Filled(value) => Self::lock(value).clone(),
190            Self::Waiting(_) => None,
191        }
192    }
193}