Skip to main content

vortex_array/
arc_swap_map.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! A concurrent, copy-on-write map backed by an [`ArcSwap`].
5
6use std::borrow::Borrow;
7use std::fmt::Debug;
8use std::fmt::Formatter;
9use std::hash::Hash;
10use std::sync::Arc;
11
12use arc_swap::ArcSwap;
13use vortex_utils::aliases::hash_map::HashMap;
14
15/// A concurrent [`HashMap`] backed by an [`ArcSwap`], offering lock-free reads
16/// and copy-on-write writes.
17///
18/// Reads load the current snapshot without blocking writers. Writes clone the
19/// whole map, apply their change, and atomically publish the new version, so a
20/// reader always observes a consistent snapshot and writers never block readers.
21///
22/// This is the shared building block behind the session-scoped registries (the
23/// optimizer-kernel and aggregate-function registries). Because every write
24/// clones the entire map, it is intended for maps that are written rarely
25/// (typically only while a session is being configured) and read often.
26///
27/// The map is held behind an [`Arc`] so that [`Clone`] shares the same
28/// underlying cell: a registry mutated through one clone is observed by all
29/// others. Session variables rely on this so that encodings registered after a
30/// session is built remain visible to clones of that session.
31pub struct ArcSwapMap<K, V> {
32    inner: Arc<ArcSwap<HashMap<K, V>>>,
33}
34
35impl<K, V> Default for ArcSwapMap<K, V> {
36    fn default() -> Self {
37        Self {
38            inner: Arc::new(ArcSwap::from_pointee(HashMap::default())),
39        }
40    }
41}
42
43impl<K, V> Clone for ArcSwapMap<K, V> {
44    fn clone(&self) -> Self {
45        Self {
46            inner: Arc::clone(&self.inner),
47        }
48    }
49}
50
51impl<K: Debug, V: Debug> Debug for ArcSwapMap<K, V> {
52    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53        self.read(|map| f.debug_tuple("ArcSwapMap").field(map).finish())
54    }
55}
56
57impl<K, V> ArcSwapMap<K, V> {
58    /// Return the currently published map snapshot.
59    pub fn snapshot(&self) -> Arc<HashMap<K, V>> {
60        self.inner.load_full()
61    }
62
63    /// Read the current snapshot, passing it to `f`.
64    ///
65    /// Every lookup inside `f` observes the same snapshot, which matters when a
66    /// single logical read consults more than one key.
67    pub fn read<R>(&self, f: impl FnOnce(&HashMap<K, V>) -> R) -> R {
68        f(&self.inner.load())
69    }
70
71    /// Replace the map with the result of applying `f` to a private copy.
72    ///
73    /// Writes are copy-on-write via [`ArcSwap::rcu`], so `f` may run more than
74    /// once under contention and must not move out of its captures.
75    fn modify(&self, f: impl Fn(&mut HashMap<K, V>))
76    where
77        K: Clone,
78        V: Clone,
79    {
80        self.inner.rcu(|existing| {
81            let mut map = existing.as_ref().clone();
82            f(&mut map);
83            map
84        });
85    }
86}
87
88impl<K: Eq + Hash, V: Clone> ArcSwapMap<K, V> {
89    /// Return a clone of the value stored under `key`, if present.
90    pub fn get<Q>(&self, key: &Q) -> Option<V>
91    where
92        K: Borrow<Q>,
93        Q: Eq + Hash + ?Sized,
94    {
95        self.inner.load().get(key).cloned()
96    }
97
98    /// Insert `value` under `key`, replacing any existing value.
99    pub fn insert(&self, key: K, value: V)
100    where
101        K: Clone,
102    {
103        self.modify(|map| {
104            map.insert(key.clone(), value.clone());
105        });
106    }
107}
108
109impl<K: Eq + Hash + Clone, T: Clone> ArcSwapMap<K, Arc<[T]>> {
110    /// Append `values` to the list stored under `key`, creating it if absent.
111    ///
112    /// Each key maps to an immutable `Arc<[T]>`; appending rebuilds that slice
113    /// copy-on-write so existing readers keep their previous snapshot.
114    pub fn extend(&self, key: K, values: &[T]) {
115        self.modify(|map| {
116            let merged: Arc<[T]> = match map.get(&key) {
117                Some(existing) => existing.iter().chain(values).cloned().collect(),
118                None => values.into(),
119            };
120            map.insert(key.clone(), merged);
121        });
122    }
123
124    /// Append a single `value` to the list stored under `key`, creating it if
125    /// absent.
126    pub fn push(&self, key: K, value: T) {
127        self.extend(key, &[value]);
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn get_and_insert() {
137        let map = ArcSwapMap::<u32, i32>::default();
138        assert_eq!(map.get(&1), None);
139        map.insert(1, 10);
140        map.insert(1, 20);
141        assert_eq!(map.get(&1), Some(20));
142    }
143
144    #[test]
145    fn extend_appends_per_key() {
146        let map = ArcSwapMap::<u32, Arc<[i32]>>::default();
147        map.extend(1, &[1, 2]);
148        map.extend(1, &[3]);
149        map.extend(2, &[4]);
150        assert_eq!(map.get(&1).as_deref(), Some([1, 2, 3].as_slice()));
151        assert_eq!(map.get(&2).as_deref(), Some([4].as_slice()));
152    }
153
154    #[test]
155    fn push_appends_single_values() {
156        let map = ArcSwapMap::<u32, Arc<[i32]>>::default();
157        map.push(1, 1);
158        map.push(1, 2);
159        assert_eq!(map.get(&1).as_deref(), Some([1, 2].as_slice()));
160    }
161
162    #[test]
163    fn read_observes_a_single_snapshot() {
164        let map = ArcSwapMap::<u32, i32>::default();
165        map.insert(1, 1);
166        map.insert(2, 2);
167        assert_eq!(map.read(|m| m.values().sum::<i32>()), 3);
168    }
169
170    #[test]
171    fn snapshot_keeps_published_view() {
172        let map = ArcSwapMap::<u32, i32>::default();
173        map.insert(1, 10);
174
175        let snapshot = map.snapshot();
176        map.insert(1, 20);
177        map.insert(2, 30);
178
179        assert_eq!(snapshot.get(&1), Some(&10));
180        assert_eq!(snapshot.get(&2), None);
181        assert_eq!(map.get(&1), Some(20));
182        assert_eq!(map.get(&2), Some(30));
183    }
184
185    #[test]
186    fn clone_shares_the_same_cell() {
187        let map = ArcSwapMap::<u32, i32>::default();
188        let clone = map.clone();
189        // A write through one handle is observed through the other.
190        map.insert(1, 10);
191        assert_eq!(clone.get(&1), Some(10));
192        clone.insert(2, 20);
193        assert_eq!(map.get(&2), Some(20));
194    }
195}