Check that Redirect is not passed to exchange()
[junction.git] / junction / ConcurrentMap_Linear.h
1 /*------------------------------------------------------------------------
2   Junction: Concurrent data structures in C++
3   Copyright (c) 2016 Jeff Preshing
4
5   Distributed under the Simplified BSD License.
6   Original location: https://github.com/preshing/junction
7
8   This software is distributed WITHOUT ANY WARRANTY; without even the
9   implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10   See the LICENSE file for more information.
11 ------------------------------------------------------------------------*/
12
13 #ifndef JUNCTION_CONCURRENTMAP_LINEAR_H
14 #define JUNCTION_CONCURRENTMAP_LINEAR_H
15
16 #include <junction/Core.h>
17 #include <junction/details/Linear.h>
18 #include <junction/QSBR.h>
19 #include <turf/Heap.h>
20 #include <turf/Trace.h>
21
22 namespace junction {
23
24 TURF_TRACE_DECLARE(ConcurrentMap_Linear, 17)
25
26 template <typename K, typename V, class KT = DefaultKeyTraits<K>, class VT = DefaultValueTraits<V> >
27 class ConcurrentMap_Linear {
28 public:
29     typedef K Key;
30     typedef V Value;
31     typedef KT KeyTraits;
32     typedef VT ValueTraits;
33     typedef typename turf::util::BestFit<Key>::Unsigned Hash;
34     typedef details::Linear<ConcurrentMap_Linear> Details;
35
36 private:
37     turf::Atomic<typename Details::Table*> m_root;
38
39 public:
40     ConcurrentMap_Linear(ureg capacity = Details::InitialSize) : m_root(Details::Table::create(capacity)) {
41     }
42
43     ~ConcurrentMap_Linear() {
44         typename Details::Table* table = m_root.loadNonatomic();
45         table->destroy();
46     }
47
48     // publishTableMigration() is called by exactly one thread from Details::TableMigration::run()
49     // after all the threads participating in the migration have completed their work.
50     void publishTableMigration(typename Details::TableMigration* migration) {
51         // There are no racing calls to this function.
52         typename Details::Table* oldRoot = m_root.loadNonatomic();
53         m_root.store(migration->m_destination, turf::Release);
54         TURF_ASSERT(oldRoot == migration->getSources()[0].table);
55         // Caller will GC the TableMigration and the source table.
56     }
57
58     // A Mutator represents a known cell in the hash table.
59     // It's meant for manipulations within a temporary function scope.
60     // Obviously you must not call QSBR::Update while holding a Mutator.
61     // Any operation that modifies the table (exchangeValue, eraseValue)
62     // may be forced to follow a redirected cell, which changes the Mutator itself.
63     // Note that even if the Mutator was constructed from an existing cell,
64     // exchangeValue() can still trigger a resize if the existing cell was previously marked deleted,
65     // or if another thread deletes the key between the two steps.
66     class Mutator {
67     private:
68         friend class ConcurrentMap_Linear;
69
70         ConcurrentMap_Linear& m_map;
71         typename Details::Table* m_table;
72         typename Details::Cell* m_cell;
73         Value m_value;
74
75         // Constructor: Find existing cell
76         Mutator(ConcurrentMap_Linear& map, Key key, bool) : m_map(map), m_value(Value(ValueTraits::NullValue)) {
77             TURF_TRACE(ConcurrentMap_Linear, 0, "[Mutator] find constructor called", uptr(m_table), uptr(key));
78             Hash hash = KeyTraits::hash(key);
79             for (;;) {
80                 m_table = m_map.m_root.load(turf::Consume);
81                 m_cell = Details::find(hash, m_table);
82                 if (!m_cell)
83                     return;
84                 m_value = m_cell->value.load(turf::Consume);
85                 if (m_value != Value(ValueTraits::Redirect))
86                     return; // Found an existing value
87                 // We've encountered a Redirect value. Help finish the migration.
88                 TURF_TRACE(ConcurrentMap_Linear, 1, "[Mutator] find was redirected", uptr(m_table), 0);
89                 m_table->jobCoordinator.participate();
90                 // Try again using the latest root.
91             }
92         }
93
94         // Constructor: Insert cell
95         Mutator(ConcurrentMap_Linear& map, Key key)
96             : m_map(map), m_table(map.m_root.load(turf::Consume)), m_value(Value(ValueTraits::NullValue)) {
97             TURF_TRACE(ConcurrentMap_Linear, 2, "[Mutator] insert constructor called", uptr(m_table), uptr(key));
98             Hash hash = KeyTraits::hash(key);
99             bool mustDouble = false;
100             for (;;) {
101                 m_table = m_map.m_root.load(turf::Consume);
102                 switch (Details::insert(hash, m_table, m_cell)) { // Modifies m_cell
103                 case Details::InsertResult_InsertedNew: {
104                     // We've inserted a new cell. Don't load m_cell->value.
105                     return;
106                 }
107                 case Details::InsertResult_AlreadyFound: {
108                     // The hash was already found in the table.
109                     m_value = m_cell->value.load(turf::Consume);
110                     if (m_value == Value(ValueTraits::Redirect)) {
111                         // We've encountered a Redirect value.
112                         TURF_TRACE(ConcurrentMap_Linear, 3, "[Mutator] insert was redirected", uptr(m_table), uptr(m_value));
113                         break; // Help finish the migration.
114                     }
115                     return; // Found an existing value
116                 }
117                 case Details::InsertResult_Overflow: {
118                     Details::beginTableMigration(m_map, m_table, mustDouble);
119                     break;
120                 }
121                 }
122                 // A migration has been started (either by us, or another thread). Participate until it's complete.
123                 m_table->jobCoordinator.participate();
124                 // If we still overflow after this, avoid an infinite loop by forcing the next table to double.
125                 mustDouble = true;
126                 // Try again using the latest root.
127             }
128         }
129
130     public:
131         Value getValue() const {
132             // Return previously loaded value. Don't load it again.
133             return Value(m_value);
134         }
135
136         Value exchangeValue(Value desired) {
137             TURF_ASSERT(desired != Value(ValueTraits::NullValue));
138             TURF_ASSERT(desired != Value(ValueTraits::Redirect));
139             TURF_ASSERT(m_cell); // Cell must have been found or inserted
140             TURF_TRACE(ConcurrentMap_Linear, 4, "[Mutator::exchangeValue] called", uptr(m_table), uptr(m_value));
141             bool mustDouble = false;
142             for (;;) {
143                 Value oldValue = m_value;
144                 if (m_cell->value.compareExchangeStrong(m_value, desired, turf::ConsumeRelease)) {
145                     // Exchange was successful. Return previous value.
146                     TURF_TRACE(ConcurrentMap_Linear, 5, "[Mutator::exchangeValue] exchanged Value", uptr(m_value), uptr(desired));
147                     Value result = m_value;
148                     m_value = desired; // Leave the mutator in a valid state
149                     return result;
150                 }
151                 // The CAS failed and m_value has been updated with the latest value.
152                 if (m_value != Value(ValueTraits::Redirect)) {
153                     TURF_TRACE(ConcurrentMap_Linear, 6, "[Mutator::exchangeValue] detected race to write value", uptr(m_table),
154                                uptr(m_value));
155                     if (oldValue == Value(ValueTraits::NullValue) && m_value != Value(ValueTraits::NullValue)) {
156                         TURF_TRACE(ConcurrentMap_Linear, 7, "[Mutator::exchangeValue] racing write inserted new value",
157                                    uptr(m_table), uptr(m_value));
158                     }
159                     // There was a racing write (or erase) to this cell.
160                     // Pretend we exchanged with ourselves, and just let the racing write win.
161                     return desired;
162                 }
163                 // We've encountered a Redirect value. Help finish the migration.
164                 TURF_TRACE(ConcurrentMap_Linear, 8, "[Mutator::exchangeValue] was redirected", uptr(m_table), uptr(m_value));
165                 Hash hash = m_cell->hash.load(turf::Relaxed);
166                 for (;;) {
167                     // Help complete the migration.
168                     m_table->jobCoordinator.participate();
169                     // Try again in the new table.
170                     m_table = m_map.m_root.load(turf::Consume);
171                     m_value = Value(ValueTraits::NullValue);
172                     switch (Details::insert(hash, m_table, m_cell)) { // Modifies m_cell
173                     case Details::InsertResult_AlreadyFound:
174                         m_value = m_cell->value.load(turf::Consume);
175                         if (m_value == Value(ValueTraits::Redirect)) {
176                             TURF_TRACE(ConcurrentMap_Linear, 9, "[Mutator::exchangeValue] was re-redirected", uptr(m_table),
177                                        uptr(m_value));
178                             break;
179                         }
180                         goto breakOuter;
181                     case Details::InsertResult_InsertedNew:
182                         goto breakOuter;
183                     case Details::InsertResult_Overflow:
184                         TURF_TRACE(ConcurrentMap_Linear, 10, "[Mutator::exchangeValue] overflow after redirect", uptr(m_table), 0);
185                         Details::beginTableMigration(m_map, m_table, mustDouble);
186                         break;
187                     }
188                     // If we still overflow after this, avoid an infinite loop by forcing the next table to double.
189                     mustDouble = true;
190                     // We were redirected... again
191                 }
192             breakOuter:;
193                 // Try again in the new table.
194             }
195         }
196
197         void setValue(Value desired) {
198             exchangeValue(desired);
199         }
200
201         Value eraseValue() {
202             TURF_ASSERT(m_cell); // Cell must have been found or inserted
203             TURF_TRACE(ConcurrentMap_Linear, 11, "[Mutator::eraseValue] called", uptr(m_table), m_cell - m_table->getCells());
204             for (;;) {
205                 if (m_value == Value(ValueTraits::NullValue))
206                     return Value(m_value);
207                 TURF_ASSERT(m_cell); // m_value is non-NullValue, therefore cell must have been found or inserted.
208                 if (m_cell->value.compareExchangeStrong(m_value, Value(ValueTraits::NullValue), turf::Consume)) {
209                     // Exchange was successful and a non-NULL value was erased and returned by reference in m_value.
210                     TURF_ASSERT(m_value != ValueTraits::NullValue); // Implied by the test at the start of the loop.
211                     Value result = m_value;
212                     m_value = Value(ValueTraits::NullValue); // Leave the mutator in a valid state
213                     return result;
214                 }
215                 // The CAS failed and m_value has been updated with the latest value.
216                 TURF_TRACE(ConcurrentMap_Linear, 12, "[Mutator::eraseValue] detected race to write value", uptr(m_table),
217                            m_cell - m_table->getCells());
218                 if (m_value != Value(ValueTraits::Redirect)) {
219                     // There was a racing write (or erase) to this cell.
220                     // Pretend we erased nothing, and just let the racing write win.
221                     return Value(ValueTraits::NullValue);
222                 }
223                 // We've been redirected to a new table.
224                 TURF_TRACE(ConcurrentMap_Linear, 13, "[Mutator::eraseValue] was redirected", uptr(m_table),
225                            m_cell - m_table->getCells());
226                 Hash hash = m_cell->hash.load(turf::Relaxed); // Re-fetch hash
227                 for (;;) {
228                     // Help complete the migration.
229                     m_table->jobCoordinator.participate();
230                     // Try again in the new table.
231                     m_table = m_map.m_root.load(turf::Consume);
232                     m_cell = Details::find(hash, m_table);
233                     if (!m_cell) {
234                         m_value = Value(ValueTraits::NullValue);
235                         return m_value;
236                     }
237                     m_value = m_cell->value.load(turf::Relaxed);
238                     if (m_value != Value(ValueTraits::Redirect))
239                         break;
240                     TURF_TRACE(ConcurrentMap_Linear, 14, "[Mutator::eraseValue] was re-redirected", uptr(m_table),
241                                m_cell - m_table->getCells());
242                 }
243             }
244         }
245     };
246
247     Mutator insert(Key key) {
248         return Mutator(*this, key);
249     }
250
251     Mutator find(Key key) {
252         return Mutator(*this, key, false);
253     }
254
255     // Lookup without creating a temporary Mutator.
256     Value get(Key key) {
257         Hash hash = KeyTraits::hash(key);
258         TURF_TRACE(ConcurrentMap_Linear, 15, "[get] called", uptr(this), uptr(hash));
259         for (;;) {
260             typename Details::Table* table = m_root.load(turf::Consume);
261             typename Details::Cell* cell = Details::find(hash, table);
262             if (!cell)
263                 return Value(ValueTraits::NullValue);
264             Value value = cell->value.load(turf::Consume);
265             if (value != Value(ValueTraits::Redirect))
266                 return value; // Found an existing value
267             // We've been redirected to a new table. Help with the migration.
268             TURF_TRACE(ConcurrentMap_Linear, 16, "[get] was redirected", uptr(table), uptr(cell));
269             table->jobCoordinator.participate();
270             // Try again in the new table.
271         }
272     }
273
274     Value insert(Key key, Value desired) {
275         Mutator iter(*this, key);
276         return iter.exchangeValue(desired);
277     }
278
279     Value exchange(Key key, Value desired) {
280         Mutator iter(*this, key);
281         return iter.exchangeValue(desired);
282     }
283
284     Value erase(Key key) {
285         Mutator iter(*this, key, false);
286         return iter.eraseValue();
287     }
288
289     // The easiest way to implement an Iterator is to prevent all Redirects.
290     // The currrent Iterator does that by forbidding concurrent inserts.
291     // To make it work with concurrent inserts, we'd need a way to block TableMigrations.
292     class Iterator {
293     private:
294         typename Details::Table* m_table;
295         ureg m_idx;
296         Key m_hash;
297         Value m_value;
298
299     public:
300         Iterator(ConcurrentMap_Linear& map) {
301             // Since we've forbidden concurrent inserts (for now), nonatomic would suffice here, but let's plan ahead:
302             m_table = map.m_root.load(turf::Consume);
303             m_idx = -1;
304             next();
305         }
306
307         void next() {
308             TURF_ASSERT(m_table);
309             TURF_ASSERT(isValid() || m_idx == -1); // Either the Iterator is already valid, or we've just started iterating.
310             while (++m_idx <= m_table->sizeMask) {
311                 // Index still inside range of table.
312                 typename Details::Cell* cell = m_table->getCells() + m_idx;
313                 m_hash = cell->hash.load(turf::Relaxed);
314                 if (m_hash != KeyTraits::NullHash) {
315                     // Cell has been reserved.
316                     m_value = cell->value.load(turf::Relaxed);
317                     TURF_ASSERT(m_value != Value(ValueTraits::Redirect));
318                     if (m_value != Value(ValueTraits::NullValue))
319                         return; // Yield this cell.
320                 }
321             }
322             // That's the end of the map.
323             m_hash = KeyTraits::NullHash;
324             m_value = ValueTraits::NullValue;
325         }
326
327         bool isValid() const {
328             return m_value != Value(ValueTraits::NullValue);
329         }
330
331         Key getKey() const {
332             TURF_ASSERT(isValid());
333             // Since we've forbidden concurrent inserts (for now), nonatomic would suffice here, but let's plan ahead:
334             return KeyTraits::dehash(m_hash);
335         }
336
337         Value getValue() const {
338             TURF_ASSERT(isValid());
339             return m_value;
340         }
341     };
342 };
343
344 } // namespace junction
345
346 #endif // JUNCTION_CONCURRENTMAP_LINEAR_H