/rust/registry/src/index.crates.io-1949cf8c6b5b557f/papaya-0.2.4/src/raw/mod.rs
Line | Count | Source |
1 | | mod alloc; |
2 | | mod probe; |
3 | | |
4 | | pub(crate) mod utils; |
5 | | |
6 | | use std::hash::{BuildHasher, Hash}; |
7 | | use std::mem::MaybeUninit; |
8 | | use std::sync::atomic::{AtomicPtr, AtomicU8, AtomicUsize, Ordering}; |
9 | | use std::sync::Mutex; |
10 | | use std::{hint, panic, ptr}; |
11 | | |
12 | | use self::alloc::{RawTable, Table}; |
13 | | use self::probe::Probe; |
14 | | |
15 | | #[allow(unused_imports)] // Contains polyfills for APIs that stabilized after MSRV. |
16 | | use self::utils::{untagged, AtomicPtrFetchOps, Counter, Parker, StrictProvenance, Tagged}; |
17 | | |
18 | | use crate::map::{Compute, Operation, ResizeMode}; |
19 | | use crate::Equivalent; |
20 | | |
21 | | use seize::{Collector, LocalGuard, OwnedGuard}; |
22 | | use utils::{MapGuard, Stack, VerifiedGuard}; |
23 | | |
24 | | /// A lock-free hash-table. |
25 | | pub struct HashMap<K, V, S> { |
26 | | /// A pointer to the root table. |
27 | | table: AtomicPtr<RawTable<Entry<K, V>>>, |
28 | | |
29 | | /// Collector for memory reclamation. |
30 | | collector: Collector, |
31 | | |
32 | | /// The resize mode, either blocking or incremental. |
33 | | resize: ResizeMode, |
34 | | |
35 | | /// An atomic counter of the number of keys in the table. |
36 | | count: Counter, |
37 | | |
38 | | /// The initial capacity provided to `HashMap::new`. |
39 | | /// |
40 | | /// The table is guaranteed to never shrink below this capacity. |
41 | | initial_capacity: usize, |
42 | | |
43 | | /// Hasher for keys. |
44 | | pub hasher: S, |
45 | | } |
46 | | |
47 | | /// Resize state for the hash-table. |
48 | | pub struct State<T> { |
49 | | /// The next table used for resizing. |
50 | | pub next: AtomicPtr<RawTable<T>>, |
51 | | |
52 | | /// A lock acquired to allocate the next table. |
53 | | pub allocating: Mutex<()>, |
54 | | |
55 | | /// The number of entries that have been copied to the next table. |
56 | | pub copied: AtomicUsize, |
57 | | |
58 | | /// The number of entries that have been claimed by copiers, |
59 | | /// but not necessarily copied. |
60 | | pub claim: AtomicUsize, |
61 | | |
62 | | /// The status of the resize. |
63 | | pub status: AtomicU8, |
64 | | |
65 | | /// A thread parker for blocking on copy operations. |
66 | | pub parker: Parker, |
67 | | |
68 | | /// Entries whose retirement has been deferred by later tables. |
69 | | pub deferred: Stack<*mut T>, |
70 | | } |
71 | | |
72 | | impl<T> Default for State<T> { |
73 | 0 | fn default() -> State<T> { |
74 | 0 | State { |
75 | 0 | next: AtomicPtr::new(ptr::null_mut()), |
76 | 0 | allocating: Mutex::new(()), |
77 | 0 | copied: AtomicUsize::new(0), |
78 | 0 | claim: AtomicUsize::new(0), |
79 | 0 | status: AtomicU8::new(State::PENDING), |
80 | 0 | parker: Parker::default(), |
81 | 0 | deferred: Stack::new(), |
82 | 0 | } |
83 | 0 | } Unexecuted instantiation: <papaya::raw::State<papaya::raw::Entry<bytes::bytes::Bytes, ()>> as core::default::Default>::default Unexecuted instantiation: <papaya::raw::State<_> as core::default::Default>::default |
84 | | } |
85 | | |
86 | | impl State<()> { |
87 | | /// A resize is in-progress. |
88 | | pub const PENDING: u8 = 0; |
89 | | |
90 | | /// The resize has been aborted, continue to the next table. |
91 | | pub const ABORTED: u8 = 1; |
92 | | |
93 | | /// The resize was complete and the table was promoted. |
94 | | pub const PROMOTED: u8 = 2; |
95 | | } |
96 | | |
97 | | // The result of an insert operation. |
98 | | pub enum InsertResult<'g, V> { |
99 | | /// Inserted the given value. |
100 | | Inserted(&'g V), |
101 | | |
102 | | /// Replaced the given value. |
103 | | Replaced(&'g V), |
104 | | |
105 | | /// Error returned by `try_insert`. |
106 | | Error { current: &'g V, not_inserted: V }, |
107 | | } |
108 | | |
109 | | // The raw result of an insert operation. |
110 | | pub enum RawInsertResult<'g, K, V> { |
111 | | /// Inserted the given value. |
112 | | Inserted(&'g V), |
113 | | |
114 | | /// Replaced the given value. |
115 | | Replaced(&'g V), |
116 | | |
117 | | /// Error returned by `try_insert`. |
118 | | Error { |
119 | | current: &'g V, |
120 | | not_inserted: *mut Entry<K, V>, |
121 | | }, |
122 | | } |
123 | | |
124 | | // An entry in the hash-table. |
125 | | #[repr(C, align(8))] // Reserve the lower 3 bits for pointer tagging. |
126 | | pub struct Entry<K, V> { |
127 | | /// The key for this entry. |
128 | | pub key: K, |
129 | | |
130 | | /// The value for this entry. |
131 | | pub value: V, |
132 | | } |
133 | | |
134 | | impl Entry<(), ()> { |
135 | | /// The entry is being copied to the new table, no updates are allowed on the old table. |
136 | | /// |
137 | | /// This bit is put down to initiate a copy, forcing all writers to complete the resize |
138 | | /// before making progress. |
139 | | const COPYING: usize = 0b001; |
140 | | |
141 | | /// The entry has been copied to the new table. |
142 | | /// |
143 | | /// This bit is put down after a copy completes. Both readers and writers must go to |
144 | | /// the new table to see the new state of the entry. |
145 | | /// |
146 | | /// In blocking mode this is unused. |
147 | | const COPIED: usize = 0b010; |
148 | | |
149 | | /// The entry was copied from a previous table. |
150 | | /// |
151 | | /// This bit indicates that an entry may still be accessible from previous tables |
152 | | /// because the resize is still in progress, and so it is unsafe to reclaim. |
153 | | /// |
154 | | /// In blocking mode this is unused. |
155 | | const BORROWED: usize = 0b100; |
156 | | } |
157 | | |
158 | | impl<K, V> utils::Unpack for Entry<K, V> { |
159 | | /// Mask for an entry pointer, ignoring any tag bits. |
160 | | const MASK: usize = !(Entry::COPYING | Entry::COPIED | Entry::BORROWED); |
161 | | } |
162 | | |
163 | | impl<K, V> Entry<K, V> { |
164 | | /// A sentinel pointer for a deleted entry. |
165 | | /// |
166 | | /// Null pointers are never copied to the new table, so this state is safe to use. |
167 | | /// Note that tombstone entries may still be marked as `COPYING`, so this state |
168 | | /// cannot be used for direct equality. |
169 | | const TOMBSTONE: *mut Entry<K, V> = Entry::COPIED as _; |
170 | | } |
171 | | |
172 | | /// The status of an entry. |
173 | | enum EntryStatus<K, V> { |
174 | | /// The entry is a tombstone or null (potentially a null copy). |
175 | | Null, |
176 | | |
177 | | /// The entry is being copied. |
178 | | Copied(Tagged<Entry<K, V>>), |
179 | | |
180 | | /// A valid entry. |
181 | | Value(Tagged<Entry<K, V>>), |
182 | | } |
183 | | |
184 | | impl<K, V> From<Tagged<Entry<K, V>>> for EntryStatus<K, V> { |
185 | | /// Returns the status for this entry. |
186 | | #[inline] |
187 | 0 | fn from(entry: Tagged<Entry<K, V>>) -> Self { |
188 | 0 | if entry.ptr.is_null() { |
189 | 0 | EntryStatus::Null |
190 | 0 | } else if entry.tag() & Entry::COPYING != 0 { |
191 | 0 | EntryStatus::Copied(entry) |
192 | | } else { |
193 | 0 | EntryStatus::Value(entry) |
194 | | } |
195 | 0 | } Unexecuted instantiation: <papaya::raw::EntryStatus<bytes::bytes::Bytes, ()> as core::convert::From<papaya::raw::utils::tagged::Tagged<papaya::raw::Entry<bytes::bytes::Bytes, ()>>>>::from Unexecuted instantiation: <papaya::raw::EntryStatus<_, _> as core::convert::From<papaya::raw::utils::tagged::Tagged<papaya::raw::Entry<_, _>>>>::from |
196 | | } |
197 | | |
198 | | /// The state of an entry we attempted to update. |
199 | | enum UpdateStatus<K, V> { |
200 | | /// Successfully replaced the given key and value. |
201 | | Replaced(Tagged<Entry<K, V>>), |
202 | | |
203 | | /// A new entry was written before we could update. |
204 | | Found(EntryStatus<K, V>), |
205 | | } |
206 | | |
207 | | /// The state of an entry we attempted to insert into. |
208 | | enum InsertStatus<K, V> { |
209 | | /// Successfully inserted the value. |
210 | | Inserted, |
211 | | |
212 | | /// A new entry was written before we could update. |
213 | | Found(EntryStatus<K, V>), |
214 | | } |
215 | | |
216 | | impl<K, V, S> HashMap<K, V, S> { |
217 | | /// Creates new hash-table with the given options. |
218 | | #[inline] |
219 | 24.9k | pub fn new( |
220 | 24.9k | capacity: usize, |
221 | 24.9k | hasher: S, |
222 | 24.9k | collector: Collector, |
223 | 24.9k | resize: ResizeMode, |
224 | 24.9k | ) -> HashMap<K, V, S> { |
225 | | // The table is lazily allocated. |
226 | 24.9k | if capacity == 0 { |
227 | 24.9k | return HashMap { |
228 | 24.9k | collector, |
229 | 24.9k | resize, |
230 | 24.9k | hasher, |
231 | 24.9k | initial_capacity: 1, |
232 | 24.9k | table: AtomicPtr::new(ptr::null_mut()), |
233 | 24.9k | count: Counter::default(), |
234 | 24.9k | }; |
235 | 0 | } |
236 | | |
237 | 0 | let capacity = probe::entries_for(capacity); |
238 | | |
239 | | // Initialize the table and mark it as the root. |
240 | 0 | let mut table = Table::alloc(capacity); |
241 | 0 | *table.state_mut().status.get_mut() = State::PROMOTED; |
242 | | |
243 | 0 | HashMap { |
244 | 0 | hasher, |
245 | 0 | resize, |
246 | 0 | collector, |
247 | 0 | initial_capacity: capacity, |
248 | 0 | table: AtomicPtr::new(table.raw), |
249 | 0 | count: Counter::default(), |
250 | 0 | } |
251 | 24.9k | } <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::new Line | Count | Source | 219 | 24.9k | pub fn new( | 220 | 24.9k | capacity: usize, | 221 | 24.9k | hasher: S, | 222 | 24.9k | collector: Collector, | 223 | 24.9k | resize: ResizeMode, | 224 | 24.9k | ) -> HashMap<K, V, S> { | 225 | | // The table is lazily allocated. | 226 | 24.9k | if capacity == 0 { | 227 | 24.9k | return HashMap { | 228 | 24.9k | collector, | 229 | 24.9k | resize, | 230 | 24.9k | hasher, | 231 | 24.9k | initial_capacity: 1, | 232 | 24.9k | table: AtomicPtr::new(ptr::null_mut()), | 233 | 24.9k | count: Counter::default(), | 234 | 24.9k | }; | 235 | 0 | } | 236 | | | 237 | 0 | let capacity = probe::entries_for(capacity); | 238 | | | 239 | | // Initialize the table and mark it as the root. | 240 | 0 | let mut table = Table::alloc(capacity); | 241 | 0 | *table.state_mut().status.get_mut() = State::PROMOTED; | 242 | | | 243 | 0 | HashMap { | 244 | 0 | hasher, | 245 | 0 | resize, | 246 | 0 | collector, | 247 | 0 | initial_capacity: capacity, | 248 | 0 | table: AtomicPtr::new(table.raw), | 249 | 0 | count: Counter::default(), | 250 | 0 | } | 251 | 24.9k | } |
Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::new |
252 | | |
253 | | /// Returns a guard for this collector |
254 | 538k | pub fn guard(&self) -> MapGuard<LocalGuard<'_>> { |
255 | | // Safety: Created the guard from our collector. |
256 | 538k | unsafe { MapGuard::new(self.collector().enter()) } |
257 | 538k | } <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::guard Line | Count | Source | 254 | 538k | pub fn guard(&self) -> MapGuard<LocalGuard<'_>> { | 255 | | // Safety: Created the guard from our collector. | 256 | 538k | unsafe { MapGuard::new(self.collector().enter()) } | 257 | 538k | } |
Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::guard |
258 | | |
259 | | /// Returns an owned guard for this collector |
260 | 0 | pub fn owned_guard(&self) -> MapGuard<OwnedGuard<'_>> { |
261 | | // Safety: Created the guard from our collector. |
262 | 0 | unsafe { MapGuard::new(self.collector().enter_owned()) } |
263 | 0 | } |
264 | | |
265 | | /// Verify a guard is valid to use with this map. |
266 | | #[inline] |
267 | 0 | pub fn verify<'g, G>(&self, guard: &'g G) -> &'g MapGuard<G> |
268 | 0 | where |
269 | 0 | G: seize::Guard, |
270 | | { |
271 | 0 | assert_eq!( |
272 | 0 | *guard.collector(), |
273 | | self.collector, |
274 | | "Attempted to access map with incorrect guard" |
275 | | ); |
276 | | |
277 | | // Safety: Verified the guard above. |
278 | 0 | unsafe { MapGuard::from_ref(guard) } |
279 | 0 | } |
280 | | |
281 | | /// Returns a reference to the root hash-table. |
282 | | #[inline] |
283 | 538k | fn root(&self, guard: &impl VerifiedGuard) -> Table<Entry<K, V>> { |
284 | | // Load the root table. |
285 | 538k | let raw = guard.protect(&self.table, Ordering::Acquire); |
286 | | |
287 | | // Safety: The root table is either null or a valid table allocation. |
288 | 538k | unsafe { Table::from_raw(raw) } |
289 | 538k | } <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::root::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Line | Count | Source | 283 | 538k | fn root(&self, guard: &impl VerifiedGuard) -> Table<Entry<K, V>> { | 284 | | // Load the root table. | 285 | 538k | let raw = guard.protect(&self.table, Ordering::Acquire); | 286 | | | 287 | | // Safety: The root table is either null or a valid table allocation. | 288 | 538k | unsafe { Table::from_raw(raw) } | 289 | 538k | } |
Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::root::<_> |
290 | | |
291 | | /// Returns a reference to the collector. |
292 | | #[inline] |
293 | 538k | pub fn collector(&self) -> &Collector { |
294 | 538k | &self.collector |
295 | 538k | } <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::collector Line | Count | Source | 293 | 538k | pub fn collector(&self) -> &Collector { | 294 | 538k | &self.collector | 295 | 538k | } |
Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::collector |
296 | | |
297 | | /// Returns the number of entries in the table. |
298 | | #[inline] |
299 | 0 | pub fn len(&self) -> usize { |
300 | 0 | self.count.sum() |
301 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::len Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::len |
302 | | |
303 | | /// Returns true if incremental resizing is enabled. |
304 | | #[inline] |
305 | 0 | fn is_incremental(&self) -> bool { |
306 | 0 | matches!(self.resize, ResizeMode::Incremental(_)) |
307 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::is_incremental Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::is_incremental |
308 | | } |
309 | | |
310 | | impl<K, V, S> HashMap<K, V, S> |
311 | | where |
312 | | K: Hash + Eq, |
313 | | S: BuildHasher, |
314 | | { |
315 | | /// Returns a reference to the entry corresponding to the key. |
316 | | #[inline] |
317 | 0 | pub fn get<'g, Q>(&self, key: &Q, guard: &'g impl VerifiedGuard) -> Option<(&'g K, &'g V)> |
318 | 0 | where |
319 | 0 | Q: Equivalent<K> + Hash + ?Sized, |
320 | | { |
321 | | // Load the root table. |
322 | 0 | let mut table = self.root(guard); |
323 | | |
324 | | // The table has not been initialized yet. |
325 | 0 | if table.raw.is_null() { |
326 | 0 | return None; |
327 | 0 | } |
328 | | |
329 | 0 | let (h1, h2) = self.hash(key); |
330 | | |
331 | | loop { |
332 | | // Initialize the probe state. |
333 | 0 | let mut probe = Probe::start(h1, table.mask); |
334 | | |
335 | | // Probe until we reach the limit. |
336 | 0 | 'probe: while probe.len <= table.limit { |
337 | | // Load the entry metadata first for cheap searches. |
338 | | // |
339 | | // Safety: `probe.i` is always in-bounds for the table length. |
340 | 0 | let meta = unsafe { table.meta(probe.i) }.load(Ordering::Acquire); |
341 | | |
342 | 0 | if meta == h2 { |
343 | | // Load the full entry. |
344 | | // |
345 | | // Safety: `probe.i` is always in-bounds for the table length. |
346 | 0 | let entry = guard |
347 | 0 | .protect(unsafe { table.entry(probe.i) }, Ordering::Acquire) |
348 | 0 | .unpack(); |
349 | | |
350 | | // The entry was deleted, keep probing. |
351 | 0 | if entry.ptr.is_null() { |
352 | 0 | probe.next(table.mask); |
353 | 0 | continue 'probe; |
354 | 0 | } |
355 | | |
356 | | // Safety: We performed a protected load of the pointer using a verified guard with |
357 | | // `Acquire` and ensured that it is non-null, meaning it is valid for reads as long |
358 | | // as we hold the guard. |
359 | 0 | let entry_ref = unsafe { &(*entry.ptr) }; |
360 | | |
361 | | // Check for a full match. |
362 | 0 | if key.equivalent(&entry_ref.key) { |
363 | | // The entry was copied to the new table. |
364 | | // |
365 | | // In blocking resize mode we do not need to perform self check as all writes block |
366 | | // until any resizes are complete, making the root table the source of truth for readers. |
367 | 0 | if entry.tag() & Entry::COPIED != 0 { |
368 | 0 | break 'probe; |
369 | 0 | } |
370 | | |
371 | | // Found the correct entry, return the key and value. |
372 | 0 | return Some((&entry_ref.key, &entry_ref.value)); |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | // The key is not in the table. |
377 | | // |
378 | | // It also cannot be in the next table because we have not went over the probe limit. |
379 | 0 | if meta == meta::EMPTY { |
380 | 0 | return None; |
381 | 0 | } |
382 | | |
383 | 0 | probe.next(table.mask); |
384 | | } |
385 | | |
386 | | // In incremental resize mode, we have to check the next table if we found |
387 | | // a copied entry or went over the probe limit. |
388 | 0 | if self.is_incremental() { |
389 | 0 | if let Some(next) = table.next_table() { |
390 | 0 | table = next; |
391 | 0 | continue; |
392 | 0 | } |
393 | 0 | } |
394 | | |
395 | | // Otherwise, the key is not in the table. |
396 | 0 | return None; |
397 | | } |
398 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::get::<[u8], papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::get::<bytes::bytes::Bytes, papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::get::<_, _> |
399 | | |
400 | | /// Inserts a key-value pair into the table. |
401 | | #[inline] |
402 | 0 | pub fn insert<'g>( |
403 | 0 | &self, |
404 | 0 | key: K, |
405 | 0 | value: V, |
406 | 0 | replace: bool, |
407 | 0 | guard: &'g impl VerifiedGuard, |
408 | 0 | ) -> InsertResult<'g, V> { |
409 | | // Perform the insert. |
410 | 0 | let raw_result = self.insert_inner(key, value, replace, guard); |
411 | | |
412 | 0 | let result = match raw_result { |
413 | | // Updated an entry. |
414 | 0 | RawInsertResult::Replaced(value) => InsertResult::Replaced(value), |
415 | | |
416 | | // Inserted a new entry. |
417 | 0 | RawInsertResult::Inserted(value) => { |
418 | | // Increment the table length. |
419 | 0 | self.count.get(guard).fetch_add(1, Ordering::Relaxed); |
420 | | |
421 | 0 | InsertResult::Inserted(value) |
422 | | } |
423 | | |
424 | | // Failed to insert the entry. |
425 | | RawInsertResult::Error { |
426 | 0 | current, |
427 | 0 | not_inserted, |
428 | | } => { |
429 | | // Safety: We allocated this box above and it was not inserted into the table. |
430 | 0 | let not_inserted = unsafe { Box::from_raw(not_inserted) }; |
431 | | |
432 | 0 | InsertResult::Error { |
433 | 0 | current, |
434 | 0 | not_inserted: not_inserted.value, |
435 | 0 | } |
436 | | } |
437 | | }; |
438 | | |
439 | 0 | result |
440 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::insert::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::insert::<_> |
441 | | |
442 | | /// Inserts an entry into the map. |
443 | | #[inline] |
444 | 0 | fn insert_inner<'g>( |
445 | 0 | &self, |
446 | 0 | key: K, |
447 | 0 | value: V, |
448 | 0 | should_replace: bool, |
449 | 0 | guard: &'g impl VerifiedGuard, |
450 | 0 | ) -> RawInsertResult<'g, K, V> { |
451 | | // Allocate the entry to be inserted. |
452 | 0 | let new_entry = untagged(Box::into_raw(Box::new(Entry { key, value }))); |
453 | | |
454 | | // Safety: We just allocated the entry above. |
455 | 0 | let new_ref = unsafe { &(*new_entry.ptr) }; |
456 | | |
457 | | // Load the root table. |
458 | 0 | let mut table = self.root(guard); |
459 | | |
460 | | // Allocate the table if it has not been initialized yet. |
461 | 0 | if table.raw.is_null() { |
462 | 0 | table = self.init(None); |
463 | 0 | } |
464 | | |
465 | 0 | let (h1, h2) = self.hash(&new_ref.key); |
466 | | |
467 | 0 | let mut help_copy = true; |
468 | | loop { |
469 | | // Initialize the probe state. |
470 | 0 | let mut probe = Probe::start(h1, table.mask); |
471 | | |
472 | | // Probe until we reach the limit. |
473 | 0 | let copying = 'probe: loop { |
474 | 0 | if probe.len > table.limit { |
475 | 0 | break None; |
476 | 0 | } |
477 | | |
478 | | // Load the entry metadata first for cheap searches. |
479 | | // |
480 | | // Safety: `probe.i` is always in-bounds for the table length. |
481 | 0 | let meta = unsafe { table.meta(probe.i) }.load(Ordering::Acquire); |
482 | | |
483 | | // The entry is empty, try to insert. |
484 | 0 | let entry = if meta == meta::EMPTY { |
485 | | // Perform the insertion. |
486 | | // |
487 | | // Safety: `probe.i` is always in-bounds for the table length. Additionally, |
488 | | // `new_entry` was allocated above and never shared. |
489 | 0 | match unsafe { self.insert_at(probe.i, h2, new_entry.raw, table, guard) } { |
490 | | // Successfully inserted. |
491 | 0 | InsertStatus::Inserted => return RawInsertResult::Inserted(&new_ref.value), |
492 | | |
493 | | // Lost to a concurrent insert. |
494 | | // |
495 | | // If the key matches, we might be able to update the value. |
496 | 0 | InsertStatus::Found(EntryStatus::Value(found)) |
497 | 0 | | InsertStatus::Found(EntryStatus::Copied(found)) => found, |
498 | | |
499 | | // Otherwise, continue probing. |
500 | | InsertStatus::Found(EntryStatus::Null) => { |
501 | 0 | probe.next(table.mask); |
502 | 0 | continue 'probe; |
503 | | } |
504 | | } |
505 | | } |
506 | | // Found a potential match. |
507 | 0 | else if meta == h2 { |
508 | | // Load the full entry. |
509 | | // |
510 | | // Safety: `probe.i` is always in-bounds for the table length. |
511 | 0 | let entry = guard |
512 | 0 | .protect(unsafe { table.entry(probe.i) }, Ordering::Acquire) |
513 | 0 | .unpack(); |
514 | | |
515 | | // The entry was deleted, keep probing. |
516 | 0 | if entry.ptr.is_null() { |
517 | 0 | probe.next(table.mask); |
518 | 0 | continue 'probe; |
519 | 0 | } |
520 | | |
521 | | // If the key matches, we might be able to update the value. |
522 | 0 | entry |
523 | | } |
524 | | // Otherwise, continue probing. |
525 | | else { |
526 | 0 | probe.next(table.mask); |
527 | 0 | continue 'probe; |
528 | | }; |
529 | | |
530 | | // Safety: We performed a protected load of the pointer using a verified guard with |
531 | | // `Acquire` and ensured that it is non-null, meaning it is valid for reads as long |
532 | | // as we hold the guard. |
533 | 0 | let entry_ref = unsafe { &(*entry.ptr) }; |
534 | | |
535 | | // Check for a full match. |
536 | 0 | if entry_ref.key != new_ref.key { |
537 | 0 | probe.next(table.mask); |
538 | 0 | continue 'probe; |
539 | 0 | } |
540 | | |
541 | | // The entry is being copied to the new table. |
542 | 0 | if entry.tag() & Entry::COPYING != 0 { |
543 | 0 | break 'probe Some(probe.i); |
544 | 0 | } |
545 | | |
546 | | // Return an error for calls to `try_insert`. |
547 | 0 | if !should_replace { |
548 | 0 | return RawInsertResult::Error { |
549 | 0 | current: &entry_ref.value, |
550 | 0 | not_inserted: new_entry.ptr, |
551 | 0 | }; |
552 | 0 | } |
553 | | |
554 | | // Try to update the value. |
555 | | // |
556 | | // Safety: |
557 | | // - `probe.i` is always in-bounds for the table length |
558 | | // - `entry` is a valid non-null entry that was inserted into the map. |
559 | 0 | match unsafe { self.insert_slow(probe.i, entry, new_entry.raw, table, guard) } { |
560 | | // Successfully performed the update. |
561 | 0 | UpdateStatus::Replaced(entry) => { |
562 | | // Safety: `entry` is a valid non-null entry that we found in the map |
563 | | // before replacing it. |
564 | 0 | let value = unsafe { &(*entry.ptr).value }; |
565 | 0 | return RawInsertResult::Replaced(value); |
566 | | } |
567 | | |
568 | | // The entry is being copied. |
569 | 0 | UpdateStatus::Found(EntryStatus::Copied(_)) => break 'probe Some(probe.i), |
570 | | |
571 | | // The entry was deleted before we could update it, continue probing. |
572 | | UpdateStatus::Found(EntryStatus::Null) => { |
573 | 0 | probe.next(table.mask); |
574 | 0 | continue 'probe; |
575 | | } |
576 | | |
577 | 0 | UpdateStatus::Found(EntryStatus::Value(_)) => {} |
578 | | } |
579 | | }; |
580 | | |
581 | | // Prepare to retry in the next table. |
582 | 0 | table = self.prepare_retry_insert(copying, &mut help_copy, table, guard); |
583 | | } |
584 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::insert_inner::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::insert_inner::<_> |
585 | | |
586 | | /// The slow-path for `insert`, updating the value. |
587 | | /// |
588 | | /// The returned pointer is guaranteed to be non-null and valid for reads. |
589 | | /// |
590 | | /// # Safety |
591 | | /// |
592 | | /// The safety requirements of `HashMap::update_at` apply. |
593 | | #[cold] |
594 | | #[inline(never)] |
595 | 0 | unsafe fn insert_slow( |
596 | 0 | &self, |
597 | 0 | i: usize, |
598 | 0 | mut entry: Tagged<Entry<K, V>>, |
599 | 0 | new_entry: *mut Entry<K, V>, |
600 | 0 | table: Table<Entry<K, V>>, |
601 | 0 | guard: &impl VerifiedGuard, |
602 | 0 | ) -> UpdateStatus<K, V> { |
603 | | loop { |
604 | | // Try to update the value. |
605 | | // |
606 | | // Safety: Guaranteed by caller. |
607 | 0 | match unsafe { self.update_at(i, entry, new_entry, table, guard) } { |
608 | | // Someone else beat us to the update, retry. |
609 | | // |
610 | | // Note that the pointer we find here is a non-null entry that was inserted |
611 | | // into the map. |
612 | 0 | UpdateStatus::Found(EntryStatus::Value(found)) => entry = found, |
613 | | |
614 | 0 | status => return status, |
615 | | } |
616 | | } |
617 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::insert_slow::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::insert_slow::<_> |
618 | | |
619 | | /// Prepare to retry an insert operation in the next table. |
620 | | #[cold] |
621 | | #[inline(never)] |
622 | 0 | fn prepare_retry_insert( |
623 | 0 | &self, |
624 | 0 | copying: Option<usize>, |
625 | 0 | help_copy: &mut bool, |
626 | 0 | table: Table<Entry<K, V>>, |
627 | 0 | guard: &impl VerifiedGuard, |
628 | 0 | ) -> Table<Entry<K, V>> { |
629 | | // If went over the probe limit or found a copied entry, trigger a resize. |
630 | 0 | let mut next_table = self.get_or_alloc_next(None, table); |
631 | | |
632 | 0 | let next_table = match self.resize { |
633 | | // In blocking mode we must complete the resize before proceeding. |
634 | 0 | ResizeMode::Blocking => self.help_copy(true, &table, guard), |
635 | | |
636 | | // In incremental mode we can perform more granular blocking. |
637 | | ResizeMode::Incremental(_) => { |
638 | | // Help out with the copy. |
639 | 0 | if *help_copy { |
640 | 0 | next_table = self.help_copy(false, &table, guard); |
641 | 0 | } |
642 | | |
643 | | // The entry we want to update is being copied. |
644 | 0 | if let Some(i) = copying { |
645 | 0 | // Wait for the entry to be copied. |
646 | 0 | // |
647 | 0 | // We could race with the copy to insert into the table. However, |
648 | 0 | // this entire code path is very rare and likely to complete quickly, |
649 | 0 | // so blocking allows us to make copies faster. |
650 | 0 | self.wait_copied(i, &table); |
651 | 0 | } |
652 | | |
653 | 0 | next_table |
654 | | } |
655 | | }; |
656 | | |
657 | | // Limit incremental copying to once per operation, for more consistent latency. |
658 | 0 | *help_copy = false; |
659 | | |
660 | | // Continue in the new table. |
661 | 0 | next_table |
662 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::prepare_retry_insert::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::prepare_retry_insert::<_> |
663 | | |
664 | | /// Removes a key from the map, returning the entry for the key if the key was previously in the map. |
665 | | #[inline] |
666 | 0 | pub fn remove<'g, Q>(&self, key: &Q, guard: &'g impl VerifiedGuard) -> Option<(&'g K, &'g V)> |
667 | 0 | where |
668 | 0 | Q: Equivalent<K> + Hash + ?Sized, |
669 | | { |
670 | | #[inline(always)] |
671 | 0 | fn should_remove<K, V>(_key: &K, _value: &V) -> bool { |
672 | 0 | true |
673 | 0 | } |
674 | | |
675 | | // Safety: `should_remove` unconditionally returns `true`. |
676 | 0 | unsafe { self.remove_if(key, should_remove, guard).unwrap_unchecked() } |
677 | 0 | } |
678 | | |
679 | | /// Removes a key from the map, returning the entry for the key if the key was previously in the map |
680 | | /// and the provided closure returns `true` |
681 | | #[inline] |
682 | 0 | pub fn remove_if<'g, Q, F>( |
683 | 0 | &self, |
684 | 0 | key: &Q, |
685 | 0 | mut should_remove: F, |
686 | 0 | guard: &'g impl VerifiedGuard, |
687 | 0 | ) -> Result<Option<(&'g K, &'g V)>, (&'g K, &'g V)> |
688 | 0 | where |
689 | 0 | Q: Equivalent<K> + Hash + ?Sized, |
690 | 0 | F: FnMut(&K, &V) -> bool, |
691 | | { |
692 | | // Load the root table. |
693 | 0 | let mut table = self.root(guard); |
694 | | |
695 | | // The table has not been initialized yet. |
696 | 0 | if table.raw.is_null() { |
697 | 0 | return Ok(None); |
698 | 0 | } |
699 | | |
700 | 0 | let (h1, h2) = self.hash(key); |
701 | | |
702 | 0 | let mut help_copy = true; |
703 | | loop { |
704 | | // Initialize the probe state. |
705 | 0 | let mut probe = Probe::start(h1, table.mask); |
706 | | |
707 | | // Probe until we reach the limit. |
708 | 0 | let copying = 'probe: loop { |
709 | 0 | if probe.len > table.limit { |
710 | 0 | break None; |
711 | 0 | } |
712 | | |
713 | | // Load the entry metadata first for cheap searches. |
714 | | // |
715 | | // Safety: `probe.i` is always in-bounds for the table length. |
716 | 0 | let meta = unsafe { table.meta(probe.i).load(Ordering::Acquire) }; |
717 | | |
718 | | // The key is not in the table. |
719 | | // It also cannot be in the next table because we have not went over the probe limit. |
720 | 0 | if meta == meta::EMPTY { |
721 | 0 | return Ok(None); |
722 | 0 | } |
723 | | |
724 | | // Check for a potential match. |
725 | 0 | if meta != h2 { |
726 | 0 | probe.next(table.mask); |
727 | 0 | continue 'probe; |
728 | 0 | } |
729 | | |
730 | | // Load the full entry. |
731 | | // |
732 | | // Safety: `probe.i` is always in-bounds for the table length. |
733 | 0 | let mut entry = guard |
734 | 0 | .protect(unsafe { table.entry(probe.i) }, Ordering::Acquire) |
735 | 0 | .unpack(); |
736 | | |
737 | | // The entry was deleted, keep probing. |
738 | 0 | if entry.ptr.is_null() { |
739 | 0 | probe.next(table.mask); |
740 | 0 | continue 'probe; |
741 | 0 | } |
742 | | |
743 | | // Check for a full match. |
744 | | // |
745 | | // Safety: We performed a protected load of the pointer using a verified guard with |
746 | | // `Acquire` and ensured that it is non-null, meaning it is valid for reads as long |
747 | | // as we hold the guard. |
748 | 0 | if !key.equivalent(unsafe { &(*entry.ptr).key }) { |
749 | 0 | probe.next(table.mask); |
750 | 0 | continue 'probe; |
751 | 0 | } |
752 | | |
753 | | // The entry is being copied to the new table, we have to complete the copy before |
754 | | // we can remove it. |
755 | 0 | if entry.tag() & Entry::COPYING != 0 { |
756 | 0 | break 'probe Some(probe.i); |
757 | 0 | } |
758 | | |
759 | | loop { |
760 | | // Safety: `entry` is a valid, non-null, protected entry that we found in the map. |
761 | 0 | let entry_ref = unsafe { &(*entry.ptr) }; |
762 | | |
763 | | // Ensure that the entry should be removed. |
764 | 0 | if !should_remove(&entry_ref.key, &entry_ref.value) { |
765 | 0 | return Err((&entry_ref.key, &entry_ref.value)); |
766 | 0 | } |
767 | | |
768 | | // Safety: |
769 | | // - `probe.i` is always in-bounds for the table length |
770 | | // - `entry` is a valid non-null entry that we found in the map. |
771 | 0 | let status = |
772 | 0 | unsafe { self.update_at(probe.i, entry, Entry::TOMBSTONE, table, guard) }; |
773 | | |
774 | 0 | match status { |
775 | | // Successfully removed the entry. |
776 | 0 | UpdateStatus::Replaced(_entry) => { |
777 | | // Mark the entry as a tombstone. |
778 | | // |
779 | | // Note that this might end up being overwritten by the metadata hash |
780 | | // if the initial insertion is lagging behind, but we avoid the RMW |
781 | | // and sacrifice reads in the extremely rare case. |
782 | | // |
783 | | // Safety: `probe.i` is always in-bounds for the table length. |
784 | | unsafe { |
785 | 0 | table |
786 | 0 | .meta(probe.i) |
787 | 0 | .store(meta::TOMBSTONE, Ordering::Release) |
788 | | }; |
789 | | |
790 | | // Decrement the table length. |
791 | 0 | self.count.get(guard).fetch_sub(1, Ordering::Relaxed); |
792 | | |
793 | | // Note that `entry_ref` here is the entry that we just replaced. |
794 | 0 | return Ok(Some((&entry_ref.key, &entry_ref.value))); |
795 | | } |
796 | | |
797 | | // The entry is being copied to the new table, we have to complete the copy |
798 | | // before we can remove. |
799 | 0 | UpdateStatus::Found(EntryStatus::Copied(_)) => break 'probe Some(probe.i), |
800 | | |
801 | | // The entry was deleted. |
802 | | // |
803 | | // We know that at some point during our execution the key was not in the map. |
804 | 0 | UpdateStatus::Found(EntryStatus::Null) => return Ok(None), |
805 | | |
806 | | // Lost to a concurrent update, retry. |
807 | 0 | UpdateStatus::Found(EntryStatus::Value(found)) => entry = found, |
808 | | } |
809 | | } |
810 | | }; |
811 | | |
812 | | // Prepare to retry in the next table. |
813 | 0 | table = match self.prepare_retry(copying, &mut help_copy, table, guard) { |
814 | 0 | Some(table) => table, |
815 | | |
816 | | // The search was exhausted. |
817 | 0 | None => return Ok(None), |
818 | | } |
819 | | } |
820 | 0 | } |
821 | | |
822 | | /// Prepare to retry an operation on an existing key in the next table. |
823 | | /// |
824 | | /// Returns `None` if the recursive search has been exhausted. |
825 | | #[cold] |
826 | 0 | fn prepare_retry( |
827 | 0 | &self, |
828 | 0 | copying: Option<usize>, |
829 | 0 | help_copy: &mut bool, |
830 | 0 | table: Table<Entry<K, V>>, |
831 | 0 | guard: &impl VerifiedGuard, |
832 | 0 | ) -> Option<Table<Entry<K, V>>> { |
833 | 0 | let next_table = match self.resize { |
834 | 0 | ResizeMode::Blocking => match copying { |
835 | | // The entry we want to perform the operation on is being copied. |
836 | | // |
837 | | // In blocking mode we must complete the resize before proceeding. |
838 | 0 | Some(_) => self.help_copy(true, &table, guard), |
839 | | |
840 | | // If we went over the probe limit, the key is not in the map. |
841 | 0 | None => return None, |
842 | | }, |
843 | | |
844 | | ResizeMode::Incremental(_) => { |
845 | | // In incremental resize mode, we always have to check the next table. |
846 | 0 | let next_table = table.next_table()?; |
847 | | |
848 | | // Help out with the copy. |
849 | 0 | if *help_copy { |
850 | 0 | self.help_copy(false, &table, guard); |
851 | 0 | } |
852 | | |
853 | 0 | if let Some(i) = copying { |
854 | 0 | // Wait for the entry to be copied. |
855 | 0 | // |
856 | 0 | // We could race with the copy to insert into the table. However, |
857 | 0 | // this entire code path is very rare and likely to complete quickly, |
858 | 0 | // so blocking allows us to make copies faster. |
859 | 0 | self.wait_copied(i, &table); |
860 | 0 | } |
861 | | |
862 | 0 | next_table |
863 | | } |
864 | | }; |
865 | | |
866 | | // Limit incremental copying to once per operation, for more consistent latency. |
867 | 0 | *help_copy = false; |
868 | | |
869 | | // Continue in the new table. |
870 | 0 | Some(next_table) |
871 | 0 | } |
872 | | |
873 | | /// Attempts to insert an entry at the given index. |
874 | | /// |
875 | | /// In the case of an error, the returned pointer is guaranteed to be |
876 | | /// protected and valid for reads as long as the guard is held. |
877 | | /// |
878 | | /// # Safety |
879 | | /// |
880 | | /// The index must be in-bounds for the table. Additionally, `new_entry` must be a |
881 | | /// valid owned pointer to insert into the map. |
882 | | #[inline] |
883 | 0 | unsafe fn insert_at( |
884 | 0 | &self, |
885 | 0 | i: usize, |
886 | 0 | meta: u8, |
887 | 0 | new_entry: *mut Entry<K, V>, |
888 | 0 | table: Table<Entry<K, V>>, |
889 | 0 | guard: &impl VerifiedGuard, |
890 | 0 | ) -> InsertStatus<K, V> { |
891 | | // Safety: The caller guarantees that `i` is in-bounds. |
892 | 0 | let entry = unsafe { table.entry(i) }; |
893 | 0 | let meta_entry = unsafe { table.meta(i) }; |
894 | | |
895 | | // Try to claim the empty entry. |
896 | 0 | let found = match guard.compare_exchange( |
897 | 0 | entry, |
898 | 0 | ptr::null_mut(), |
899 | 0 | new_entry, |
900 | 0 | Ordering::Release, |
901 | 0 | Ordering::Acquire, |
902 | 0 | ) { |
903 | | // Successfully claimed the entry. |
904 | | Ok(_) => { |
905 | | // Update the metadata table. |
906 | 0 | meta_entry.store(meta, Ordering::Release); |
907 | | |
908 | | // Return the value we inserted. |
909 | 0 | return InsertStatus::Inserted; |
910 | | } |
911 | | |
912 | | // Lost to a concurrent update. |
913 | 0 | Err(found) => found.unpack(), |
914 | | }; |
915 | | |
916 | 0 | let (meta, status) = match EntryStatus::from(found) { |
917 | | EntryStatus::Value(_) | EntryStatus::Copied(_) => { |
918 | | // Safety: We performed a protected load of the pointer using a verified guard |
919 | | // with `Acquire` and ensured that it is non-null, meaning it is valid for reads |
920 | | // as long as we hold the guard. |
921 | 0 | let key = unsafe { &(*found.ptr).key }; |
922 | | |
923 | | // An entry was inserted, we have to hash it to get the metadata. |
924 | | // |
925 | | // The logic is the same for copied entries here as we have to |
926 | | // check if the key matches and continue the update in the new table. |
927 | 0 | let hash = self.hasher.hash_one(key); |
928 | 0 | (meta::h2(hash), EntryStatus::Value(found)) |
929 | | } |
930 | | |
931 | | // The entry was deleted or null copied. |
932 | 0 | EntryStatus::Null => (meta::TOMBSTONE, EntryStatus::Null), |
933 | | }; |
934 | | |
935 | | // Ensure the meta table is updated to keep the probe chain alive for readers. |
936 | 0 | if meta_entry.load(Ordering::Relaxed) == meta::EMPTY { |
937 | 0 | meta_entry.store(meta, Ordering::Release); |
938 | 0 | } |
939 | | |
940 | 0 | InsertStatus::Found(status) |
941 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::insert_at::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::insert_at::<_> |
942 | | |
943 | | /// Attempts to replace the value of an existing entry at the given index. |
944 | | /// |
945 | | /// In the case of an error, the returned pointer is guaranteed to be |
946 | | /// protected and valid for reads. |
947 | | /// |
948 | | /// # Safety |
949 | | /// |
950 | | /// - The index must be in-bounds for the table. |
951 | | /// - `current` must be a valid non-null entry that was inserted into the map. |
952 | | /// - `new_entry` must be a valid sentinel or owned pointer to insert into the map. |
953 | | #[inline] |
954 | 0 | unsafe fn update_at( |
955 | 0 | &self, |
956 | 0 | i: usize, |
957 | 0 | current: Tagged<Entry<K, V>>, |
958 | 0 | new_entry: *mut Entry<K, V>, |
959 | 0 | table: Table<Entry<K, V>>, |
960 | 0 | guard: &impl VerifiedGuard, |
961 | 0 | ) -> UpdateStatus<K, V> { |
962 | | // Safety: The caller guarantees that `i` is in-bounds. |
963 | 0 | let entry = unsafe { table.entry(i) }; |
964 | | |
965 | | // Try to perform the update. |
966 | 0 | let found = match guard.compare_exchange_weak( |
967 | 0 | entry, |
968 | 0 | current.raw, |
969 | 0 | new_entry, |
970 | 0 | Ordering::Release, |
971 | 0 | Ordering::Acquire, |
972 | 0 | ) { |
973 | | // Successfully updated. |
974 | | Ok(_) => unsafe { |
975 | | // Safety: The caller guarantees that `current` is a valid non-null entry that was |
976 | | // inserted into the map. Additionally, it is now unreachable from this table due |
977 | | // to the CAS above. |
978 | 0 | self.defer_retire(current, &table, guard); |
979 | | |
980 | 0 | return UpdateStatus::Replaced(current); |
981 | | }, |
982 | | |
983 | | // Lost to a concurrent update. |
984 | 0 | Err(found) => found.unpack(), |
985 | | }; |
986 | | |
987 | 0 | UpdateStatus::Found(EntryStatus::from(found)) |
988 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::update_at::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::update_at::<_> |
989 | | |
990 | | /// Reserve capacity for `additional` more elements. |
991 | | #[inline] |
992 | 0 | pub fn reserve(&self, additional: usize, guard: &impl VerifiedGuard) { |
993 | 0 | let mut table = self.root(guard); |
994 | | |
995 | | // The table has not yet been allocated, initialize it. |
996 | 0 | if table.raw.is_null() { |
997 | 0 | table = self.init(Some(probe::entries_for(additional))); |
998 | 0 | } |
999 | | |
1000 | | loop { |
1001 | 0 | let capacity = probe::entries_for(self.count.sum().checked_add(additional).unwrap()); |
1002 | | |
1003 | | // We have enough capacity. |
1004 | 0 | if table.len() >= capacity { |
1005 | 0 | return; |
1006 | 0 | } |
1007 | | |
1008 | | // Race to allocate the new table. |
1009 | 0 | self.get_or_alloc_next(Some(capacity), table); |
1010 | | |
1011 | | // Force the copy to complete. |
1012 | | // |
1013 | | // Note that this is not strictly necessary for a `reserve` operation. |
1014 | 0 | table = self.help_copy(true, &table, guard); |
1015 | | } |
1016 | 0 | } |
1017 | | |
1018 | | /// Remove all entries from this table. |
1019 | | #[inline] |
1020 | 538k | pub fn clear(&self, guard: &impl VerifiedGuard) { |
1021 | | // Load the root table. |
1022 | 538k | let mut table = self.root(guard); |
1023 | | |
1024 | | // The table has not been initialized yet. |
1025 | 538k | if table.raw.is_null() { |
1026 | 538k | return; |
1027 | 0 | } |
1028 | | |
1029 | | loop { |
1030 | | // Get a clean copy of the table to delete from. |
1031 | 0 | table = self.linearize(table, guard); |
1032 | | |
1033 | | // Note that this method is not implemented in terms of `retain(|_, _| true)` to avoid |
1034 | | // loading entry metadata, as there is no need to provide consistency with `get`. |
1035 | 0 | let mut copying = false; |
1036 | | |
1037 | 0 | 'probe: for i in 0..table.len() { |
1038 | | // Load the entry to delete. |
1039 | | // |
1040 | | // Safety: `i` is in bounds for the table length. |
1041 | 0 | let mut entry = guard |
1042 | 0 | .protect(unsafe { table.entry(i) }, Ordering::Acquire) |
1043 | 0 | .unpack(); |
1044 | | |
1045 | | loop { |
1046 | | // The entry is empty or already deleted. |
1047 | 0 | if entry.ptr.is_null() { |
1048 | 0 | continue 'probe; |
1049 | 0 | } |
1050 | | |
1051 | | // Found a non-empty entry being copied. |
1052 | 0 | if entry.tag() & Entry::COPYING != 0 { |
1053 | | // Clear every entry in this table that we can, then deal with the copy. |
1054 | 0 | copying = true; |
1055 | 0 | continue 'probe; |
1056 | 0 | } |
1057 | | |
1058 | | // Try to delete the entry. |
1059 | | // |
1060 | | // Safety: `i` is in bounds for the table length. |
1061 | 0 | let result = unsafe { |
1062 | 0 | table.entry(i).compare_exchange( |
1063 | 0 | entry.raw, |
1064 | | Entry::TOMBSTONE, |
1065 | 0 | Ordering::Release, |
1066 | 0 | Ordering::Acquire, |
1067 | | ) |
1068 | | }; |
1069 | | |
1070 | 0 | match result { |
1071 | | // Successfully deleted the entry. |
1072 | | Ok(_) => { |
1073 | | // Update the metadata table. |
1074 | | // |
1075 | | // Safety: `i` is in bounds for the table length. |
1076 | 0 | unsafe { table.meta(i).store(meta::TOMBSTONE, Ordering::Release) }; |
1077 | | |
1078 | | // Decrement the table length. |
1079 | 0 | self.count.get(guard).fetch_sub(1, Ordering::Relaxed); |
1080 | | |
1081 | | // Safety: The caller guarantees that `current` is a valid non-null entry that was |
1082 | | // inserted into the map. Additionally, it is now unreachable from this table due |
1083 | | // to the CAS above. |
1084 | 0 | unsafe { self.defer_retire(entry, &table, guard) }; |
1085 | 0 | continue 'probe; |
1086 | | } |
1087 | | |
1088 | | // Lost to a concurrent update, retry. |
1089 | 0 | Err(found) => entry = found.unpack(), |
1090 | | } |
1091 | | } |
1092 | | } |
1093 | | |
1094 | | // We cleared every entry in this table. |
1095 | 0 | if !copying { |
1096 | 0 | break; |
1097 | 0 | } |
1098 | | |
1099 | | // A resize prevented us from deleting all the entries in this table. |
1100 | | // |
1101 | | // Complete the resize and retry in the new table. |
1102 | 0 | table = self.help_copy(true, &table, guard); |
1103 | | } |
1104 | 538k | } <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::clear::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Line | Count | Source | 1020 | 538k | pub fn clear(&self, guard: &impl VerifiedGuard) { | 1021 | | // Load the root table. | 1022 | 538k | let mut table = self.root(guard); | 1023 | | | 1024 | | // The table has not been initialized yet. | 1025 | 538k | if table.raw.is_null() { | 1026 | 538k | return; | 1027 | 0 | } | 1028 | | | 1029 | | loop { | 1030 | | // Get a clean copy of the table to delete from. | 1031 | 0 | table = self.linearize(table, guard); | 1032 | | | 1033 | | // Note that this method is not implemented in terms of `retain(|_, _| true)` to avoid | 1034 | | // loading entry metadata, as there is no need to provide consistency with `get`. | 1035 | 0 | let mut copying = false; | 1036 | | | 1037 | 0 | 'probe: for i in 0..table.len() { | 1038 | | // Load the entry to delete. | 1039 | | // | 1040 | | // Safety: `i` is in bounds for the table length. | 1041 | 0 | let mut entry = guard | 1042 | 0 | .protect(unsafe { table.entry(i) }, Ordering::Acquire) | 1043 | 0 | .unpack(); | 1044 | | | 1045 | | loop { | 1046 | | // The entry is empty or already deleted. | 1047 | 0 | if entry.ptr.is_null() { | 1048 | 0 | continue 'probe; | 1049 | 0 | } | 1050 | | | 1051 | | // Found a non-empty entry being copied. | 1052 | 0 | if entry.tag() & Entry::COPYING != 0 { | 1053 | | // Clear every entry in this table that we can, then deal with the copy. | 1054 | 0 | copying = true; | 1055 | 0 | continue 'probe; | 1056 | 0 | } | 1057 | | | 1058 | | // Try to delete the entry. | 1059 | | // | 1060 | | // Safety: `i` is in bounds for the table length. | 1061 | 0 | let result = unsafe { | 1062 | 0 | table.entry(i).compare_exchange( | 1063 | 0 | entry.raw, | 1064 | | Entry::TOMBSTONE, | 1065 | 0 | Ordering::Release, | 1066 | 0 | Ordering::Acquire, | 1067 | | ) | 1068 | | }; | 1069 | | | 1070 | 0 | match result { | 1071 | | // Successfully deleted the entry. | 1072 | | Ok(_) => { | 1073 | | // Update the metadata table. | 1074 | | // | 1075 | | // Safety: `i` is in bounds for the table length. | 1076 | 0 | unsafe { table.meta(i).store(meta::TOMBSTONE, Ordering::Release) }; | 1077 | | | 1078 | | // Decrement the table length. | 1079 | 0 | self.count.get(guard).fetch_sub(1, Ordering::Relaxed); | 1080 | | | 1081 | | // Safety: The caller guarantees that `current` is a valid non-null entry that was | 1082 | | // inserted into the map. Additionally, it is now unreachable from this table due | 1083 | | // to the CAS above. | 1084 | 0 | unsafe { self.defer_retire(entry, &table, guard) }; | 1085 | 0 | continue 'probe; | 1086 | | } | 1087 | | | 1088 | | // Lost to a concurrent update, retry. | 1089 | 0 | Err(found) => entry = found.unpack(), | 1090 | | } | 1091 | | } | 1092 | | } | 1093 | | | 1094 | | // We cleared every entry in this table. | 1095 | 0 | if !copying { | 1096 | 0 | break; | 1097 | 0 | } | 1098 | | | 1099 | | // A resize prevented us from deleting all the entries in this table. | 1100 | | // | 1101 | | // Complete the resize and retry in the new table. | 1102 | 0 | table = self.help_copy(true, &table, guard); | 1103 | | } | 1104 | 538k | } |
Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::clear::<_> |
1105 | | |
1106 | | /// Retains only the elements specified by the predicate. |
1107 | | #[inline] |
1108 | 0 | pub fn retain<F>(&self, mut f: F, guard: &impl VerifiedGuard) |
1109 | 0 | where |
1110 | 0 | F: FnMut(&K, &V) -> bool, |
1111 | | { |
1112 | | // Load the root table. |
1113 | 0 | let mut table = self.root(guard); |
1114 | | |
1115 | | // The table has not been initialized yet. |
1116 | 0 | if table.raw.is_null() { |
1117 | 0 | return; |
1118 | 0 | } |
1119 | | |
1120 | | loop { |
1121 | | // Get a clean copy of the table to delete from. |
1122 | 0 | table = self.linearize(table, guard); |
1123 | | |
1124 | 0 | let mut copying = false; |
1125 | 0 | 'probe: for i in 0..table.len() { |
1126 | | // Load the entry metadata first to ensure consistency with calls to `get` |
1127 | | // for entries that are retained. |
1128 | | // |
1129 | | // Safety: `i` is in bounds for the table length. |
1130 | 0 | let meta = unsafe { table.meta(i) }.load(Ordering::Acquire); |
1131 | | |
1132 | | // The entry is empty or deleted. |
1133 | 0 | if matches!(meta, meta::EMPTY | meta::TOMBSTONE) { |
1134 | 0 | continue 'probe; |
1135 | 0 | } |
1136 | | |
1137 | | // Load the entry to delete. |
1138 | | // |
1139 | | // Safety: `i` is in bounds for the table length. |
1140 | 0 | let mut entry = guard |
1141 | 0 | .protect(unsafe { table.entry(i) }, Ordering::Acquire) |
1142 | 0 | .unpack(); |
1143 | | |
1144 | | loop { |
1145 | | // The entry is empty or already deleted. |
1146 | 0 | if entry.ptr.is_null() { |
1147 | 0 | continue 'probe; |
1148 | 0 | } |
1149 | | |
1150 | | // Found a non-empty entry being copied. |
1151 | 0 | if entry.tag() & Entry::COPYING != 0 { |
1152 | | // Clear every entry in this table that we can, then deal with the copy. |
1153 | 0 | copying = true; |
1154 | 0 | continue 'probe; |
1155 | 0 | } |
1156 | | |
1157 | | // Safety: We performed a protected load of the pointer using a verified guard with |
1158 | | // `Acquire` and ensured that it is non-null, meaning it is valid for reads as long |
1159 | | // as we hold the guard. |
1160 | 0 | let entry_ref = unsafe { &*entry.ptr }; |
1161 | | |
1162 | | // Should we retain this entry? |
1163 | 0 | if f(&entry_ref.key, &entry_ref.value) { |
1164 | 0 | continue 'probe; |
1165 | 0 | } |
1166 | | |
1167 | | // Try to delete the entry. |
1168 | | // |
1169 | | // Safety: `i` is in bounds for the table length. |
1170 | 0 | let result = unsafe { |
1171 | 0 | table.entry(i).compare_exchange( |
1172 | 0 | entry.raw, |
1173 | | Entry::TOMBSTONE, |
1174 | 0 | Ordering::Release, |
1175 | 0 | Ordering::Acquire, |
1176 | | ) |
1177 | | }; |
1178 | | |
1179 | 0 | match result { |
1180 | | // Successfully deleted the entry. |
1181 | | Ok(_) => { |
1182 | | // Update the metadata table. |
1183 | | // |
1184 | | // Safety: `i` is in bounds for the table length. |
1185 | 0 | unsafe { table.meta(i).store(meta::TOMBSTONE, Ordering::Release) }; |
1186 | | |
1187 | | // Decrement the table length. |
1188 | 0 | self.count.get(guard).fetch_sub(1, Ordering::Relaxed); |
1189 | | |
1190 | | // Safety: The caller guarantees that `current` is a valid non-null entry that was |
1191 | | // inserted into the map. Additionally, it is now unreachable from this table due |
1192 | | // to the CAS above. |
1193 | 0 | unsafe { self.defer_retire(entry, &table, guard) }; |
1194 | 0 | continue 'probe; |
1195 | | } |
1196 | | |
1197 | | // Lost to a concurrent update, retry. |
1198 | 0 | Err(found) => entry = found.unpack(), |
1199 | | } |
1200 | | } |
1201 | | } |
1202 | | |
1203 | | // We cleared every entry in this table. |
1204 | 0 | if !copying { |
1205 | 0 | break; |
1206 | 0 | } |
1207 | | |
1208 | | // A resize prevented us from deleting all the entries in this table. |
1209 | | // |
1210 | | // Complete the resize and retry in the new table. |
1211 | 0 | table = self.help_copy(true, &table, guard); |
1212 | | } |
1213 | 0 | } |
1214 | | |
1215 | | /// Returns an iterator over the keys and values of this table. |
1216 | | #[inline] |
1217 | 162 | pub fn iter<'g, G>(&self, guard: &'g G) -> Iter<'g, K, V, G> |
1218 | 162 | where |
1219 | 162 | G: VerifiedGuard, |
1220 | | { |
1221 | | // Load the root table. |
1222 | 162 | let root = self.root(guard); |
1223 | | |
1224 | | // The table has not been initialized yet, return a dummy iterator. |
1225 | 162 | if root.raw.is_null() { |
1226 | 162 | return Iter { |
1227 | 162 | i: 0, |
1228 | 162 | guard, |
1229 | 162 | table: root, |
1230 | 162 | }; |
1231 | 0 | } |
1232 | | |
1233 | | // Get a clean copy of the table to iterate over. |
1234 | 0 | let table = self.linearize(root, guard); |
1235 | | |
1236 | 0 | Iter { i: 0, guard, table } |
1237 | 162 | } <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::iter::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Line | Count | Source | 1217 | 162 | pub fn iter<'g, G>(&self, guard: &'g G) -> Iter<'g, K, V, G> | 1218 | 162 | where | 1219 | 162 | G: VerifiedGuard, | 1220 | | { | 1221 | | // Load the root table. | 1222 | 162 | let root = self.root(guard); | 1223 | | | 1224 | | // The table has not been initialized yet, return a dummy iterator. | 1225 | 162 | if root.raw.is_null() { | 1226 | 162 | return Iter { | 1227 | 162 | i: 0, | 1228 | 162 | guard, | 1229 | 162 | table: root, | 1230 | 162 | }; | 1231 | 0 | } | 1232 | | | 1233 | | // Get a clean copy of the table to iterate over. | 1234 | 0 | let table = self.linearize(root, guard); | 1235 | | | 1236 | 0 | Iter { i: 0, guard, table } | 1237 | 162 | } |
Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::iter::<_> |
1238 | | |
1239 | | /// Returns the h1 and h2 hash for the given key. |
1240 | | #[inline] |
1241 | 0 | fn hash<Q>(&self, key: &Q) -> (usize, u8) |
1242 | 0 | where |
1243 | 0 | Q: Hash + ?Sized, |
1244 | | { |
1245 | 0 | let hash = self.hasher.hash_one(key); |
1246 | 0 | (meta::h1(hash), meta::h2(hash)) |
1247 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::hash::<[u8]> Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::hash::<bytes::bytes::Bytes> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::hash::<_> |
1248 | | } |
1249 | | |
1250 | | /// A wrapper around a CAS function that manages the computed state. |
1251 | | struct ComputeState<F, K, V, T> { |
1252 | | /// The CAS function. |
1253 | | compute: F, |
1254 | | |
1255 | | /// A cached insert transition. |
1256 | | insert: Option<V>, |
1257 | | |
1258 | | /// A cached update transition. |
1259 | | update: Option<CachedUpdate<K, V, T>>, |
1260 | | } |
1261 | | |
1262 | | /// A cached update transition. |
1263 | | struct CachedUpdate<K, V, T> { |
1264 | | /// The entry that the CAS function was called with. |
1265 | | input: *mut Entry<K, V>, |
1266 | | |
1267 | | /// The cached result. |
1268 | | output: Operation<V, T>, |
1269 | | } |
1270 | | |
1271 | | impl<'g, F, K, V, T> ComputeState<F, K, V, T> |
1272 | | where |
1273 | | F: FnMut(Option<(&'g K, &'g V)>) -> Operation<V, T>, |
1274 | | K: 'g, |
1275 | | V: 'g, |
1276 | | { |
1277 | | /// Create a new `ComputeState` for the given function. |
1278 | | #[inline] |
1279 | 0 | fn new(compute: F) -> ComputeState<F, K, V, T> { |
1280 | 0 | ComputeState { |
1281 | 0 | compute, |
1282 | 0 | insert: None, |
1283 | 0 | update: None, |
1284 | 0 | } |
1285 | 0 | } |
1286 | | |
1287 | | /// Performs a state transition. |
1288 | | /// |
1289 | | /// # Safety |
1290 | | /// |
1291 | | /// The entry pointer must be valid for reads if provided. |
1292 | | #[inline] |
1293 | 0 | unsafe fn next(&mut self, entry: Option<*mut Entry<K, V>>) -> Operation<V, T> { |
1294 | 0 | let Some(entry) = entry else { |
1295 | | // If there is no current entry, perform a transition for the insert. |
1296 | 0 | return match self.insert.take() { |
1297 | | // Use the cached insert. |
1298 | 0 | Some(value) => Operation::Insert(value), |
1299 | | |
1300 | | // Otherwise, compute the value to insert. |
1301 | 0 | None => (self.compute)(None), |
1302 | | }; |
1303 | | }; |
1304 | | |
1305 | | // Otherwise, perform an update transition. |
1306 | 0 | match self.update.take() { |
1307 | | // Used the cached update if the entry has not changed. |
1308 | 0 | Some(CachedUpdate { input, output }) if input == entry => output, |
1309 | | |
1310 | | // Otherwise, compute the value to update. |
1311 | | _ => { |
1312 | | // Safety: The caller guarantees that `entry` is valid for reads. |
1313 | 0 | let entry_ref = unsafe { &*entry }; |
1314 | 0 | (self.compute)(Some((&entry_ref.key, &entry_ref.value))) |
1315 | | } |
1316 | | } |
1317 | 0 | } |
1318 | | |
1319 | | /// Restores the state if an operation fails. |
1320 | | /// |
1321 | | /// This allows the result of the compute closure with a given input to be cached. |
1322 | | /// This is useful at it avoids calling the closure multiple times if an update needs |
1323 | | /// to be retried in a new table. |
1324 | | /// |
1325 | | /// Additionally, update and insert operations are cached separately, although this |
1326 | | /// is not guaranteed in the public API. This means that internal methods can rely on |
1327 | | /// `compute(None)` being called at most once. |
1328 | | #[inline] |
1329 | 0 | fn restore(&mut self, input: Option<*mut Entry<K, V>>, output: Operation<V, T>) { |
1330 | 0 | match input { |
1331 | 0 | Some(input) => self.update = Some(CachedUpdate { input, output }), |
1332 | 0 | None => match output { |
1333 | 0 | Operation::Insert(value) => self.insert = Some(value), |
1334 | 0 | _ => unreachable!(), |
1335 | | }, |
1336 | | } |
1337 | 0 | } |
1338 | | } |
1339 | | |
1340 | | /// A lazy initialized `Entry` allocation. |
1341 | | enum LazyEntry<K, V> { |
1342 | | /// An uninitialized entry, containing just the owned key. |
1343 | | Uninit(K), |
1344 | | |
1345 | | /// An allocated entry. |
1346 | | Init(*mut Entry<K, MaybeUninit<V>>), |
1347 | | } |
1348 | | |
1349 | | impl<K, V> LazyEntry<K, V> { |
1350 | | /// Returns a reference to the entry's key. |
1351 | | #[inline] |
1352 | 0 | fn key(&self) -> &K { |
1353 | 0 | match self { |
1354 | 0 | LazyEntry::Uninit(key) => key, |
1355 | 0 | LazyEntry::Init(entry) => unsafe { &(**entry).key }, |
1356 | | } |
1357 | 0 | } |
1358 | | |
1359 | | /// Initializes the entry if it has not already been initialized, returning the pointer |
1360 | | /// to the entry allocation. |
1361 | | #[inline] |
1362 | 0 | fn init(&mut self) -> *mut Entry<K, MaybeUninit<V>> { |
1363 | 0 | match self { |
1364 | 0 | LazyEntry::Init(entry) => *entry, |
1365 | 0 | LazyEntry::Uninit(key) => { |
1366 | | // Safety: we read the current key with `ptr::read` and overwrite the |
1367 | | // state with `ptr::write`. We also make sure to abort if the allocator |
1368 | | // panics, ensuring the current value is not dropped twice. |
1369 | | unsafe { |
1370 | 0 | let key = ptr::read(key); |
1371 | 0 | let entry = panic::catch_unwind(panic::AssertUnwindSafe(|| { |
1372 | 0 | Box::into_raw(Box::new(Entry { |
1373 | 0 | value: MaybeUninit::uninit(), |
1374 | 0 | key, |
1375 | 0 | })) |
1376 | 0 | })) |
1377 | 0 | .unwrap_or_else(|_| std::process::abort()); |
1378 | 0 | ptr::write(self, LazyEntry::Init(entry)); |
1379 | 0 | entry |
1380 | | } |
1381 | | } |
1382 | | } |
1383 | 0 | } |
1384 | | } |
1385 | | |
1386 | | /// RMW operations. |
1387 | | impl<K, V, S> HashMap<K, V, S> |
1388 | | where |
1389 | | K: Hash + Eq, |
1390 | | S: BuildHasher, |
1391 | | { |
1392 | | /// Tries to insert a key and value computed from a closure into the map, |
1393 | | /// and returns a reference to the value that was inserted. |
1394 | | #[inline] |
1395 | 0 | pub fn try_insert_with<'g, F>( |
1396 | 0 | &self, |
1397 | 0 | key: K, |
1398 | 0 | f: F, |
1399 | 0 | guard: &'g impl VerifiedGuard, |
1400 | 0 | ) -> Result<&'g V, &'g V> |
1401 | 0 | where |
1402 | 0 | F: FnOnce() -> V, |
1403 | 0 | K: 'g, |
1404 | | { |
1405 | 0 | let mut f = Some(f); |
1406 | 0 | let compute = |entry| match entry { |
1407 | | // There is already an existing value. |
1408 | 0 | Some((_, current)) => Operation::Abort(current), |
1409 | | |
1410 | | // Insert the initial value. |
1411 | | // |
1412 | | // Note that this case is guaranteed to be executed at most |
1413 | | // once as insert values are cached, so this can never panic. |
1414 | 0 | None => Operation::Insert((f.take().unwrap())()), |
1415 | 0 | }; |
1416 | | |
1417 | 0 | match self.compute(key, compute, guard) { |
1418 | | // Failed to insert, return the existing value. |
1419 | 0 | Compute::Aborted(current) => Err(current), |
1420 | | |
1421 | | // Successfully inserted. |
1422 | 0 | Compute::Inserted(_, value) => Ok(value), |
1423 | | |
1424 | 0 | _ => unreachable!(), |
1425 | | } |
1426 | 0 | } |
1427 | | |
1428 | | /// Returns a reference to the value corresponding to the key, or inserts a default value |
1429 | | /// computed from a closure. |
1430 | | #[inline] |
1431 | 0 | pub fn get_or_insert_with<'g, F>(&self, key: K, f: F, guard: &'g impl VerifiedGuard) -> &'g V |
1432 | 0 | where |
1433 | 0 | F: FnOnce() -> V, |
1434 | 0 | K: 'g, |
1435 | | { |
1436 | 0 | match self.try_insert_with(key, f, guard) { |
1437 | 0 | Ok(value) => value, |
1438 | 0 | Err(value) => value, |
1439 | | } |
1440 | 0 | } |
1441 | | |
1442 | | /// Updates an existing entry atomically, returning the value that was inserted. |
1443 | | #[inline] |
1444 | 0 | pub fn update<'g, F>( |
1445 | 0 | &self, |
1446 | 0 | key: K, |
1447 | 0 | mut update: F, |
1448 | 0 | guard: &'g impl VerifiedGuard, |
1449 | 0 | ) -> Option<&'g V> |
1450 | 0 | where |
1451 | 0 | F: FnMut(&V) -> V, |
1452 | 0 | K: 'g, |
1453 | | { |
1454 | 0 | let compute = |entry| match entry { |
1455 | | // There is nothing to update. |
1456 | 0 | None => Operation::Abort(()), |
1457 | | // Perform the update. |
1458 | 0 | Some((_, value)) => Operation::Insert(update(value)), |
1459 | 0 | }; |
1460 | | |
1461 | 0 | match self.compute(key, compute, guard) { |
1462 | | // Return the updated value. |
1463 | | Compute::Updated { |
1464 | 0 | new: (_, value), .. |
1465 | 0 | } => Some(value), |
1466 | | |
1467 | | // There was nothing to update. |
1468 | 0 | Compute::Aborted(_) => None, |
1469 | | |
1470 | 0 | _ => unreachable!(), |
1471 | | } |
1472 | 0 | } |
1473 | | |
1474 | | /// Updates an existing entry or inserts a default value computed from a closure. |
1475 | | #[inline] |
1476 | 0 | pub fn update_or_insert_with<'g, U, F>( |
1477 | 0 | &self, |
1478 | 0 | key: K, |
1479 | 0 | update: U, |
1480 | 0 | f: F, |
1481 | 0 | guard: &'g impl VerifiedGuard, |
1482 | 0 | ) -> &'g V |
1483 | 0 | where |
1484 | 0 | F: FnOnce() -> V, |
1485 | 0 | U: Fn(&V) -> V, |
1486 | 0 | K: 'g, |
1487 | | { |
1488 | 0 | let mut f = Some(f); |
1489 | 0 | let compute = |entry| match entry { |
1490 | | // Perform the update. |
1491 | 0 | Some((_, value)) => Operation::Insert::<_, ()>(update(value)), |
1492 | | |
1493 | | // Insert the initial value. |
1494 | | // |
1495 | | // Note that this case is guaranteed to be executed at most |
1496 | | // once as insert values are cached, so this can never panic. |
1497 | 0 | None => Operation::Insert((f.take().unwrap())()), |
1498 | 0 | }; |
1499 | | |
1500 | 0 | match self.compute(key, compute, guard) { |
1501 | | // Return the updated value. |
1502 | | Compute::Updated { |
1503 | 0 | new: (_, value), .. |
1504 | 0 | } => value, |
1505 | | |
1506 | | // Return the value we inserted. |
1507 | 0 | Compute::Inserted(_, value) => value, |
1508 | | |
1509 | 0 | _ => unreachable!(), |
1510 | | } |
1511 | 0 | } |
1512 | | |
1513 | | /// Update an entry with a CAS function. |
1514 | | /// |
1515 | | /// Note that `compute` closure is guaranteed to be called for a `None` input only once, allowing the |
1516 | | /// insertion of values that cannot be cloned or reconstructed. |
1517 | | #[inline] |
1518 | 0 | pub fn compute<'g, F, T>( |
1519 | 0 | &self, |
1520 | 0 | key: K, |
1521 | 0 | compute: F, |
1522 | 0 | guard: &'g impl VerifiedGuard, |
1523 | 0 | ) -> Compute<'g, K, V, T> |
1524 | 0 | where |
1525 | 0 | F: FnMut(Option<(&'g K, &'g V)>) -> Operation<V, T>, |
1526 | | { |
1527 | | // Lazy initialize the entry allocation. |
1528 | 0 | let mut entry = LazyEntry::Uninit(key); |
1529 | | |
1530 | | // Perform the update. |
1531 | | // |
1532 | | // Safety: We just allocated the entry above. |
1533 | 0 | let result = unsafe { self.compute_with(&mut entry, ComputeState::new(compute), guard) }; |
1534 | | |
1535 | | // Deallocate the entry if it was not inserted. |
1536 | 0 | if matches!(result, Compute::Removed(..) | Compute::Aborted(_)) { |
1537 | 0 | if let LazyEntry::Init(entry) = entry { |
1538 | 0 | // Safety: The entry was allocated but not inserted into the map. |
1539 | 0 | let _ = unsafe { Box::from_raw(entry) }; |
1540 | 0 | } |
1541 | 0 | } |
1542 | | |
1543 | 0 | result |
1544 | 0 | } |
1545 | | |
1546 | | /// Update an entry with a CAS function. |
1547 | | /// |
1548 | | /// # Safety |
1549 | | /// |
1550 | | /// The new entry must be a valid owned pointer to insert into the map. |
1551 | | #[inline] |
1552 | 0 | unsafe fn compute_with<'g, F, T>( |
1553 | 0 | &self, |
1554 | 0 | new_entry: &mut LazyEntry<K, V>, |
1555 | 0 | mut state: ComputeState<F, K, V, T>, |
1556 | 0 | guard: &'g impl VerifiedGuard, |
1557 | 0 | ) -> Compute<'g, K, V, T> |
1558 | 0 | where |
1559 | 0 | F: FnMut(Option<(&'g K, &'g V)>) -> Operation<V, T>, |
1560 | | { |
1561 | | // Load the root table. |
1562 | 0 | let mut table = self.root(guard); |
1563 | | |
1564 | | // The table has not yet been allocated. |
1565 | 0 | if table.raw.is_null() { |
1566 | | // Compute the value to insert. |
1567 | | // |
1568 | | // Safety: Insert transitions are always sound. |
1569 | 0 | match unsafe { state.next(None) } { |
1570 | 0 | op @ Operation::Insert(_) => state.restore(None, op), |
1571 | 0 | Operation::Remove => panic!("Cannot remove `None` entry."), |
1572 | 0 | Operation::Abort(value) => return Compute::Aborted(value), |
1573 | | } |
1574 | | |
1575 | | // Initialize the table. |
1576 | 0 | table = self.init(None); |
1577 | 0 | } |
1578 | | |
1579 | 0 | let (h1, h2) = self.hash(new_entry.key()); |
1580 | 0 | let mut help_copy = false; |
1581 | | |
1582 | | loop { |
1583 | | // Initialize the probe state. |
1584 | 0 | let mut probe = Probe::start(h1, table.mask); |
1585 | | |
1586 | | // Probe until we reach the limit. |
1587 | 0 | let copying = 'probe: loop { |
1588 | 0 | if probe.len > table.limit { |
1589 | 0 | break 'probe None; |
1590 | 0 | } |
1591 | | |
1592 | | // Load the entry metadata first for cheap searches. |
1593 | | // |
1594 | | // Safety: `probe.i` is always in-bounds for the table length. |
1595 | 0 | let meta = unsafe { table.meta(probe.i) }.load(Ordering::Acquire); |
1596 | | |
1597 | | // The entry is empty. |
1598 | 0 | let mut entry = if meta == meta::EMPTY { |
1599 | | // Compute the value to insert. |
1600 | | // |
1601 | | // Safety: Insert transitions are always sound. |
1602 | 0 | let value = match unsafe { state.next(None) } { |
1603 | 0 | Operation::Insert(value) => value, |
1604 | 0 | Operation::Remove => panic!("Cannot remove `None` entry."), |
1605 | 0 | Operation::Abort(value) => return Compute::Aborted(value), |
1606 | | }; |
1607 | | |
1608 | 0 | let new_entry = new_entry.init(); |
1609 | | // Safety: `new_entry` was just allocated above and is valid for writes. |
1610 | 0 | unsafe { (*new_entry).value = MaybeUninit::new(value) } |
1611 | | |
1612 | | // Attempt to insert. |
1613 | | // |
1614 | | // Safety: `probe.i` is always in-bounds for the table length.Additionally, |
1615 | | // `new_entry` was allocated above and never shared. |
1616 | 0 | match unsafe { self.insert_at(probe.i, h2, new_entry.cast(), table, guard) } { |
1617 | | // Successfully inserted. |
1618 | | InsertStatus::Inserted => { |
1619 | | // Increment the table length. |
1620 | 0 | self.count.get(guard).fetch_add(1, Ordering::Relaxed); |
1621 | | |
1622 | | // Safety: `new_entry` was initialized above. |
1623 | 0 | let new_ref = unsafe { &*new_entry.cast::<Entry<K, V>>() }; |
1624 | 0 | return Compute::Inserted(&new_ref.key, &new_ref.value); |
1625 | | } |
1626 | | |
1627 | | // Lost to a concurrent insert. |
1628 | | // |
1629 | | // If the key matches, we might be able to update the value. |
1630 | 0 | InsertStatus::Found(EntryStatus::Value(found)) |
1631 | 0 | | InsertStatus::Found(EntryStatus::Copied(found)) => { |
1632 | | // Cache the previous value |
1633 | | // |
1634 | | // Safety: `new_entry` was initialized above and was not inserted |
1635 | | // into the map. |
1636 | 0 | let value = unsafe { (*new_entry).value.assume_init_read() }; |
1637 | 0 | state.restore(None, Operation::Insert(value)); |
1638 | | |
1639 | 0 | found |
1640 | | } |
1641 | | |
1642 | | // The entry was removed or invalidated. |
1643 | | InsertStatus::Found(EntryStatus::Null) => { |
1644 | | // Cache the previous value. |
1645 | | // |
1646 | | // Safety: `new_entry` was initialized above and was not inserted |
1647 | | // into the map. |
1648 | 0 | let value = unsafe { (*new_entry).value.assume_init_read() }; |
1649 | 0 | state.restore(None, Operation::Insert(value)); |
1650 | | |
1651 | | // Continue probing. |
1652 | 0 | probe.next(table.mask); |
1653 | 0 | continue 'probe; |
1654 | | } |
1655 | | } |
1656 | | } |
1657 | | // Found a potential match. |
1658 | 0 | else if meta == h2 { |
1659 | | // Load the full entry. |
1660 | | // |
1661 | | // Safety: `probe.i` is always in-bounds for the table length. |
1662 | 0 | let found = guard |
1663 | 0 | .protect(unsafe { table.entry(probe.i) }, Ordering::Acquire) |
1664 | 0 | .unpack(); |
1665 | | |
1666 | | // The entry was deleted, keep probing. |
1667 | 0 | if found.ptr.is_null() { |
1668 | 0 | probe.next(table.mask); |
1669 | 0 | continue 'probe; |
1670 | 0 | } |
1671 | | |
1672 | | // If the key matches, we might be able to update the value. |
1673 | 0 | found |
1674 | | } |
1675 | | // Otherwise, continue probing. |
1676 | | else { |
1677 | 0 | probe.next(table.mask); |
1678 | 0 | continue 'probe; |
1679 | | }; |
1680 | | |
1681 | | // Check for a full match. |
1682 | | // |
1683 | | // Safety: We performed a protected load of the pointer using a verified guard with |
1684 | | // `Acquire` and ensured that it is non-null, meaning it is valid for reads as long |
1685 | | // as we hold the guard. |
1686 | 0 | if unsafe { (*entry.ptr).key != *new_entry.key() } { |
1687 | 0 | probe.next(table.mask); |
1688 | 0 | continue 'probe; |
1689 | 0 | } |
1690 | | |
1691 | | // The entry is being copied to the new table. |
1692 | 0 | if entry.tag() & Entry::COPYING != 0 { |
1693 | 0 | break 'probe Some(probe.i); |
1694 | 0 | } |
1695 | | |
1696 | | loop { |
1697 | | // Compute the value to insert. |
1698 | | // |
1699 | | // Safety: `entry` is valid for reads. |
1700 | 0 | let failure = match unsafe { state.next(Some(entry.ptr)) } { |
1701 | | // The operation was aborted. |
1702 | 0 | Operation::Abort(value) => return Compute::Aborted(value), |
1703 | | |
1704 | | // Update the value. |
1705 | 0 | Operation::Insert(value) => { |
1706 | 0 | let new_entry = new_entry.init(); |
1707 | | |
1708 | | // Safety: `new_entry` was just allocated above and is valid for writes. |
1709 | 0 | unsafe { (*new_entry).value = MaybeUninit::new(value) } |
1710 | | |
1711 | | // Try to perform the update. |
1712 | | // |
1713 | | // Safety: |
1714 | | // - `probe.i` is always in-bounds for the table length |
1715 | | // - `entry` is a valid non-null entry that we found in the map. |
1716 | | // - `new_entry` was initialized above and never shared. |
1717 | 0 | let status = unsafe { |
1718 | 0 | self.update_at(probe.i, entry, new_entry.cast(), table, guard) |
1719 | | }; |
1720 | | |
1721 | 0 | match status { |
1722 | | // Successfully updated. |
1723 | 0 | UpdateStatus::Replaced(entry) => { |
1724 | | // Safety: `entry` is a valid non-null entry that we found in the map |
1725 | | // before replacing it. |
1726 | 0 | let entry_ref = unsafe { &(*entry.ptr) }; |
1727 | | |
1728 | | // Safety: `new_entry` was initialized above. |
1729 | 0 | let new_ref = unsafe { &*new_entry.cast::<Entry<K, V>>() }; |
1730 | | |
1731 | 0 | return Compute::Updated { |
1732 | 0 | old: (&entry_ref.key, &entry_ref.value), |
1733 | 0 | new: (&new_ref.key, &new_ref.value), |
1734 | 0 | }; |
1735 | | } |
1736 | | |
1737 | | // The update failed. |
1738 | 0 | failure => { |
1739 | | // Save the previous value. |
1740 | | // |
1741 | | // Safety: `new_entry` was initialized above and was not inserted |
1742 | | // into the map. |
1743 | 0 | let value = unsafe { (*new_entry).value.assume_init_read() }; |
1744 | 0 | state.restore(Some(entry.ptr), Operation::Insert(value)); |
1745 | | |
1746 | 0 | failure |
1747 | | } |
1748 | | } |
1749 | | } |
1750 | | |
1751 | | // Remove the key from the map. |
1752 | | Operation::Remove => { |
1753 | | // Try to perform the removal. |
1754 | | // |
1755 | | // Safety: |
1756 | | // - `probe.i` is always in-bounds for the table length |
1757 | | // - `entry` is a valid non-null entry that we found in the map. |
1758 | 0 | let status = unsafe { |
1759 | 0 | self.update_at(probe.i, entry, Entry::TOMBSTONE, table, guard) |
1760 | | }; |
1761 | | |
1762 | 0 | match status { |
1763 | | // Successfully removed the entry. |
1764 | 0 | UpdateStatus::Replaced(entry) => { |
1765 | | // Mark the entry as a tombstone. |
1766 | | // |
1767 | | // Note that this might end up being overwritten by the metadata hash |
1768 | | // if the initial insertion is lagging behind, but we avoid the RMW |
1769 | | // and sacrifice reads in the extremely rare case. |
1770 | | unsafe { |
1771 | 0 | table |
1772 | 0 | .meta(probe.i) |
1773 | 0 | .store(meta::TOMBSTONE, Ordering::Release) |
1774 | | }; |
1775 | | |
1776 | | // Decrement the table length. |
1777 | 0 | self.count.get(guard).fetch_sub(1, Ordering::Relaxed); |
1778 | | |
1779 | | // Safety: `entry` is a valid non-null entry that we found in the map |
1780 | | // before replacing it. |
1781 | 0 | let entry_ref = unsafe { &(*entry.ptr) }; |
1782 | 0 | return Compute::Removed(&entry_ref.key, &entry_ref.value); |
1783 | | } |
1784 | | |
1785 | | // The remove failed. |
1786 | 0 | failure => { |
1787 | | // Save the removal operation. |
1788 | 0 | state.restore(Some(entry.ptr), Operation::Remove); |
1789 | | |
1790 | 0 | failure |
1791 | | } |
1792 | | } |
1793 | | } |
1794 | | }; |
1795 | | |
1796 | 0 | match failure { |
1797 | | // The entry is being copied to the new table. |
1798 | 0 | UpdateStatus::Found(EntryStatus::Copied(_)) => break 'probe Some(probe.i), |
1799 | | |
1800 | | // The entry was deleted before we could update it. |
1801 | | // |
1802 | | // We know that at some point during our execution the key was not in the map. |
1803 | | UpdateStatus::Found(EntryStatus::Null) => { |
1804 | | // Compute the next operation. |
1805 | | // |
1806 | | // Safety: Insert transitions are always sound. |
1807 | 0 | match unsafe { state.next(None) } { |
1808 | 0 | Operation::Insert(value) => { |
1809 | | // Save the computed value. |
1810 | 0 | state.restore(None, Operation::Insert(value)); |
1811 | | |
1812 | | // Continue probing to find an empty slot. |
1813 | 0 | probe.next(table.mask); |
1814 | 0 | continue 'probe; |
1815 | | } |
1816 | 0 | Operation::Remove => panic!("Cannot remove `None` entry."), |
1817 | 0 | Operation::Abort(value) => return Compute::Aborted(value), |
1818 | | } |
1819 | | } |
1820 | | |
1821 | | // Someone else beat us to the update, retry. |
1822 | 0 | UpdateStatus::Found(EntryStatus::Value(found)) => entry = found, |
1823 | | |
1824 | 0 | _ => unreachable!(), |
1825 | | } |
1826 | | } |
1827 | | }; |
1828 | | |
1829 | | // Prepare to retry in the next table. |
1830 | 0 | if let Some(next_table) = self.prepare_retry(copying, &mut help_copy, table, guard) { |
1831 | 0 | table = next_table; |
1832 | 0 | continue; |
1833 | 0 | } |
1834 | | |
1835 | | // Otherwise, the key is not in the map. |
1836 | | // |
1837 | | // Safety: Insert transitions are always sound. |
1838 | 0 | match unsafe { state.next(None) } { |
1839 | | // Need to insert into the new table. |
1840 | 0 | op @ Operation::Insert(_) => { |
1841 | 0 | table = self.prepare_retry_insert(None, &mut help_copy, table, guard); |
1842 | 0 | state.restore(None, op); |
1843 | 0 | } |
1844 | | // The operation was aborted. |
1845 | 0 | Operation::Abort(value) => return Compute::Aborted(value), |
1846 | 0 | Operation::Remove => panic!("Cannot remove `None` entry."), |
1847 | | } |
1848 | | } |
1849 | 0 | } |
1850 | | } |
1851 | | |
1852 | | /// Resize operations. |
1853 | | impl<K, V, S> HashMap<K, V, S> |
1854 | | where |
1855 | | K: Hash + Eq, |
1856 | | S: BuildHasher, |
1857 | | { |
1858 | | /// Allocate the initial table. |
1859 | | #[cold] |
1860 | | #[inline(never)] |
1861 | 0 | fn init(&self, capacity: Option<usize>) -> Table<Entry<K, V>> { |
1862 | | const CAPACITY: usize = 32; |
1863 | | |
1864 | | // Allocate the table and mark it as the root. |
1865 | 0 | let mut new = Table::alloc(capacity.unwrap_or(CAPACITY)); |
1866 | 0 | *new.state_mut().status.get_mut() = State::PROMOTED; |
1867 | | |
1868 | | // Race to write the initial table. |
1869 | 0 | match self.table.compare_exchange( |
1870 | 0 | ptr::null_mut(), |
1871 | 0 | new.raw, |
1872 | 0 | Ordering::Release, |
1873 | 0 | Ordering::Acquire, |
1874 | 0 | ) { |
1875 | | // Successfully initialized the table. |
1876 | 0 | Ok(_) => new, |
1877 | | |
1878 | | // Someone beat us, deallocate our table and use the table that was written. |
1879 | 0 | Err(found) => { |
1880 | | // Safety: We allocated the table above and never shared it. |
1881 | 0 | unsafe { Table::dealloc(new) } |
1882 | | |
1883 | | // Safety: The table was just initialized. |
1884 | 0 | unsafe { Table::from_raw(found) } |
1885 | | } |
1886 | | } |
1887 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::init Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::init |
1888 | | |
1889 | | /// Returns the next table, allocating it has not already been created. |
1890 | | #[cold] |
1891 | | #[inline(never)] |
1892 | 0 | fn get_or_alloc_next( |
1893 | 0 | &self, |
1894 | 0 | capacity: Option<usize>, |
1895 | 0 | table: Table<Entry<K, V>>, |
1896 | 0 | ) -> Table<Entry<K, V>> { |
1897 | | // Avoid spinning in tests, which can hide race conditions. |
1898 | | const SPIN_ALLOC: usize = if cfg!(any(test, debug_assertions)) { |
1899 | | 1 |
1900 | | } else { |
1901 | | 7 |
1902 | | }; |
1903 | | |
1904 | | // The next table is already allocated. |
1905 | 0 | if let Some(next) = table.next_table() { |
1906 | 0 | return next; |
1907 | 0 | } |
1908 | | |
1909 | 0 | let state = table.state(); |
1910 | | |
1911 | | // Otherwise, try to acquire the allocation lock. |
1912 | | // |
1913 | | // Unlike in `init`, we do not race here to prevent unnecessary allocator pressure. |
1914 | 0 | let _allocating = match state.allocating.try_lock() { |
1915 | 0 | Ok(lock) => lock, |
1916 | | // Someone else is currently allocating. |
1917 | | Err(_) => { |
1918 | 0 | let mut spun = 0; |
1919 | | |
1920 | | // Spin for a bit, waiting for the table to be initialized. |
1921 | 0 | while spun <= SPIN_ALLOC { |
1922 | 0 | for _ in 0..(spun * spun) { |
1923 | 0 | hint::spin_loop(); |
1924 | 0 | } |
1925 | | |
1926 | | // The table was initialized. |
1927 | 0 | if let Some(next) = table.next_table() { |
1928 | 0 | return next; |
1929 | 0 | } |
1930 | | |
1931 | 0 | spun += 1; |
1932 | | } |
1933 | | |
1934 | | // Otherwise, we have to block. |
1935 | 0 | state.allocating.lock().unwrap() |
1936 | | } |
1937 | | }; |
1938 | | |
1939 | | // The table was allocated while we were waiting for the lock. |
1940 | 0 | if let Some(next) = table.next_table() { |
1941 | 0 | return next; |
1942 | 0 | } |
1943 | | |
1944 | 0 | let current_capacity = table.len(); |
1945 | | |
1946 | | // Loading the length here is quite expensive, we may want to consider |
1947 | | // a probabilistic counter to detect high-deletion workloads. |
1948 | 0 | let active_entries = self.len(); |
1949 | | |
1950 | 0 | let next_capacity = match cfg!(papaya_stress) { |
1951 | | // Never grow the table to stress the incremental resizing algorithm. |
1952 | 0 | true => current_capacity, |
1953 | | |
1954 | | // Double the table capacity if we are at least 50% full. |
1955 | 0 | false if active_entries >= (current_capacity >> 1) => current_capacity << 1, |
1956 | | |
1957 | | // Halve the table if we are at most 12.5% full. |
1958 | | // |
1959 | | // This heuristic is intentionally pessimistic as unnecessarily shrinking |
1960 | | // is an expensive operation, but it may change in the future. We also respect |
1961 | | // the initial capacity to give the user a way to retain a strict minimum table |
1962 | | // size. |
1963 | 0 | false if active_entries <= (current_capacity >> 3) => { |
1964 | 0 | self.initial_capacity.max(current_capacity >> 1) |
1965 | | } |
1966 | | |
1967 | | // Otherwise keep the capacity the same. |
1968 | | // |
1969 | | // This can occur due to poor hash distribution or frequent cycling of |
1970 | | // insertions and deletions, in which case we want to avoid continuously |
1971 | | // growing the table. |
1972 | 0 | false => current_capacity, |
1973 | | }; |
1974 | | |
1975 | 0 | let next_capacity = capacity.unwrap_or(next_capacity); |
1976 | 0 | assert!( |
1977 | 0 | next_capacity <= isize::MAX as usize, |
1978 | | "`HashMap` exceeded maximum capacity" |
1979 | | ); |
1980 | | |
1981 | | // Allocate the new table while holding the lock. |
1982 | 0 | let next = Table::alloc(next_capacity); |
1983 | 0 | state.next.store(next.raw, Ordering::Release); |
1984 | 0 | drop(_allocating); |
1985 | | |
1986 | 0 | next |
1987 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::get_or_alloc_next Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::get_or_alloc_next |
1988 | | |
1989 | | /// Help along with an existing resize operation, returning the new root table. |
1990 | | /// |
1991 | | /// If `copy_all` is `false` in incremental resize mode, this returns the current reference's next |
1992 | | /// table, not necessarily the new root. |
1993 | | #[cold] |
1994 | | #[inline(never)] |
1995 | 0 | fn help_copy( |
1996 | 0 | &self, |
1997 | 0 | copy_all: bool, |
1998 | 0 | table: &Table<Entry<K, V>>, |
1999 | 0 | guard: &impl VerifiedGuard, |
2000 | 0 | ) -> Table<Entry<K, V>> { |
2001 | 0 | match self.resize { |
2002 | 0 | ResizeMode::Blocking => self.help_copy_blocking(table, guard), |
2003 | 0 | ResizeMode::Incremental(chunk) => { |
2004 | 0 | let copied_to = self.help_copy_incremental(chunk, copy_all, guard); |
2005 | | |
2006 | 0 | if !copy_all { |
2007 | | // If we weren't trying to linearize, we have to write to the next table |
2008 | | // even if the copy hasn't completed yet. |
2009 | 0 | return table.next_table().unwrap(); |
2010 | 0 | } |
2011 | | |
2012 | 0 | copied_to |
2013 | | } |
2014 | | } |
2015 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::help_copy::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::help_copy::<_> |
2016 | | |
2017 | | /// Help along the resize operation until it completes and the next table is promoted. |
2018 | | /// |
2019 | | /// Should only be called on the root table. |
2020 | 0 | fn help_copy_blocking( |
2021 | 0 | &self, |
2022 | 0 | table: &Table<Entry<K, V>>, |
2023 | 0 | guard: &impl VerifiedGuard, |
2024 | 0 | ) -> Table<Entry<K, V>> { |
2025 | | // Load the next table. |
2026 | 0 | let mut next = table.next_table().unwrap(); |
2027 | | |
2028 | | 'copy: loop { |
2029 | | // Make sure we are copying to the correct table. |
2030 | 0 | while next.state().status.load(Ordering::Relaxed) == State::ABORTED { |
2031 | 0 | next = self.get_or_alloc_next(None, next); |
2032 | 0 | } |
2033 | | |
2034 | | // The copy already completed |
2035 | 0 | if self.try_promote(table, &next, 0, guard) { |
2036 | 0 | return next; |
2037 | 0 | } |
2038 | | |
2039 | 0 | let copy_chunk = table.len().min(4096); |
2040 | | |
2041 | | loop { |
2042 | | // Every entry has already been claimed. |
2043 | 0 | if next.state().claim.load(Ordering::Relaxed) >= table.len() { |
2044 | 0 | break; |
2045 | 0 | } |
2046 | | |
2047 | | // Claim a chunk to copy. |
2048 | 0 | let copy_start = next.state().claim.fetch_add(copy_chunk, Ordering::Relaxed); |
2049 | | |
2050 | | // Copy our chunk of entries. |
2051 | 0 | let mut copied = 0; |
2052 | 0 | for i in 0..copy_chunk { |
2053 | 0 | let i = copy_start + i; |
2054 | | |
2055 | 0 | if i >= table.len() { |
2056 | 0 | break; |
2057 | 0 | } |
2058 | | |
2059 | | // Copy the entry. |
2060 | | // |
2061 | | // Safety: We verified that `i` is in-bounds above. |
2062 | 0 | if unsafe { !self.copy_at_blocking(i, table, &next, guard) } { |
2063 | | // This table doesn't have space for the next entry. |
2064 | | // |
2065 | | // Abort the current resize. |
2066 | | // |
2067 | | // Note that the `SeqCst` is necessary to make the store visible |
2068 | | // to threads that are unparked. |
2069 | 0 | next.state().status.store(State::ABORTED, Ordering::SeqCst); |
2070 | | |
2071 | | // Allocate the next table. |
2072 | 0 | let allocated = self.get_or_alloc_next(None, next); |
2073 | | |
2074 | | // Wake anyone waiting for us to finish. |
2075 | 0 | let state = table.state(); |
2076 | 0 | state.parker.unpark(&state.status); |
2077 | | |
2078 | | // Retry in a new table. |
2079 | 0 | next = allocated; |
2080 | 0 | continue 'copy; |
2081 | 0 | } |
2082 | | |
2083 | 0 | copied += 1; |
2084 | | } |
2085 | | |
2086 | | // Are we done? |
2087 | 0 | if self.try_promote(table, &next, copied, guard) { |
2088 | 0 | return next; |
2089 | 0 | } |
2090 | | |
2091 | | // If the resize was aborted while we were copying, continue in the new table. |
2092 | 0 | if next.state().status.load(Ordering::Relaxed) == State::ABORTED { |
2093 | 0 | continue 'copy; |
2094 | 0 | } |
2095 | | } |
2096 | | |
2097 | 0 | let state = next.state(); |
2098 | | // We copied all that we can, wait for the table to be promoted. |
2099 | 0 | for spun in 0.. { |
2100 | | // Avoid spinning in tests, which can hide race conditions. |
2101 | | const SPIN_WAIT: usize = if cfg!(any(test, debug_assertions)) { |
2102 | | 1 |
2103 | | } else { |
2104 | | 7 |
2105 | | }; |
2106 | | |
2107 | | // Note that `Acquire` is necessary here to ensure we see the |
2108 | | // relevant modifications to the root table if see the updated |
2109 | | // state before parking. |
2110 | | // |
2111 | | // Otherwise, `Parker::park` will ensure the necessary synchronization |
2112 | | // when we are unparked. |
2113 | 0 | let status = state.status.load(Ordering::Acquire); |
2114 | | |
2115 | | // If this copy was aborted, we have to retry in the new table. |
2116 | 0 | if status == State::ABORTED { |
2117 | 0 | continue 'copy; |
2118 | 0 | } |
2119 | | |
2120 | | // The copy has completed. |
2121 | 0 | if status == State::PROMOTED { |
2122 | 0 | return next; |
2123 | 0 | } |
2124 | | |
2125 | | // Copy chunks are relatively small and we expect to finish quickly, |
2126 | | // so spin for a bit before resorting to parking. |
2127 | 0 | if spun <= SPIN_WAIT { |
2128 | 0 | for _ in 0..(spun * spun) { |
2129 | 0 | hint::spin_loop(); |
2130 | 0 | } |
2131 | | |
2132 | 0 | continue; |
2133 | 0 | } |
2134 | | |
2135 | | // Park until the table is promoted. |
2136 | 0 | state |
2137 | 0 | .parker |
2138 | 0 | .park(&state.status, |status| status == State::PENDING); Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::help_copy_blocking::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>>::{closure#0}Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::help_copy_blocking::<_>::{closure#0} |
2139 | | } |
2140 | | } |
2141 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::help_copy_blocking::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::help_copy_blocking::<_> |
2142 | | |
2143 | | /// Copy the entry at the given index to the new table. |
2144 | | /// |
2145 | | /// Returns `true` if the entry was copied into the table or `false` if the table was full. |
2146 | | /// |
2147 | | /// # Safety |
2148 | | /// |
2149 | | /// The index must be in-bounds for the table. |
2150 | 0 | unsafe fn copy_at_blocking( |
2151 | 0 | &self, |
2152 | 0 | i: usize, |
2153 | 0 | table: &Table<Entry<K, V>>, |
2154 | 0 | next_table: &Table<Entry<K, V>>, |
2155 | 0 | guard: &impl VerifiedGuard, |
2156 | 0 | ) -> bool { |
2157 | | // Mark the entry as copying. |
2158 | | // |
2159 | | // Safety: The caller guarantees that the index is in-bounds. |
2160 | | // |
2161 | | // Note that we don't need to protect the returned entry here, because |
2162 | | // no one is allowed to retire the entry once we put the `COPYING` bit |
2163 | | // down until it is inserted into the new table. |
2164 | 0 | let entry = unsafe { table.entry(i) } |
2165 | 0 | .fetch_or(Entry::COPYING, Ordering::AcqRel) |
2166 | 0 | .unpack(); |
2167 | | |
2168 | | // The entry is a tombstone. |
2169 | 0 | if entry.raw == Entry::TOMBSTONE { |
2170 | 0 | return true; |
2171 | 0 | } |
2172 | | |
2173 | | // There is nothing to copy, we're done. |
2174 | 0 | if entry.ptr.is_null() { |
2175 | | // Mark as a tombstone so readers avoid having to load the entry. |
2176 | | // |
2177 | | // Safety: The caller guarantees that the index is in-bounds. |
2178 | 0 | unsafe { table.meta(i) }.store(meta::TOMBSTONE, Ordering::Release); |
2179 | 0 | return true; |
2180 | 0 | } |
2181 | | |
2182 | | // Copy the value to the new table. |
2183 | | // |
2184 | | // Safety: We marked the entry as `COPYING`, ensuring that any updates |
2185 | | // or removals wait until we complete the copy, and allowing us to get |
2186 | | // away without a protected load. Additionally, we verified that the |
2187 | | // entry is non-null, meaning that it is valid for reads. |
2188 | | unsafe { |
2189 | 0 | self.insert_copy(entry.ptr.unpack(), false, next_table, guard) |
2190 | 0 | .is_some() |
2191 | | } |
2192 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::copy_at_blocking::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::copy_at_blocking::<_> |
2193 | | |
2194 | | /// Help along an in-progress resize incrementally by copying a chunk of entries. |
2195 | | /// |
2196 | | /// Returns the table that was copied to. |
2197 | 0 | fn help_copy_incremental( |
2198 | 0 | &self, |
2199 | 0 | chunk: usize, |
2200 | 0 | block: bool, |
2201 | 0 | guard: &impl VerifiedGuard, |
2202 | 0 | ) -> Table<Entry<K, V>> { |
2203 | | // Always help the highest priority root resize. |
2204 | 0 | let table = self.root(guard); |
2205 | | |
2206 | | // Load the next table. |
2207 | 0 | let Some(next) = table.next_table() else { |
2208 | | // The copy we tried to help was already promoted. |
2209 | 0 | return table; |
2210 | | }; |
2211 | | |
2212 | | loop { |
2213 | | // The copy already completed. |
2214 | 0 | if self.try_promote(&table, &next, 0, guard) { |
2215 | 0 | return next; |
2216 | 0 | } |
2217 | | |
2218 | | loop { |
2219 | | // Every entry has already been claimed. |
2220 | 0 | if next.state().claim.load(Ordering::Relaxed) >= table.len() { |
2221 | 0 | break; |
2222 | 0 | } |
2223 | | |
2224 | | // Claim a chunk to copy. |
2225 | 0 | let copy_start = next.state().claim.fetch_add(chunk, Ordering::Relaxed); |
2226 | | |
2227 | | // Copy our chunk of entries. |
2228 | 0 | let mut copied = 0; |
2229 | 0 | for i in 0..chunk { |
2230 | 0 | let i = copy_start + i; |
2231 | | |
2232 | 0 | if i >= table.len() { |
2233 | 0 | break; |
2234 | 0 | } |
2235 | | |
2236 | | // Copy the entry. |
2237 | | // |
2238 | | // Safety: We verified that `i` is in-bounds above. |
2239 | 0 | unsafe { self.copy_at_incremental(i, &table, &next, guard) }; |
2240 | 0 | copied += 1; |
2241 | | } |
2242 | | |
2243 | | // Update the copy state, and try to promote the table. |
2244 | | // |
2245 | | // Only copy a single chunk if promotion fails, unless we are forced |
2246 | | // to complete the resize. |
2247 | 0 | if self.try_promote(&table, &next, copied, guard) || !block { |
2248 | 0 | return next; |
2249 | 0 | } |
2250 | | } |
2251 | | |
2252 | | // There are no entries that we can copy, block if necessary. |
2253 | 0 | if !block { |
2254 | 0 | return next; |
2255 | 0 | } |
2256 | | |
2257 | 0 | let state = next.state(); |
2258 | 0 | for spun in 0.. { |
2259 | | // Avoid spinning in tests, which can hide race conditions. |
2260 | | const SPIN_WAIT: usize = if cfg!(any(test, debug_assertions)) { |
2261 | | 1 |
2262 | | } else { |
2263 | | 7 |
2264 | | }; |
2265 | | |
2266 | | // The copy has completed. |
2267 | | // |
2268 | | // Note that `Acquire` is necessary here to ensure we see the |
2269 | | // relevant modifications to the root table if see the updated |
2270 | | // state before parking. |
2271 | | // |
2272 | | // Otherwise, `Parker::park` will ensure the necessary synchronization |
2273 | | // when we are unparked. |
2274 | 0 | let status = state.status.load(Ordering::Acquire); |
2275 | 0 | if status == State::PROMOTED { |
2276 | 0 | return next; |
2277 | 0 | } |
2278 | | |
2279 | | // Copy chunks are relatively small and we expect to finish quickly, |
2280 | | // so spin for a bit before resorting to parking. |
2281 | 0 | if spun <= SPIN_WAIT { |
2282 | 0 | for _ in 0..(spun * spun) { |
2283 | 0 | hint::spin_loop(); |
2284 | 0 | } |
2285 | | |
2286 | 0 | continue; |
2287 | 0 | } |
2288 | | |
2289 | | // Park until the table is promoted. |
2290 | 0 | state |
2291 | 0 | .parker |
2292 | 0 | .park(&state.status, |status| status == State::PENDING); Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::help_copy_incremental::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>>::{closure#0}Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::help_copy_incremental::<_>::{closure#0} |
2293 | | } |
2294 | | } |
2295 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::help_copy_incremental::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::help_copy_incremental::<_> |
2296 | | |
2297 | | /// Copy the entry at the given index to the new table. |
2298 | | /// |
2299 | | /// # Safety |
2300 | | /// |
2301 | | /// The index must be in-bounds for the table. |
2302 | 0 | unsafe fn copy_at_incremental( |
2303 | 0 | &self, |
2304 | 0 | i: usize, |
2305 | 0 | table: &Table<Entry<K, V>>, |
2306 | 0 | next_table: &Table<Entry<K, V>>, |
2307 | 0 | guard: &impl VerifiedGuard, |
2308 | 0 | ) { |
2309 | | // Safety: The caller guarantees that the index is in-bounds. |
2310 | 0 | let entry = unsafe { table.entry(i) }; |
2311 | | |
2312 | | // Mark the entry as copying. |
2313 | 0 | let found = entry.fetch_or(Entry::COPYING, Ordering::AcqRel).unpack(); |
2314 | | |
2315 | | // The entry is a tombstone. |
2316 | 0 | if found.raw == Entry::TOMBSTONE { |
2317 | 0 | return; |
2318 | 0 | } |
2319 | | |
2320 | | // There is nothing to copy, we're done. |
2321 | 0 | if found.ptr.is_null() { |
2322 | | // Mark as a tombstone so readers avoid having to load the entry. |
2323 | | // |
2324 | | // Safety: The caller guarantees that the index is in-bounds. |
2325 | 0 | unsafe { table.meta(i) }.store(meta::TOMBSTONE, Ordering::Release); |
2326 | 0 | return; |
2327 | 0 | } |
2328 | | |
2329 | | // Mark the entry as borrowed so writers in the new table know it was copied. |
2330 | 0 | let new_entry = found.map_tag(|addr| addr | Entry::BORROWED); Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::copy_at_incremental::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>>::{closure#0}Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::copy_at_incremental::<_>::{closure#0} |
2331 | | |
2332 | | // Copy the value to the new table. |
2333 | | // |
2334 | | // Safety: We marked the entry as `COPYING`, ensuring that any updates |
2335 | | // or removals wait until we complete the copy, and allowing us to get |
2336 | | // away without a protected load. Additionally, we verified that the |
2337 | | // entry is non-null, meaning that it is valid for reads. |
2338 | 0 | unsafe { |
2339 | 0 | self.insert_copy(new_entry, true, next_table, guard) |
2340 | 0 | .unwrap(); |
2341 | 0 | } |
2342 | | |
2343 | | // Mark the entry as copied. |
2344 | 0 | let copied = found |
2345 | 0 | .raw |
2346 | 0 | .map_addr(|addr| addr | Entry::COPYING | Entry::COPIED); Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::copy_at_incremental::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>>::{closure#1}Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::copy_at_incremental::<_>::{closure#1} |
2347 | | |
2348 | | // Note that we already wrote the COPYING bit, so no one is writing to the old |
2349 | | // entry except us. |
2350 | | // |
2351 | | // Note that the `SeqCst` is necessary to make the store visible to threads |
2352 | | // that are unparked. |
2353 | 0 | entry.store(copied, Ordering::SeqCst); |
2354 | | |
2355 | | // Notify any writers that the copy has completed. |
2356 | 0 | table.state().parker.unpark(entry); |
2357 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::copy_at_incremental::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::copy_at_incremental::<_> |
2358 | | |
2359 | | // Copy an entry into the table, returning the index it was inserted into. |
2360 | | // |
2361 | | // This is an optimized version of `insert_entry` where the caller is the only writer |
2362 | | // inserting the given key into the new table, as it has already been marked as copying. |
2363 | | // |
2364 | | // # Safety |
2365 | | // |
2366 | | // The new entry must be valid for reads. |
2367 | 0 | unsafe fn insert_copy( |
2368 | 0 | &self, |
2369 | 0 | new_entry: Tagged<Entry<K, V>>, |
2370 | 0 | resize: bool, |
2371 | 0 | table: &Table<Entry<K, V>>, |
2372 | 0 | guard: &impl VerifiedGuard, |
2373 | 0 | ) -> Option<(Table<Entry<K, V>>, usize)> { |
2374 | | // Safety: The new entry is guaranteed to be valid for reads. |
2375 | 0 | let key = unsafe { &(*new_entry.ptr).key }; |
2376 | | |
2377 | 0 | let mut table = *table; |
2378 | 0 | let (h1, h2) = self.hash(key); |
2379 | | |
2380 | | loop { |
2381 | | // Initialize the probe state. |
2382 | 0 | let mut probe = Probe::start(h1, table.mask); |
2383 | | |
2384 | | // Probe until we reach the limit. |
2385 | 0 | while probe.len <= table.limit { |
2386 | | // Safety: `probe.i` is always in-bounds for the table length. |
2387 | 0 | let meta_entry = unsafe { table.meta(probe.i) }; |
2388 | | |
2389 | | // Load the entry metadata first for cheap searches. |
2390 | 0 | let meta = meta_entry.load(Ordering::Acquire); |
2391 | | |
2392 | | // The entry is empty, try to insert. |
2393 | 0 | if meta == meta::EMPTY { |
2394 | | // Safety: `probe.i` is always in-bounds for the table length. |
2395 | 0 | let entry = unsafe { table.entry(probe.i) }; |
2396 | | |
2397 | | // Try to claim the entry. |
2398 | 0 | match guard.compare_exchange( |
2399 | 0 | entry, |
2400 | 0 | ptr::null_mut(), |
2401 | 0 | new_entry.raw, |
2402 | 0 | Ordering::Release, |
2403 | 0 | Ordering::Acquire, |
2404 | 0 | ) { |
2405 | | // Successfully inserted. |
2406 | | Ok(_) => { |
2407 | | // Update the metadata table. |
2408 | 0 | meta_entry.store(h2, Ordering::Release); |
2409 | 0 | return Some((table, probe.i)); |
2410 | | } |
2411 | 0 | Err(found) => { |
2412 | 0 | let found = found.unpack(); |
2413 | | |
2414 | | // The entry was deleted or copied. |
2415 | 0 | let meta = if found.ptr.is_null() { |
2416 | 0 | meta::TOMBSTONE |
2417 | | } else { |
2418 | | // Safety: We performed a protected load of the pointer using a verified guard with |
2419 | | // `Acquire` and ensured that it is non-null, meaning it is valid for reads as long |
2420 | | // as we hold the guard. |
2421 | 0 | let found_ref = unsafe { &(*found.ptr) }; |
2422 | | |
2423 | | // Ensure the meta table is updated to avoid breaking the probe chain. |
2424 | 0 | let hash = self.hasher.hash_one(&found_ref.key); |
2425 | 0 | meta::h2(hash) |
2426 | | }; |
2427 | | |
2428 | 0 | if meta_entry.load(Ordering::Relaxed) == meta::EMPTY { |
2429 | 0 | meta_entry.store(meta, Ordering::Release); |
2430 | 0 | } |
2431 | | } |
2432 | | } |
2433 | 0 | } |
2434 | | |
2435 | | // Continue probing. |
2436 | 0 | probe.next(table.mask); |
2437 | | } |
2438 | | |
2439 | 0 | if !resize { |
2440 | 0 | return None; |
2441 | 0 | } |
2442 | | |
2443 | | // Insert into the next table. |
2444 | 0 | table = self.get_or_alloc_next(None, table); |
2445 | | } |
2446 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::insert_copy::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::insert_copy::<_> |
2447 | | |
2448 | | // Update the copy state and attempt to promote a table to the root. |
2449 | | // |
2450 | | // Returns `true` if the table was promoted. |
2451 | 0 | fn try_promote( |
2452 | 0 | &self, |
2453 | 0 | table: &Table<Entry<K, V>>, |
2454 | 0 | next: &Table<Entry<K, V>>, |
2455 | 0 | copied: usize, |
2456 | 0 | guard: &impl VerifiedGuard, |
2457 | 0 | ) -> bool { |
2458 | 0 | let state = next.state(); |
2459 | | |
2460 | | // Update the copy count. |
2461 | 0 | let copied = if copied > 0 { |
2462 | 0 | state.copied.fetch_add(copied, Ordering::AcqRel) + copied |
2463 | | } else { |
2464 | 0 | state.copied.load(Ordering::Acquire) |
2465 | | }; |
2466 | | |
2467 | | // If we copied all the entries in the table, we can try to promote. |
2468 | 0 | if copied == table.len() { |
2469 | 0 | let root = self.table.load(Ordering::Relaxed); |
2470 | | |
2471 | | // Only promote root copies. |
2472 | | // |
2473 | | // We can't promote a nested copy before it's parent has finished, as |
2474 | | // it may not contain all the entries in the table. |
2475 | 0 | if table.raw == root { |
2476 | | // Try to update the root. |
2477 | 0 | if self |
2478 | 0 | .table |
2479 | 0 | .compare_exchange(table.raw, next.raw, Ordering::Release, Ordering::Acquire) |
2480 | 0 | .is_ok() |
2481 | | { |
2482 | | // Successfully promoted the table. |
2483 | | // |
2484 | | // Note that the `SeqCst` is necessary to make the store visible to threads |
2485 | | // that are unparked. |
2486 | 0 | state.status.store(State::PROMOTED, Ordering::SeqCst); |
2487 | | |
2488 | | // Retire the old table. |
2489 | | // |
2490 | | // Safety: `table.raw` is a valid pointer to the table we just copied from. |
2491 | | // Additionally, the CAS above made the previous table unreachable from the |
2492 | | // root pointer, allowing it to be safely retired. |
2493 | | unsafe { |
2494 | 0 | guard.defer_retire(table.raw, |table, collector| { |
2495 | | // Note that we do not drop entries because they have been copied to |
2496 | | // the new root. |
2497 | 0 | drop_table(Table::from_raw(table), collector); |
2498 | 0 | }); Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::try_promote::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>>::{closure#0}Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::try_promote::<_>::{closure#0} |
2499 | | } |
2500 | 0 | } |
2501 | | |
2502 | | // Wake up any writers waiting for the resize to complete. |
2503 | 0 | state.parker.unpark(&state.status); |
2504 | 0 | return true; |
2505 | 0 | } |
2506 | 0 | } |
2507 | | |
2508 | | // Not ready to promote yet. |
2509 | 0 | false |
2510 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::try_promote::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::try_promote::<_> |
2511 | | |
2512 | | // Completes all pending copies in incremental mode to get a clean copy of the table. |
2513 | | // |
2514 | | // This is necessary for operations like `iter` or `clear`, where entries in multiple tables |
2515 | | // can cause lead to incomplete results. |
2516 | | #[inline] |
2517 | 0 | fn linearize( |
2518 | 0 | &self, |
2519 | 0 | mut table: Table<Entry<K, V>>, |
2520 | 0 | guard: &impl VerifiedGuard, |
2521 | 0 | ) -> Table<Entry<K, V>> { |
2522 | 0 | if self.is_incremental() { |
2523 | | // If we're in incremental resize mode, we need to complete any in-progress resizes to |
2524 | | // ensure we don't miss any entries in the next table. We can't iterate over both because |
2525 | | // we risk returning the same entry twice. |
2526 | 0 | while table.next_table().is_some() { |
2527 | 0 | table = self.help_copy(true, &table, guard); |
2528 | 0 | } |
2529 | 0 | } |
2530 | | |
2531 | 0 | table |
2532 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::linearize::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::linearize::<_> |
2533 | | |
2534 | | // Wait for an incremental copy of a given entry to complete. |
2535 | | #[cold] |
2536 | | #[inline(never)] |
2537 | 0 | fn wait_copied(&self, i: usize, table: &Table<Entry<K, V>>) { |
2538 | | // Avoid spinning in tests, which can hide race conditions. |
2539 | | const SPIN_WAIT: usize = if cfg!(any(test, debug_assertions)) { |
2540 | | 1 |
2541 | | } else { |
2542 | | 5 |
2543 | | }; |
2544 | | |
2545 | 0 | let entry = unsafe { table.entry(i) }; |
2546 | | |
2547 | | // Spin for a short while, waiting for the entry to be copied. |
2548 | 0 | for spun in 0..SPIN_WAIT { |
2549 | | // The entry was copied. |
2550 | 0 | let entry = entry.load(Ordering::Acquire).unpack(); |
2551 | 0 | if entry.tag() & Entry::COPIED != 0 { |
2552 | 0 | return; |
2553 | 0 | } |
2554 | | |
2555 | 0 | for _ in 0..(spun * spun) { |
2556 | 0 | hint::spin_loop(); |
2557 | 0 | } |
2558 | | } |
2559 | | |
2560 | | // Park until the copy completes. |
2561 | 0 | let parker = &table.state().parker; |
2562 | 0 | parker.park(entry, |entry| entry.addr() & Entry::COPIED == 0); Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::wait_copied::{closure#0}Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::wait_copied::{closure#0} |
2563 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::wait_copied Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::wait_copied |
2564 | | |
2565 | | /// Retire an entry that was removed from the current table, but may still be reachable from |
2566 | | /// previous tables. |
2567 | | /// |
2568 | | /// # Safety |
2569 | | /// |
2570 | | /// The entry must be a valid pointer that is unreachable from the current table. Additionally, |
2571 | | /// it is *undefined behavior* to call this method multiple times for the same entry. |
2572 | | #[inline] |
2573 | 0 | unsafe fn defer_retire( |
2574 | 0 | &self, |
2575 | 0 | entry: Tagged<Entry<K, V>>, |
2576 | 0 | table: &Table<Entry<K, V>>, |
2577 | 0 | guard: &impl VerifiedGuard, |
2578 | 0 | ) { |
2579 | 0 | match self.resize { |
2580 | | // Safety: In blocking resize mode, we only ever write to the root table, so the entry |
2581 | | // is inaccessible from all tables. |
2582 | 0 | ResizeMode::Blocking => unsafe { |
2583 | 0 | guard.defer_retire(entry.ptr, seize::reclaim::boxed); |
2584 | 0 | }, |
2585 | | // In incremental resize mode, the entry may be accessible in previous tables. |
2586 | | ResizeMode::Incremental(_) => { |
2587 | 0 | if entry.tag() & Entry::BORROWED == 0 { |
2588 | | // Safety: If the entry is not borrowed, meaning it is not in any previous tables, |
2589 | | // it is inaccessible even if the current table is not root. Thus we can safely retire. |
2590 | 0 | unsafe { guard.defer_retire(entry.ptr, seize::reclaim::boxed) }; |
2591 | 0 | return; |
2592 | 0 | } |
2593 | | |
2594 | 0 | let root = self.root(guard); |
2595 | | |
2596 | | // Check if our table, or any subsequent table, is the root. |
2597 | 0 | let mut next = Some(*table); |
2598 | 0 | while let Some(table) = next { |
2599 | 0 | if table.raw == root.raw { |
2600 | | // Safety: The root table is our table or a table that succeeds ours. |
2601 | | // Thus any previous tables are unreachable from the root, so we can safely retire. |
2602 | 0 | unsafe { guard.defer_retire(entry.ptr, seize::reclaim::boxed) }; |
2603 | 0 | return; |
2604 | 0 | } |
2605 | | |
2606 | 0 | next = table.next_table(); |
2607 | | } |
2608 | | |
2609 | | // Otherwise, we have to wait for the table we are copying from to be reclaimed. |
2610 | | // |
2611 | | // Find the table we are copying from, searching from the root. |
2612 | 0 | let mut prev = root; |
2613 | | |
2614 | | loop { |
2615 | 0 | let next = prev.next_table().unwrap(); |
2616 | | |
2617 | | // Defer the entry to be retired by the table we are copying from. |
2618 | 0 | if next.raw == table.raw { |
2619 | 0 | prev.state().deferred.push(entry.ptr); |
2620 | 0 | return; |
2621 | 0 | } |
2622 | | |
2623 | 0 | prev = next; |
2624 | | } |
2625 | | } |
2626 | | } |
2627 | 0 | } Unexecuted instantiation: <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState>>::defer_retire::<papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> Unexecuted instantiation: <papaya::raw::HashMap<_, _, _>>::defer_retire::<_> |
2628 | | } |
2629 | | |
2630 | | // An iterator over the keys and values of this table. |
2631 | | pub struct Iter<'g, K, V, G> { |
2632 | | i: usize, |
2633 | | table: Table<Entry<K, V>>, |
2634 | | guard: &'g G, |
2635 | | } |
2636 | | |
2637 | | impl<'g, K: 'g, V: 'g, G> Iterator for Iter<'g, K, V, G> |
2638 | | where |
2639 | | G: VerifiedGuard, |
2640 | | { |
2641 | | type Item = (&'g K, &'g V); |
2642 | | |
2643 | | #[inline] |
2644 | 162 | fn next(&mut self) -> Option<Self::Item> { |
2645 | | // The table has not yet been allocated. |
2646 | 162 | if self.table.raw.is_null() { |
2647 | 162 | return None; |
2648 | 0 | } |
2649 | | |
2650 | | loop { |
2651 | | // Iterated over every entry in the table, we're done. |
2652 | 0 | if self.i >= self.table.len() { |
2653 | 0 | return None; |
2654 | 0 | } |
2655 | | |
2656 | | // Load the entry metadata first to ensure consistency with calls to `get`. |
2657 | | // |
2658 | | // Safety: We verified that `self.i` is in-bounds above. |
2659 | 0 | let meta = unsafe { self.table.meta(self.i) }.load(Ordering::Acquire); |
2660 | | |
2661 | | // The entry is empty or deleted. |
2662 | 0 | if matches!(meta, meta::EMPTY | meta::TOMBSTONE) { |
2663 | 0 | self.i += 1; |
2664 | 0 | continue; |
2665 | 0 | } |
2666 | | |
2667 | | // Load the entry. |
2668 | | // |
2669 | | // Safety: We verified that `self.i` is in-bounds above. |
2670 | 0 | let entry = self |
2671 | 0 | .guard |
2672 | 0 | .protect(unsafe { self.table.entry(self.i) }, Ordering::Acquire) |
2673 | 0 | .unpack(); |
2674 | | |
2675 | | // The entry was deleted. |
2676 | 0 | if entry.ptr.is_null() { |
2677 | 0 | self.i += 1; |
2678 | 0 | continue; |
2679 | 0 | } |
2680 | | |
2681 | | // Safety: We performed a protected load of the pointer using a verified guard with |
2682 | | // `Acquire` and ensured that it is non-null, meaning it is valid for reads as long |
2683 | | // as we hold the guard. |
2684 | 0 | let entry_ref = unsafe { &(*entry.ptr) }; |
2685 | | |
2686 | 0 | self.i += 1; |
2687 | 0 | return Some((&entry_ref.key, &entry_ref.value)); |
2688 | | } |
2689 | 162 | } <papaya::raw::Iter<bytes::bytes::Bytes, (), papaya::raw::utils::MapGuard<seize::guard::LocalGuard>> as core::iter::traits::iterator::Iterator>::next Line | Count | Source | 2644 | 162 | fn next(&mut self) -> Option<Self::Item> { | 2645 | | // The table has not yet been allocated. | 2646 | 162 | if self.table.raw.is_null() { | 2647 | 162 | return None; | 2648 | 0 | } | 2649 | | | 2650 | | loop { | 2651 | | // Iterated over every entry in the table, we're done. | 2652 | 0 | if self.i >= self.table.len() { | 2653 | 0 | return None; | 2654 | 0 | } | 2655 | | | 2656 | | // Load the entry metadata first to ensure consistency with calls to `get`. | 2657 | | // | 2658 | | // Safety: We verified that `self.i` is in-bounds above. | 2659 | 0 | let meta = unsafe { self.table.meta(self.i) }.load(Ordering::Acquire); | 2660 | | | 2661 | | // The entry is empty or deleted. | 2662 | 0 | if matches!(meta, meta::EMPTY | meta::TOMBSTONE) { | 2663 | 0 | self.i += 1; | 2664 | 0 | continue; | 2665 | 0 | } | 2666 | | | 2667 | | // Load the entry. | 2668 | | // | 2669 | | // Safety: We verified that `self.i` is in-bounds above. | 2670 | 0 | let entry = self | 2671 | 0 | .guard | 2672 | 0 | .protect(unsafe { self.table.entry(self.i) }, Ordering::Acquire) | 2673 | 0 | .unpack(); | 2674 | | | 2675 | | // The entry was deleted. | 2676 | 0 | if entry.ptr.is_null() { | 2677 | 0 | self.i += 1; | 2678 | 0 | continue; | 2679 | 0 | } | 2680 | | | 2681 | | // Safety: We performed a protected load of the pointer using a verified guard with | 2682 | | // `Acquire` and ensured that it is non-null, meaning it is valid for reads as long | 2683 | | // as we hold the guard. | 2684 | 0 | let entry_ref = unsafe { &(*entry.ptr) }; | 2685 | | | 2686 | 0 | self.i += 1; | 2687 | 0 | return Some((&entry_ref.key, &entry_ref.value)); | 2688 | | } | 2689 | 162 | } |
Unexecuted instantiation: <papaya::raw::Iter<_, _, _> as core::iter::traits::iterator::Iterator>::next |
2690 | | } |
2691 | | |
2692 | | // Safety: An iterator holds a shared reference to the HashMap |
2693 | | // and Guard, and outputs shared references to keys and values. |
2694 | | // Thus everything must be `Sync` for the iterator to be `Send` |
2695 | | // or `Sync`. |
2696 | | // |
2697 | | // It is not possible to obtain an owned key, value, or guard |
2698 | | // from an iterator, so `Send` is not a required bound. |
2699 | | unsafe impl<K, V, G> Send for Iter<'_, K, V, G> |
2700 | | where |
2701 | | K: Sync, |
2702 | | V: Sync, |
2703 | | G: Sync, |
2704 | | { |
2705 | | } |
2706 | | |
2707 | | unsafe impl<K, V, G> Sync for Iter<'_, K, V, G> |
2708 | | where |
2709 | | K: Sync, |
2710 | | V: Sync, |
2711 | | G: Sync, |
2712 | | { |
2713 | | } |
2714 | | |
2715 | | impl<K, V, G> Clone for Iter<'_, K, V, G> { |
2716 | | #[inline] |
2717 | 0 | fn clone(&self) -> Self { |
2718 | 0 | Iter { |
2719 | 0 | i: self.i, |
2720 | 0 | table: self.table, |
2721 | 0 | guard: self.guard, |
2722 | 0 | } |
2723 | 0 | } |
2724 | | } |
2725 | | |
2726 | | impl<K, V, S> Drop for HashMap<K, V, S> { |
2727 | 24.9k | fn drop(&mut self) { |
2728 | 24.9k | let mut raw = *self.table.get_mut(); |
2729 | | |
2730 | | // Make sure all objects are reclaimed before the collector is dropped. |
2731 | | // |
2732 | | // Dropping a table depends on accessing the collector for deferred retirement, |
2733 | | // using the shared collector pointer that is invalidated by drop. |
2734 | | // |
2735 | | // Safety: We have a unique reference to the collector. |
2736 | 24.9k | unsafe { self.collector.reclaim_all() }; |
2737 | | |
2738 | | // Drop all nested tables and entries. |
2739 | 24.9k | while !raw.is_null() { |
2740 | 0 | // Safety: The root and next tables are always valid pointers to a |
2741 | 0 | // table allocation, or null. |
2742 | 0 | let mut table = unsafe { Table::from_raw(raw) }; |
2743 | 0 |
|
2744 | 0 | // Read the next table pointer before dropping the current one. |
2745 | 0 | let next = *table.state_mut().next.get_mut(); |
2746 | 0 |
|
2747 | 0 | // Safety: We have unique access to the table and do |
2748 | 0 | // not access the entries after this call. |
2749 | 0 | unsafe { drop_entries(table) }; |
2750 | 0 |
|
2751 | 0 | // Safety: We have unique access to the table and do |
2752 | 0 | // not access it after this call. |
2753 | 0 | unsafe { drop_table(table, &self.collector) }; |
2754 | 0 |
|
2755 | 0 | // Continue for all nested tables. |
2756 | 0 | raw = next; |
2757 | 0 | } |
2758 | 24.9k | } <papaya::raw::HashMap<bytes::bytes::Bytes, (), std::hash::random::RandomState> as core::ops::drop::Drop>::drop Line | Count | Source | 2727 | 24.9k | fn drop(&mut self) { | 2728 | 24.9k | let mut raw = *self.table.get_mut(); | 2729 | | | 2730 | | // Make sure all objects are reclaimed before the collector is dropped. | 2731 | | // | 2732 | | // Dropping a table depends on accessing the collector for deferred retirement, | 2733 | | // using the shared collector pointer that is invalidated by drop. | 2734 | | // | 2735 | | // Safety: We have a unique reference to the collector. | 2736 | 24.9k | unsafe { self.collector.reclaim_all() }; | 2737 | | | 2738 | | // Drop all nested tables and entries. | 2739 | 24.9k | while !raw.is_null() { | 2740 | 0 | // Safety: The root and next tables are always valid pointers to a | 2741 | 0 | // table allocation, or null. | 2742 | 0 | let mut table = unsafe { Table::from_raw(raw) }; | 2743 | 0 |
| 2744 | 0 | // Read the next table pointer before dropping the current one. | 2745 | 0 | let next = *table.state_mut().next.get_mut(); | 2746 | 0 |
| 2747 | 0 | // Safety: We have unique access to the table and do | 2748 | 0 | // not access the entries after this call. | 2749 | 0 | unsafe { drop_entries(table) }; | 2750 | 0 |
| 2751 | 0 | // Safety: We have unique access to the table and do | 2752 | 0 | // not access it after this call. | 2753 | 0 | unsafe { drop_table(table, &self.collector) }; | 2754 | 0 |
| 2755 | 0 | // Continue for all nested tables. | 2756 | 0 | raw = next; | 2757 | 0 | } | 2758 | 24.9k | } |
Unexecuted instantiation: <papaya::raw::HashMap<_, _, _> as core::ops::drop::Drop>::drop |
2759 | | } |
2760 | | |
2761 | | // Drop all entries in this table. |
2762 | | // |
2763 | | // # Safety |
2764 | | // |
2765 | | // The table entries must not be accessed after this call. |
2766 | 0 | unsafe fn drop_entries<K, V>(table: Table<Entry<K, V>>) { |
2767 | 0 | for i in 0..table.len() { |
2768 | | // Safety: `i` is in-bounds and we have unique access to the table. |
2769 | 0 | let entry = unsafe { (*table.entry(i).as_ptr()).unpack() }; |
2770 | | |
2771 | | // The entry was copied, or there is nothing to deallocate. |
2772 | 0 | if entry.ptr.is_null() || entry.tag() & Entry::COPYING != 0 { |
2773 | 0 | continue; |
2774 | 0 | } |
2775 | | |
2776 | | // Drop the entry. |
2777 | | // |
2778 | | // Safety: We verified that the table is non-null and will |
2779 | | // not be accessed after this call. Additionally, we ensured |
2780 | | // that the entry is not copied to avoid double freeing entries |
2781 | | // that may exist in multiple tables. |
2782 | 0 | unsafe { drop(Box::from_raw(entry.ptr)) } |
2783 | | } |
2784 | 0 | } Unexecuted instantiation: papaya::raw::drop_entries::<bytes::bytes::Bytes, ()> Unexecuted instantiation: papaya::raw::drop_entries::<_, _> |
2785 | | |
2786 | | // Drop the table allocation. |
2787 | | // |
2788 | | // # Safety |
2789 | | // |
2790 | | // The table must not be accessed after this call. |
2791 | 0 | unsafe fn drop_table<K, V>(mut table: Table<Entry<K, V>>, collector: &Collector) { |
2792 | | // Drop any entries that were deferred during an incremental resize. |
2793 | | // |
2794 | | // Safety: Entries are deferred after they are made unreachable from the |
2795 | | // next table during a resize from this table. This table must have been accessible |
2796 | | // from the root for any entry to have been deferred. Thus it is being retired now, |
2797 | | // *after* the entry was made inaccessible from the next table. Additionally, for |
2798 | | // this table to have been retired, it also must no longer be accessible from the root, |
2799 | | // meaning that the entry has been totally removed from the map, and can be safely |
2800 | | // retired. |
2801 | 0 | table |
2802 | 0 | .state_mut() |
2803 | 0 | .deferred |
2804 | 0 | .drain(|entry| unsafe { collector.retire(entry, seize::reclaim::boxed) });Unexecuted instantiation: papaya::raw::drop_table::<bytes::bytes::Bytes, ()>::{closure#0}Unexecuted instantiation: papaya::raw::drop_table::<_, _>::{closure#0} |
2805 | | |
2806 | | // Deallocate the table. |
2807 | | // |
2808 | | // Safety: The caller guarantees that the table will not be accessed after this call. |
2809 | 0 | unsafe { Table::dealloc(table) }; |
2810 | 0 | } Unexecuted instantiation: papaya::raw::drop_table::<bytes::bytes::Bytes, ()> Unexecuted instantiation: papaya::raw::drop_table::<_, _> |
2811 | | |
2812 | | // Entry metadata, inspired by `hashbrown`. |
2813 | | mod meta { |
2814 | | use std::mem; |
2815 | | |
2816 | | // Indicates an empty entry. |
2817 | | pub const EMPTY: u8 = 0x80; |
2818 | | |
2819 | | // Indicates an entry that has been deleted. |
2820 | | pub const TOMBSTONE: u8 = u8::MAX; |
2821 | | |
2822 | | // Returns the primary hash for an entry. |
2823 | | #[inline] |
2824 | 0 | pub fn h1(hash: u64) -> usize { |
2825 | 0 | hash as usize |
2826 | 0 | } Unexecuted instantiation: papaya::raw::meta::h1 Unexecuted instantiation: papaya::raw::meta::h1 Unexecuted instantiation: papaya::raw::meta::h1 |
2827 | | |
2828 | | /// Return a byte of hash metadata, used for cheap searches. |
2829 | | #[inline] |
2830 | 0 | pub fn h2(hash: u64) -> u8 { |
2831 | | const MIN_HASH_LEN: usize = if mem::size_of::<usize>() < mem::size_of::<u64>() { |
2832 | | mem::size_of::<usize>() |
2833 | | } else { |
2834 | | mem::size_of::<u64>() |
2835 | | }; |
2836 | | |
2837 | | // Grab the top 7 bits of the hash. |
2838 | | // |
2839 | | // While the hash is normally a full 64-bit value, some hash functions |
2840 | | // (such as fxhash) produce a usize result instead, which means that the |
2841 | | // top 32 bits are 0 on 32-bit platforms. |
2842 | 0 | let top7 = hash >> (MIN_HASH_LEN * 8 - 7); |
2843 | 0 | (top7 & 0x7f) as u8 |
2844 | 0 | } Unexecuted instantiation: papaya::raw::meta::h2 Unexecuted instantiation: papaya::raw::meta::h2 Unexecuted instantiation: papaya::raw::meta::h2 |
2845 | | } |