kovan_map/hashmap/resize.rs
1//! Replacing the table: a resize copies it into a table of another capacity, a clear replaces it
2//! by an empty one. Both take the single resizer latch, freeze every link of the old table in
3//! chain order (copying, for a resize, each node that is not deleted), and publish the new table
4//! with the exact count of what it holds.
5//!
6//! Freezing is what makes a write final: every write is one CAS on one link, and a frozen link
7//! fails every CAS a writer makes (it expects the word without the flag). A write whose CAS
8//! landed before the freeze reached its link is in the chain the copy reads; a write that meets
9//! a frozen link waits for the new table and writes there. No write lands in a table after its
10//! copy read the link, so no landed write is lost and none is retried: an insert that landed
11//! never meets its own copy and never reports it as another caller's entry, and a remove never
12//! removes a copy of what it already removed. Modelled in `proofs/tla/chained/ChainedMap.tla`
13//! (actions Z0 to Z6), where 0.1.20's read-then-revalidate protocol (Mutation "revalidate")
14//! breaks both.
15
16use super::node::{FROZEN, Node, is_frozen, is_held, is_marked, ptr, with, word};
17use super::table::TableRef;
18use super::{HashMap, MIN_CAPACITY};
19use crate::sync::spin_hint;
20use core::hash::{BuildHasher, Hash};
21use core::sync::atomic::Ordering;
22use kovan::{Atomic, Guard, Shared, alloc_node, pin, retire_node};
23
24extern crate alloc;
25
26impl<K, V, S> HashMap<K, V, S>
27where
28 K: Hash + Eq + Clone + Send + 'static,
29 V: Clone + Send + 'static,
30 S: BuildHasher,
31{
32 /// Spin until the resize or clear in flight has published its table (a writer that met a
33 /// frozen link, its guard dropped).
34 #[inline]
35 pub(super) fn wait_for_resize(&self) {
36 while self.resizing.load(Ordering::Acquire) {
37 spin_hint();
38 }
39 }
40
41 /// Resize the table to `new_capacity` buckets (the single resizer wins; a call that finds
42 /// the latch taken returns at once).
43 pub(super) fn try_resize(&self, new_capacity: usize) {
44 if self
45 .resizing
46 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
47 .is_err()
48 {
49 return;
50 }
51 let new_capacity = new_capacity
52 .next_power_of_two()
53 .max(MIN_CAPACITY)
54 .max(self.floor);
55 let guard = pin();
56 let old = self.table_ref(&guard);
57 if old.capacity() != new_capacity {
58 let new = TableRef::alloc(new_capacity);
59 // A `K::clone` or `V::clone` that panics in the migration unwinds through this,
60 // which undoes it (see `Undo`): the old table stays current and every writer goes
61 // on, where the latch held for good would have stopped them all.
62 let undo = Undo {
63 old,
64 new,
65 guard: &guard,
66 latch: &self.resizing,
67 };
68 let copied = Self::migrate(old, Some(new), &guard);
69 core::mem::forget(undo);
70 // Relaxed: the table's publication below releases it.
71 new.count().store(copied as isize, Ordering::Relaxed);
72 self.publish(old, new);
73 }
74 drop(guard);
75 self.resizing.store(false, Ordering::Release);
76 }
77
78 /// Clears the map, removing all key-value pairs.
79 ///
80 /// Linearizes when the empty table is published: a write that landed before its link was
81 /// frozen is cleared, a write that met a frozen link lands in the empty table after.
82 pub fn clear(&self) {
83 while self
84 .resizing
85 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
86 .is_err()
87 {
88 spin_hint();
89 }
90 let guard = pin();
91 let old = self.table_ref(&guard);
92 Self::migrate(old, None, &guard);
93 self.publish(old, TableRef::alloc(old.capacity()));
94 drop(guard);
95 self.resizing.store(false, Ordering::Release);
96 }
97
98 /// Freeze `link` and return its frozen word. A held link is waited for: only its holder
99 /// writes it, and the conditional write holding it lands in this table before the link
100 /// freezes (so the copy reads it).
101 fn freeze(link: &Atomic<Node<K, V>>, guard: &Guard) -> *mut Node<K, V> {
102 loop {
103 // A word a failed CAS returns is reloaded here rather than used: only a load
104 // protects the node it names.
105 let w = link.load(Ordering::Acquire, guard).as_raw();
106 if is_frozen(w) {
107 return w;
108 }
109 if is_held(w) {
110 spin_hint();
111 continue;
112 }
113 let frozen = with(w, FROZEN);
114 // AcqRel: the copy below reads the fields of the node the word names.
115 if link
116 .compare_exchange(
117 word(w),
118 word(frozen),
119 Ordering::AcqRel,
120 Ordering::Relaxed,
121 guard,
122 )
123 .is_ok()
124 {
125 return frozen;
126 }
127 }
128 }
129
130 /// Freeze every link of `old`, bucket by bucket in chain order, and copy each node that is
131 /// not deleted into `into` (a table no other thread sees yet). Returns the number copied.
132 fn migrate(old: TableRef<K, V>, into: Option<TableRef<K, V>>, guard: &Guard) -> usize {
133 let mut copied = 0;
134 for b in 0..old.capacity() {
135 let mut w = Self::freeze(old.bucket(b), guard);
136 loop {
137 let cur = ptr(w);
138 if cur.is_null() {
139 break;
140 }
141 // SAFETY: the frozen link that names it named it when this thread froze it, and
142 // a frozen link never changes, so it stays reachable (never retired) from here.
143 let node = unsafe { &*cur };
144 w = Self::freeze(&node.next, guard);
145 if is_marked(w) {
146 continue;
147 }
148 if let Some(new) = into {
149 let dst = new.bucket(new.bucket_index(node.hash));
150 let copy = Node::new(node.hash, node.key.clone(), node.value.clone());
151 // Relaxed: `new` is private to this thread until its publication releases it.
152 copy.next
153 .store(dst.load(Ordering::Relaxed, guard), Ordering::Relaxed);
154 dst.store(word(alloc_node(copy)), Ordering::Relaxed);
155 copied += 1;
156 }
157 }
158 }
159 copied
160 }
161
162 /// Make `new` the current table and retire `old`, every link of which is frozen.
163 fn publish(&self, old: TableRef<K, V>, new: TableRef<K, V>) {
164 // Release: a thread that acquires the new table sees its buckets, nodes and count.
165 // SAFETY: `new` is a live table allocation.
166 self.table
167 .store(unsafe { Shared::from_raw(new.as_raw()) }, Ordering::Release);
168 // SAFETY: the old table is unreachable from the map now; kovan frees it (and the nodes
169 // its frozen chains hold) once no guard that loaded it remains. TableProxy is
170 // #[repr(C)] with its RetiredNode at offset 0.
171 unsafe { retire_node(old.take_proxy()) };
172 }
173}
174
175/// Undoes a migration that unwinds (a clone of a key or value panicked in it): the links it
176/// froze in the old table thawed, the new table (no other thread ever saw it) freed with the
177/// copies made so far, and the latch let go, so the map goes on with its old table.
178struct Undo<'a, K: 'static, V: 'static> {
179 old: TableRef<K, V>,
180 new: TableRef<K, V>,
181 guard: &'a Guard,
182 latch: &'a crate::sync::AtomicBool,
183}
184
185impl<K: 'static, V: 'static> Drop for Undo<'_, K, V> {
186 fn drop(&mut self) {
187 thaw_links(self.old, self.guard);
188 // SAFETY: never published, so this thread alone reaches it and its copies.
189 unsafe { self.new.free() };
190 self.latch.store(false, Ordering::Release);
191 }
192}
193
194/// Clears the frozen flag of every link a migration froze in `table`: what a migration that
195/// cannot finish leaves behind. A migration freezes each chain from its head in order, so
196/// the frozen links of a chain are a prefix of it; while the latch is held no other thread
197/// changes a frozen link (a writer that meets one waits for the latch).
198fn thaw_links<K: 'static, V: 'static>(table: TableRef<K, V>, guard: &Guard) {
199 for b in 0..table.capacity() {
200 let mut link = table.bucket(b);
201 loop {
202 let w = link.load(Ordering::Acquire, guard).as_raw();
203 if !is_frozen(w) {
204 break;
205 }
206 let thawed = w.map_addr(|a| a & !FROZEN);
207 // AcqRel: a writer that loads the thawed word sees the chain as it was.
208 let _ = link.compare_exchange(
209 word(w),
210 word(thawed),
211 Ordering::AcqRel,
212 Ordering::Relaxed,
213 guard,
214 );
215 let cur = ptr(w);
216 if cur.is_null() {
217 break;
218 }
219 // SAFETY: a frozen link that names it named it while this thread froze it, so it
220 // is reachable and never retired while the guard is held.
221 link = unsafe { &(*cur).next };
222 }
223 }
224}