xenium
harris_michael_list_based_set.hpp
1 //
2 // Copyright (c) 2018-2020 Manuel Pöter.
3 // Licensed under the MIT License. See LICENSE file in the project root for full license information.
4 //
5 
6 #ifndef XENIUM_HARRIS_MICHAEL_LIST_BASED_SET_HPP
7 #define XENIUM_HARRIS_MICHAEL_LIST_BASED_SET_HPP
8 
9 #include <xenium/acquire_guard.hpp>
10 #include <xenium/backoff.hpp>
11 #include <xenium/parameter.hpp>
12 #include <xenium/policy.hpp>
13 
14 #include <cassert>
15 #include <functional>
16 
17 namespace xenium {
39 template <class Key, class... Policies>
41 public:
42  using value_type = Key;
43  using reclaimer = parameter::type_param_t<policy::reclaimer, parameter::nil, Policies...>;
44  using backoff = parameter::type_param_t<policy::backoff, no_backoff, Policies...>;
45  using compare = parameter::type_param_t<policy::compare, std::less<Key>, Policies...>;
46 
47  template <class... NewPolicies>
48  using with = harris_michael_list_based_set<Key, NewPolicies..., Policies...>;
49 
50  static_assert(parameter::is_set<reclaimer>::value, "reclaimer policy must be specified");
51 
54 
55  class iterator;
56 
71  template <class... Args>
72  bool emplace(Args&&... args);
73 
90  template <class... Args>
91  std::pair<iterator, bool> emplace_or_get(Args&&... args);
92 
103  bool erase(const Key& key);
104 
115  iterator erase(iterator pos);
116 
126  iterator find(const Key& key);
127 
136  bool contains(const Key& key);
137 
142  iterator begin();
143 
150  iterator end();
151 
152 private:
153  struct node;
154 
155  using concurrent_ptr = typename reclaimer::template concurrent_ptr<node, 1>;
156  using marked_ptr = typename concurrent_ptr::marked_ptr;
157  using guard_ptr = typename concurrent_ptr::guard_ptr;
158 
159  struct find_info {
160  concurrent_ptr* prev;
161  marked_ptr next{};
162  guard_ptr cur{};
163  guard_ptr save{};
164  };
165  bool find(const Key& key, find_info& info, backoff& backoff);
166 
167  concurrent_ptr head;
168 };
169 
183 template <class Key, class... Policies>
184 class harris_michael_list_based_set<Key, Policies...>::iterator {
185 public:
186  using iterator_category = std::forward_iterator_tag;
187  using value_type = Key;
188  using difference_type = std::ptrdiff_t;
189  using pointer = const Key*;
190  using reference = const Key&;
191 
192  iterator(iterator&&) = default;
193  iterator(const iterator&) = default;
194 
195  iterator& operator=(iterator&&) = default;
196  iterator& operator=(const iterator&) = default;
197 
206  iterator& operator++();
207  iterator operator++(int);
208 
209  bool operator==(const iterator& other) const { return info.cur.get() == other.info.cur.get(); }
210  bool operator!=(const iterator& other) const { return !(*this == other); }
211  reference operator*() const noexcept { return info.cur->key; }
212  pointer operator->() const noexcept { return &info.cur->key; }
213 
220  void reset() {
221  info.cur.reset();
222  info.save.reset();
223  }
224 
225 private:
227 
228  explicit iterator(harris_michael_list_based_set& list, concurrent_ptr* start) : list(&list) {
229  info.prev = start;
230  if (start) {
231  // (2) - this acquire-load synchronizes-with the release-CAS (7, 8, 10, 13)
232  info.cur.acquire(*start, std::memory_order_acquire);
233  }
234  }
235 
236  explicit iterator(harris_michael_list_based_set& list, find_info&& info) : list(&list), info(std::move(info)) {}
237 
238  harris_michael_list_based_set* list;
239  find_info info;
240 };
241 
242 template <class Key, class... Policies>
243 struct harris_michael_list_based_set<Key, Policies...>::node : reclaimer::template enable_concurrent_ptr<node, 1> {
244  const Key key;
245  concurrent_ptr next;
246  template <class... Args>
247  explicit node(Args&&... args) : key(std::forward<Args>(args)...), next() {}
248 };
249 
250 template <class Key, class... Policies>
252  assert(info.cur.get() != nullptr);
253  auto next = info.cur->next.load(std::memory_order_relaxed);
254  guard_ptr tmp_guard;
255  // (1) - this acquire-load synchronizes-with the release-CAS (7, 8, 10, 13)
256  if (next.mark() == 0 && tmp_guard.acquire_if_equal(info.cur->next, next, std::memory_order_acquire)) {
257  info.prev = &info.cur->next;
258  info.save = std::move(info.cur);
259  info.cur = std::move(tmp_guard);
260  } else {
261  // cur is marked for removal
262  // -> use find to remove it and get to the next node with a compare(key, cur->key) == false
263  auto key = info.cur->key;
264  backoff backoff;
265  list->find(key, info, backoff);
266  }
267  assert(info.prev == &list->head || info.cur.get() == nullptr ||
268  (info.save.get() != nullptr && &info.save->next == info.prev));
269  return *this;
270 }
271 
272 template <class Key, class... Policies>
274  iterator retval = *this;
275  ++(*this);
276  return retval;
277 }
278 
279 template <class Key, class... Policies>
281  // delete all remaining nodes
282  // (3) - this acquire-load synchronizes-with the release-CAS (7, 8, 10, 13)
283  auto p = head.load(std::memory_order_acquire);
284  while (p) {
285  // (4) - this acquire-load synchronizes-with the release-CAS (7, 8, 10, 13)
286  auto next = p->next.load(std::memory_order_acquire);
287  delete p.get();
288  p = next;
289  }
290 }
291 
292 template <class Key, class... Policies>
293 bool harris_michael_list_based_set<Key, Policies...>::find(const Key& key, find_info& info, backoff& backoff) {
294  assert((info.save == nullptr && info.prev == &head) || &info.save->next == info.prev);
295  concurrent_ptr* start = info.prev;
296  guard_ptr start_guard = info.save; // we have to keep a guard_ptr to prevent start's node from getting reclaimed.
297 retry:
298  info.prev = start;
299  info.save = start_guard;
300  info.next = info.prev->load(std::memory_order_relaxed);
301  if (info.next.mark() != 0) {
302  // our start node is marked for removal -> we have to restart from head
303  start = &head;
304  start_guard.reset();
305  goto retry;
306  }
307 
308  for (;;) {
309  // (5) - this acquire-load synchronizes-with the release-CAS (7, 8, 10, 13)
310  if (!info.cur.acquire_if_equal(*info.prev, info.next, std::memory_order_acquire)) {
311  goto retry;
312  }
313 
314  if (!info.cur) {
315  return false;
316  }
317 
318  info.next = info.cur->next.load(std::memory_order_relaxed);
319  if (info.next.mark() != 0) {
320  // Node *cur is marked for deletion -> update the link and retire the element
321 
322  // (6) - this acquire-load synchronizes-with the release-CAS (7, 8, 10, 13)
323  info.next = info.cur->next.load(std::memory_order_acquire).get();
324 
325  // Try to splice out node
326  marked_ptr expected = info.cur.get();
327  // (7) - this release-CAS synchronizes with the acquire-load (1, 2, 3, 4, 5, 6, 11)
328  // and the require-CAS (9, 12)
329  // it is the head of a potential release sequence containing (9, 12)
330  if (!info.prev->compare_exchange_weak(
331  expected, info.next, std::memory_order_release, std::memory_order_relaxed)) {
332  backoff();
333  goto retry;
334  }
335  info.cur.reclaim();
336  } else {
337  if (info.prev->load(std::memory_order_relaxed) != info.cur.get()) {
338  goto retry; // cur might be cut from list.
339  }
340 
341  const Key& ckey = info.cur->key;
342  compare compare;
343  if (!compare(ckey, key)) {
344  return !compare(key, ckey);
345  }
346 
347  info.prev = &info.cur->next;
348  std::swap(info.save, info.cur);
349  }
350  }
351 }
352 
353 template <class Key, class... Policies>
355  find_info info{&head};
356  backoff backoff;
357  return find(key, info, backoff);
358 }
359 
360 template <class Key, class... Policies>
362  find_info info{&head};
363  backoff backoff;
364  if (find(key, info, backoff)) {
365  return iterator(*this, std::move(info));
366  }
367  return end();
368 }
369 
370 template <class Key, class... Policies>
371 template <class... Args>
373  auto result = emplace_or_get(std::forward<Args>(args)...);
374  return result.second;
375 }
376 
377 template <class Key, class... Policies>
378 template <class... Args>
379 auto harris_michael_list_based_set<Key, Policies...>::emplace_or_get(Args&&... args) -> std::pair<iterator, bool> {
380  node* n = new node(std::forward<Args>(args)...);
381  find_info info{&head};
382  backoff backoff;
383  for (;;) {
384  if (find(n->key, info, backoff)) {
385  delete n;
386  return {iterator(*this, std::move(info)), false};
387  }
388 
389  // Try to install new node
390  marked_ptr expected = info.cur.get();
391  n->next.store(expected, std::memory_order_relaxed);
392  guard_ptr new_guard(n);
393 
394  // (8) - this release-CAS synchronizes with the acquire-load (1, 2, 3, 4, 5, 6, 11)
395  // and the acquire-CAS (9, 12)
396  // it is the head of a potential release sequence containing (9, 12)
397  if (info.prev->compare_exchange_weak(expected, n, std::memory_order_release, std::memory_order_relaxed)) {
398  info.cur = std::move(new_guard);
399  return {iterator(*this, std::move(info)), true};
400  }
401 
402  backoff();
403  }
404 }
405 
406 template <class Key, class... Policies>
408  backoff backoff;
409  find_info info{&head};
410  // Find node in list with matching key and mark it for reclamation.
411  for (;;) {
412  if (!find(key, info, backoff)) {
413  return false; // No such node in the list
414  }
415 
416  // (9) - this acquire-CAS synchronizes with the release-CAS (7, 8, 10, 13)
417  // and is part of a release sequence headed by those operations
418  if (info.cur->next.compare_exchange_weak(
419  info.next, marked_ptr(info.next.get(), 1), std::memory_order_acquire, std::memory_order_relaxed)) {
420  break;
421  }
422 
423  backoff();
424  }
425 
426  assert(info.next.mark() == 0);
427  assert(info.cur.mark() == 0);
428 
429  // Try to splice out node
430  marked_ptr expected = info.cur;
431  // (10) - this release-CAS synchronizes with the acquire-load (1, 2, 3, 4, 5, 6, 11)
432  // and the acquire-CAS (9, 12)
433  // it is the head of a potential release sequence containing (9, 12)
434  if (info.prev->compare_exchange_weak(expected, info.next, std::memory_order_release, std::memory_order_relaxed)) {
435  info.cur.reclaim();
436  } else {
437  // Another thread interfered -> rewalk the list to ensure reclamation of marked node before returning.
438  find(key, info, backoff);
439  }
440 
441  return true;
442 }
443 
444 template <class Key, class... Policies>
446  backoff backoff;
447  // (11) - this acquire-load synchronizes-with the release-CAS (7, 8, 10, 13)
448  auto next = pos.info.cur->next.load(std::memory_order_acquire);
449  while (next.mark() == 0) {
450  // (12) - this acquire-CAS synchronizes-with the release-CAS (7, 8, 10, 13)
451  // and is part of a release sequence headed by those operations
452  if (pos.info.cur->next.compare_exchange_weak(next, marked_ptr(next.get(), 1), std::memory_order_acquire)) {
453  break;
454  }
455 
456  backoff();
457  }
458 
459  guard_ptr next_guard(next.get());
460  assert(pos.info.cur.mark() == 0);
461 
462  // Try to splice out node
463  marked_ptr expected = pos.info.cur;
464  // (13) - this release-CAS synchronizes with the acquire-load (1, 2, 3, 4, 5, 6, 11)
465  // and the acquire-CAS (9, 12)
466  // it is the head of a potential release sequence containing (9, 12)
467  if (pos.info.prev->compare_exchange_weak(
468  expected, next_guard, std::memory_order_release, std::memory_order_relaxed)) {
469  pos.info.cur.reclaim();
470  pos.info.cur = std::move(next_guard);
471  } else {
472  next_guard.reset();
473  Key key = pos.info.cur->key;
474 
475  // Another thread interfered -> rewalk the list to ensure reclamation of marked node before returning.
476  find(key, pos.info, backoff);
477  }
478 
479  return pos;
480 }
481 
482 template <class Key, class... Policies>
484  return iterator(*this, &head);
485 }
486 
487 template <class Key, class... Policies>
489  return iterator(*this, nullptr);
490 }
491 } // namespace xenium
492 
493 #endif
xenium::policy::backoff
Policy to configure the backoff strategy.
Definition: policy.hpp:39
xenium::harris_michael_list_based_set::find
iterator find(const Key &key)
Finds an element with key equivalent to key.
Definition: harris_michael_list_based_set.hpp:361
xenium::harris_michael_list_based_set::emplace_or_get
std::pair< iterator, bool > emplace_or_get(Args &&... args)
Inserts a new element into the container if the container doesn't already contain an element with an ...
xenium::harris_michael_list_based_set::erase
bool erase(const Key &key)
Removes the element with the key equivalent to key (if one exists).
Definition: harris_michael_list_based_set.hpp:407
xenium::harris_michael_list_based_set::contains
bool contains(const Key &key)
Checks if there is an element with key equivalent to key in the container.
Definition: harris_michael_list_based_set.hpp:354
xenium::harris_michael_list_based_set::iterator::reset
void reset()
Resets the iterator; this is equivalent to assigning end() to it.
Definition: harris_michael_list_based_set.hpp:220
xenium::harris_michael_list_based_set::emplace
bool emplace(Args &&... args)
Inserts a new element into the container if the container doesn't already contain an element with an ...
Definition: harris_michael_list_based_set.hpp:372
xenium::harris_michael_list_based_set::end
iterator end()
Returns an iterator to the element following the last element of the container.
Definition: harris_michael_list_based_set.hpp:488
xenium::harris_michael_list_based_set
A lock-free container that contains a sorted set of unique objects of type Key.
Definition: harris_michael_list_based_set.hpp:40
xenium::policy::reclaimer
Policy to configure the reclamation scheme to be used.
Definition: policy.hpp:25
xenium::no_backoff
Dummy backoff strategy that does nothing.
Definition: backoff.hpp:16
xenium::harris_michael_list_based_set::iterator
A ForwardIterator to safely iterate the list.
Definition: harris_michael_list_based_set.hpp:184
xenium::harris_michael_list_based_set::iterator::operator++
iterator & operator++()
Moves the iterator to the next element. In the absence of conflicting operations, this operation has ...
Definition: harris_michael_list_based_set.hpp:251
xenium::harris_michael_list_based_set::begin
iterator begin()
Returns an iterator to the first element of the container.
Definition: harris_michael_list_based_set.hpp:483