Files
uv/crates/once-map/src/lib.rs
T

91 lines
2.9 KiB
Rust
Raw Normal View History

use std::borrow::Borrow;
use std::hash::Hash;
2024-01-30 12:25:22 -08:00
use std::sync::Arc;
2024-01-30 12:25:22 -08:00
use dashmap::DashMap;
use tokio::sync::Notify;
/// Run tasks only once and store the results in a parallel hash map.
///
/// We often have jobs `Fn(K) -> V` that we only want to run once and memoize, e.g. network
/// requests for metadata. When multiple tasks start the same query in parallel, e.g. through source
/// dist builds, we want to wait until the other task is done and get a reference to the same
/// result.
2024-04-24 11:11:46 -04:00
///
/// Note that this always clones the value out of the underlying map. Because
/// of this, it's common to wrap the `V` in an `Arc<V>` to make cloning cheap.
pub struct OnceMap<K, V> {
2024-01-30 12:25:22 -08:00
items: DashMap<K, Value<V>>,
}
2024-04-24 11:11:46 -04:00
impl<K: Eq + Hash, V: Clone> OnceMap<K, V> {
/// Register that you want to start a job.
///
/// If this method returns `true`, you need to start a job and call [`OnceMap::done`] eventually
/// or other tasks will hang. If it returns `false`, this job is already in progress and you
/// can [`OnceMap::wait`] for the result.
2024-01-30 12:25:22 -08:00
pub fn register(&self, key: K) -> bool {
let entry = self.items.entry(key);
match entry {
dashmap::mapref::entry::Entry::Occupied(_) => false,
dashmap::mapref::entry::Entry::Vacant(entry) => {
entry.insert(Value::Waiting(Arc::new(Notify::new())));
true
}
}
2024-01-03 00:21:46 -04:00
}
/// Submit the result of a job you registered.
pub fn done(&self, key: K, value: V) {
2024-04-24 11:11:46 -04:00
if let Some(Value::Waiting(notify)) = self.items.insert(key, Value::Filled(value)) {
2024-01-30 12:25:22 -08:00
notify.notify_waiters();
}
}
/// Wait for the result of a job that is running.
///
/// Will hang if [`OnceMap::done`] isn't called for this key.
2024-04-24 11:11:46 -04:00
pub async fn wait(&self, key: &K) -> Option<V> {
2024-01-30 12:25:22 -08:00
let entry = self.items.get(key)?;
match entry.value() {
Value::Filled(value) => Some(value.clone()),
Value::Waiting(notify) => {
let notify = notify.clone();
drop(entry);
notify.notified().await;
let entry = self.items.get(key).expect("map is append-only");
match entry.value() {
Value::Filled(value) => Some(value.clone()),
Value::Waiting(_) => unreachable!("notify was called"),
}
}
}
}
/// Return the result of a previous job, if any.
2024-04-24 11:11:46 -04:00
pub fn get<Q: ?Sized + Hash + Eq>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
{
2024-01-30 12:25:22 -08:00
let entry = self.items.get(key)?;
match entry.value() {
Value::Filled(value) => Some(value.clone()),
Value::Waiting(_) => None,
}
}
}
impl<K: Eq + Hash + Clone, V> Default for OnceMap<K, V> {
fn default() -> Self {
Self {
2024-01-30 12:25:22 -08:00
items: DashMap::new(),
}
}
}
2024-01-30 12:25:22 -08:00
enum Value<V> {
Waiting(Arc<Notify>),
2024-04-24 11:11:46 -04:00
Filled(V),
}