Skip to main content

obj_pool/
par.rs

1use crate::*;
2use parking_lot::{
3    MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
4};
5use std::cell::Cell;
6use std::convert::TryInto;
7
8thread_local! {
9  static COUNTER: Cell<usize> = const { Cell::new(0) };
10}
11
12/// A sharded, thread-safe object pool.
13///
14/// An external `ObjId` packs the shard index into its highest `SHARD_BITS`
15/// bits, where `SHARD_BITS` is the smallest number of bits able to represent
16/// all shard indices `0..S`. The remaining low bits hold the object index
17/// inside the shard, so each shard can hold up to `2^(32 - SHARD_BITS) - 1`
18/// objects.
19///
20/// In debug builds every `ParObjPool` additionally mixes its own random tag
21/// into the external `ObjId`s it issues, so ids of one pool are (with
22/// overwhelming probability) rejected by any other pool. See the crate-level
23/// documentation for details.
24pub struct ParObjPool<T, const S: usize> {
25    shards: [RwLock<ObjPool<T>>; S],
26
27    /// Debug-only random tag mixed into every external `ObjId` of this pool.
28    tag: PoolTag,
29}
30
31impl<T, const S: usize> Default for ParObjPool<T, S> {
32    fn default() -> Self {
33        Self::new()
34    }
35}
36
37impl<T, const S: usize> ParObjPool<T, S> {
38    /// Number of high bits of an external `ObjId` storing the shard index:
39    /// the smallest number of bits able to represent all shard indices `0..S`.
40    /// For `S == 1` it is zero and ids pass through unchanged.
41    const SHARD_BITS: u32 = usize::BITS - (S - 1).leading_zeros();
42
43    /// Number of low bits of an external `ObjId` storing the in-shard object id.
44    const INDEX_BITS: u32 = u32::BITS - Self::SHARD_BITS;
45
46    /// Mask selecting the in-shard object id bits of an external `ObjId`.
47    const INDEX_MASK: u32 = u32::MAX >> Self::SHARD_BITS;
48
49    pub fn new() -> Self {
50        const {
51            assert!(S > 0, "ParObjPool requires at least one shard");
52            assert!(
53                Self::SHARD_BITS < u32::BITS,
54                "too many shards: no bits left in ObjId for the object index"
55            );
56        }
57        let mut shards = Vec::with_capacity(S);
58        shards.resize_with(S, || RwLock::new(ObjPool::<T>::new()));
59        Self {
60            shards: shards.try_into().expect("invalid array"),
61            tag: PoolTag::random(),
62        }
63    }
64
65    pub fn insert(&self, object: T) -> ObjId {
66        let counter = COUNTER.with(|c| {
67            let v = c.get();
68            c.set(v.wrapping_add(1));
69            v
70        });
71        let shard_index = counter % S;
72        let mut shard = self.shards[shard_index].write();
73        let inner = shard.insert(object);
74        let index = shard.obj_id_to_index(inner);
75        drop(shard);
76        self.obj_id_to_external(shard_index, index)
77    }
78
79    pub fn remove(&self, obj_id: ObjId) -> Option<T> {
80        let (shard_index, index) = self.obj_id_from_external(obj_id)?;
81        let mut shard = self.shards[shard_index].write();
82        let inner = shard.index_to_obj_id(index);
83        shard.remove(inner)
84    }
85
86    pub fn get(&self, obj_id: ObjId) -> Option<MappedRwLockReadGuard<'_, T>> {
87        let (shard_index, index) = self.obj_id_from_external(obj_id)?;
88        RwLockReadGuard::try_map(self.shards[shard_index].read(), |obj_pool| {
89            obj_pool.get(obj_pool.index_to_obj_id(index))
90        })
91        .ok()
92    }
93
94    pub fn try_get(&self, obj_id: ObjId) -> Option<MappedRwLockReadGuard<'_, T>> {
95        let (shard_index, index) = self.obj_id_from_external(obj_id)?;
96        RwLockReadGuard::try_map(self.shards[shard_index].try_read()?, |obj_pool| {
97            obj_pool.get(obj_pool.index_to_obj_id(index))
98        })
99        .ok()
100    }
101
102    pub fn get_mut(&self, obj_id: ObjId) -> Option<MappedRwLockWriteGuard<'_, T>> {
103        let (shard_index, index) = self.obj_id_from_external(obj_id)?;
104        RwLockWriteGuard::try_map(self.shards[shard_index].write(), |obj_pool| {
105            obj_pool.get_mut(obj_pool.index_to_obj_id(index))
106        })
107        .ok()
108    }
109
110    pub fn try_get_mut(&self, obj_id: ObjId) -> Option<MappedRwLockWriteGuard<'_, T>> {
111        let (shard_index, index) = self.obj_id_from_external(obj_id)?;
112        RwLockWriteGuard::try_map(self.shards[shard_index].try_write()?, |obj_pool| {
113            obj_pool.get_mut(obj_pool.index_to_obj_id(index))
114        })
115        .ok()
116    }
117
118    pub fn clear(&self) {
119        for shard in &self.shards {
120            shard.write().clear();
121        }
122    }
123
124    pub fn shrink_to_fit(&self) {
125        for shard in &self.shards {
126            let mut shard = shard.write();
127            shard.shrink_to_fit();
128        }
129    }
130
131    pub fn capacity(&self) -> usize {
132        self.shards.iter().map(|s| s.read().capacity()).sum()
133    }
134
135    fn obj_id_to_external(&self, shard_index: usize, index: u32) -> ObjId {
136        debug_assert!(
137            index < Self::INDEX_MASK,
138            "shard is full: object index does not fit into the index bits"
139        );
140        let raw = if Self::SHARD_BITS == 0 {
141            index
142        } else {
143            (shard_index as u32) << Self::INDEX_BITS | index
144        };
145        self.tag.mask_id(ObjId::from_index(raw))
146    }
147
148    fn obj_id_from_external(&self, obj_id: ObjId) -> Option<(usize, u32)> {
149        let raw = self.tag.unmask_id(obj_id).into_index();
150        let (shard_index, index) = if Self::SHARD_BITS == 0 {
151            (0, raw)
152        } else {
153            ((raw >> Self::INDEX_BITS) as usize, raw & Self::INDEX_MASK)
154        };
155        // A foreign or corrupted id can decode to a nonexistent shard.
156        (shard_index < S).then_some((shard_index, index))
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test() {
166        let o = ParObjPool::<usize, 16>::new();
167        let k = o.insert(10);
168        assert_eq!(o.get(k).map(|v| *v), Some(10));
169        let k = o.insert(20);
170        assert_eq!(o.get(k).map(|v| *v), Some(20));
171        let k = o.insert(30);
172        assert_eq!(o.get(k).map(|v| *v), Some(30));
173        assert_eq!(o.try_get(k).map(|v| *v), Some(30));
174        assert_eq!(o.try_get_mut(k).map(|v| *v), Some(30));
175    }
176
177    #[test]
178    fn shard_bits() {
179        assert_eq!(ParObjPool::<u8, 1>::SHARD_BITS, 0);
180        assert_eq!(ParObjPool::<u8, 1>::INDEX_MASK, u32::MAX);
181        assert_eq!(ParObjPool::<u8, 2>::SHARD_BITS, 1);
182        assert_eq!(ParObjPool::<u8, 3>::SHARD_BITS, 2);
183        assert_eq!(ParObjPool::<u8, 16>::SHARD_BITS, 4);
184        assert_eq!(ParObjPool::<u8, 16>::INDEX_MASK, 0x0FFFFFFF);
185        assert_eq!(ParObjPool::<u8, 64>::SHARD_BITS, 6);
186        assert_eq!(ParObjPool::<u8, 64>::INDEX_MASK, 0x03FFFFFF);
187        assert_eq!(ParObjPool::<u8, 65>::SHARD_BITS, 7);
188        assert_eq!(ParObjPool::<u8, 100>::SHARD_BITS, 7);
189    }
190
191    fn insert_get_remove<const S: usize>() {
192        let o = ParObjPool::<usize, S>::new();
193        // More inserts than shards, so every shard is exercised.
194        let keys: Vec<_> = (0..S * 3 + 1).map(|v| (o.insert(v), v)).collect();
195        for (k, v) in &keys {
196            let (shard_index, _) = o.obj_id_from_external(*k).expect("id of this pool");
197            assert!(shard_index < S);
198            assert_eq!(o.get(*k).map(|v| *v), Some(*v));
199        }
200        for (k, v) in &keys {
201            assert_eq!(o.remove(*k), Some(*v));
202            assert_eq!(o.get(*k).map(|v| *v), None);
203        }
204    }
205
206    #[test]
207    fn shard_counts() {
208        insert_get_remove::<1>();
209        insert_get_remove::<2>();
210        insert_get_remove::<3>();
211        insert_get_remove::<16>();
212        insert_get_remove::<64>();
213        insert_get_remove::<100>();
214    }
215
216    #[cfg(debug_assertions)]
217    #[test]
218    fn foreign_ids_are_rejected() {
219        let a = ParObjPool::<usize, 4>::new();
220        let id = a.insert(10);
221
222        let mut b = ParObjPool::<usize, 4>::new();
223        // Make sure the pools did not roll the same random tag.
224        while b.tag.offset == a.tag.offset {
225            b = ParObjPool::new();
226        }
227        let own = b.insert(20);
228
229        assert!(b.get(id).is_none());
230        assert!(b.get_mut(id).is_none());
231        assert!(b.remove(id).is_none());
232        assert_eq!(b.get(own).map(|v| *v), Some(20));
233    }
234
235    #[test]
236    fn clear_releases_and_reuses() {
237        let o = ParObjPool::<usize, 4>::new();
238        let keys: Vec<_> = (0..8).map(|v| o.insert(v)).collect();
239        assert!(o.capacity() >= 8);
240
241        o.clear();
242        assert_eq!(o.capacity(), 0);
243        for k in keys {
244            assert!(o.get(k).is_none());
245            assert!(o.remove(k).is_none());
246        }
247
248        let k = o.insert(42);
249        assert_eq!(o.get(k).map(|v| *v), Some(42));
250    }
251
252    #[test]
253    fn get_mut_mutates_and_remove_twice() {
254        let o = ParObjPool::<usize, 4>::new();
255        let k = o.insert(1);
256
257        *o.get_mut(k).unwrap() = 2;
258        assert_eq!(o.get(k).map(|v| *v), Some(2));
259
260        assert_eq!(o.remove(k), Some(2));
261        assert_eq!(o.remove(k), None);
262        assert!(o.get(k).is_none());
263        assert!(o.get_mut(k).is_none());
264    }
265
266    #[test]
267    fn try_get_respects_shard_write_lock() {
268        let o = ParObjPool::<usize, 2>::new();
269        let k = o.insert(7);
270
271        let guard = o.get_mut(k).unwrap();
272        // The shard holding `k` is write-locked, so non-blocking reads must fail
273        // instead of deadlocking.
274        assert!(o.try_get(k).is_none());
275        assert!(o.try_get_mut(k).is_none());
276        drop(guard);
277
278        assert_eq!(o.try_get(k).map(|v| *v), Some(7));
279        assert_eq!(o.try_get_mut(k).map(|v| *v), Some(7));
280    }
281
282    #[test]
283    fn concurrent_insert_get_remove() {
284        let pool = ParObjPool::<usize, 8>::new();
285        std::thread::scope(|s| {
286            for t in 0..4 {
287                let pool = &pool;
288                s.spawn(move || {
289                    let keys: Vec<_> = (0..100)
290                        .map(|i| {
291                            let value = t * 1000 + i;
292                            (pool.insert(value), value)
293                        })
294                        .collect();
295                    for (k, v) in &keys {
296                        assert_eq!(pool.get(*k).map(|g| *g), Some(*v));
297                        *pool.get_mut(*k).unwrap() += 1;
298                    }
299                    for (k, v) in keys {
300                        assert_eq!(pool.remove(k), Some(v + 1));
301                    }
302                });
303            }
304        });
305    }
306}