PMDK C++ bindings  1.13.0-git107.g7e59f08f
This is the C++ bindings documentation for PMDK's libpmemobj.
concurrent_skip_list_impl.hpp
Go to the documentation of this file.
1 // SPDX-License-Identifier: BSD-3-Clause
2 /* Copyright 2020-2021, Intel Corporation */
3 
9 #ifndef PMEMOBJ_CONCURRENT_SKIP_LIST_IMPL_HPP
10 #define PMEMOBJ_CONCURRENT_SKIP_LIST_IMPL_HPP
11 
12 #include <algorithm>
13 #include <array>
14 #include <atomic>
15 #include <cstdlib>
16 #include <limits>
17 #include <mutex> /* for std::unique_lock */
18 #include <random>
19 #include <type_traits>
20 
26 #include <libpmemobj++/mutex.hpp>
28 #include <libpmemobj++/pool.hpp>
30 
33 
34 /* Windows has a max and a min macros which collides with min() and max()
35  * methods of default_random_generator */
36 #if defined(_WIN32)
37 #if defined(max)
38 #undef max
39 #endif
40 #if defined(min)
41 #undef min
42 #endif
43 #endif
44 
45 namespace pmem
46 {
47 namespace detail
48 {
49 
50 #ifndef NDEBUG
51 inline void
52 try_insert_node_finish_marker()
53 {
54 }
55 #endif
56 
61 template <typename MyAlloc, typename OtherAlloc>
62 inline void
63 allocator_copy_assignment(MyAlloc &my_allocator, OtherAlloc &other_allocator,
64  std::true_type)
65 {
66  my_allocator = other_allocator;
67 }
68 
73 template <typename MyAlloc, typename OtherAlloc>
74 inline void
75 allocator_copy_assignment(MyAlloc &, OtherAlloc &, std::false_type)
76 { /* NO COPY */
77 }
78 
83 template <typename MyAlloc, typename OtherAlloc>
84 inline void
85 allocator_move_assignment(MyAlloc &my_allocator, OtherAlloc &other_allocator,
86  std::true_type)
87 {
88  my_allocator = std::move(other_allocator);
89 }
90 
95 template <typename MyAlloc, typename OtherAlloc>
96 inline void
97 allocator_move_assignment(MyAlloc &, OtherAlloc &, std::false_type)
98 { /* NO MOVE */
99 }
100 
105 template <typename MyAlloc, typename OtherAlloc>
106 inline void
107 allocator_swap(MyAlloc &my_allocator, OtherAlloc &other_allocator,
108  std::true_type)
109 {
110  std::swap(my_allocator, other_allocator);
111 }
112 
117 template <typename MyAlloc, typename OtherAlloc>
118 inline void
119 allocator_swap(MyAlloc &, OtherAlloc &, std::false_type)
120 { /* NO SWAP */
121 }
122 
123 template <typename Value, typename Mutex = pmem::obj::mutex,
124  typename LockType = std::unique_lock<Mutex>>
125 class skip_list_node {
126 public:
127  using value_type = Value;
128  using size_type = std::size_t;
129  using reference = value_type &;
130  using const_reference = const value_type &;
131  using pointer = value_type *;
132  using const_pointer = const value_type *;
133  using node_pointer =
135  using atomic_node_pointer = std::atomic<node_pointer>;
136  using mutex_type = Mutex;
137  using lock_type = LockType;
138 
139  skip_list_node(size_type levels) : height_(levels)
140  {
141  for (size_type lev = 0; lev < height_; ++lev)
142  detail::create<atomic_node_pointer>(&get_next(lev),
143  nullptr);
144 
145  assert(height() == levels);
146 #if LIBPMEMOBJ_CPP_VG_HELGRIND_ENABLED
147  /*
148  * Valgrind does not understand atomic semantic and reports
149  * false-postives in drd and helgrind tools.
150  */
151  for (size_type lev = 0; lev < height_; ++lev) {
152  VALGRIND_HG_DISABLE_CHECKING(&get_next(lev),
153  sizeof(get_next(lev)));
154  }
155 #endif
156  }
157 
158  skip_list_node(size_type levels, const node_pointer *new_nexts)
159  : height_(levels)
160  {
161  for (size_type lev = 0; lev < height_; ++lev)
162  detail::create<atomic_node_pointer>(&get_next(lev),
163  new_nexts[lev]);
164 
165  assert(height() == levels);
166 #if LIBPMEMOBJ_CPP_VG_HELGRIND_ENABLED
167  /*
168  * Valgrind does not understand atomic semantic and reports
169  * false-postives in drd and helgrind tools.
170  */
171  for (size_type lev = 0; lev < height_; ++lev) {
172  VALGRIND_HG_DISABLE_CHECKING(&get_next(lev),
173  sizeof(get_next(lev)));
174  }
175 #endif
176  }
177 
178  ~skip_list_node()
179  {
180  for (size_type lev = 0; lev < height_; ++lev)
181  detail::destroy<atomic_node_pointer>(get_next(lev));
182  }
183 
184  skip_list_node(const skip_list_node &) = delete;
185 
186  skip_list_node &operator=(const skip_list_node &) = delete;
187 
188  pointer
189  get() noexcept
190  {
191  return &val;
192  }
193 
194  const_pointer
195  get() const noexcept
196  {
197  return &val;
198  }
199 
200  reference
201  value()
202  {
203  return *get();
204  }
205 
206  node_pointer
207  next(size_type level) const
208  {
209  assert(level < height());
210  return get_next(level).load(std::memory_order_acquire);
211  }
212 
217  void
218  set_next_tx(size_type level, node_pointer next)
219  {
220  assert(level < height());
221  assert(pmemobj_tx_stage() == TX_STAGE_WORK);
222  auto &node = get_next(level);
223  obj::flat_transaction::snapshot<atomic_node_pointer>(&node);
224  node.store(next, std::memory_order_release);
225  }
226 
227  void
228  set_next(obj::pool_base pop, size_type level, node_pointer next)
229  {
230  assert(level < height());
231  auto &node = get_next(level);
232  node.store(next, std::memory_order_release);
233  pop.persist(&node, sizeof(node));
234  }
235 
236  void
237  set_nexts(const node_pointer *new_nexts, size_type h)
238  {
239  assert(h == height());
240  auto *nexts = get_nexts();
241 
242  for (size_type i = 0; i < h; i++) {
243  nexts[i].store(new_nexts[i], std::memory_order_relaxed);
244  }
245  }
246 
247  void
248  set_nexts(obj::pool_base pop, const node_pointer *new_nexts,
249  size_type h)
250  {
251  set_nexts(new_nexts, h);
252 
253  auto *nexts = get_nexts();
254  pop.persist(nexts, sizeof(nexts[0]) * h);
255  }
256 
258  size_type
259  height() const
260  {
261  return height_;
262  }
263 
264  lock_type
265  acquire()
266  {
267  return lock_type(mutex);
268  }
269 
270 private:
271  atomic_node_pointer *
272  get_nexts()
273  {
274  return reinterpret_cast<atomic_node_pointer *>(this + 1);
275  }
276 
277  atomic_node_pointer &
278  get_next(size_type level)
279  {
280  auto *arr = get_nexts();
281  return arr[level];
282  }
283 
284  const atomic_node_pointer &
285  get_next(size_type level) const
286  {
287  auto *arr =
288  reinterpret_cast<const atomic_node_pointer *>(this + 1);
289  return arr[level];
290  }
291 
292  mutex_type mutex;
293  union {
294  value_type val;
295  };
296  size_type height_;
297 };
298 
299 template <typename NodeType, bool is_const>
300 class skip_list_iterator {
301  using node_type = NodeType;
302  using node_ptr = typename std::conditional<is_const, const node_type *,
303  node_type *>::type;
304  friend class skip_list_iterator<node_type, true>;
305 
306 public:
307  using value_type = typename node_type::value_type;
308  using iterator_category = std::forward_iterator_tag;
309  using difference_type = std::ptrdiff_t;
310  using reference =
311  typename std::conditional<is_const,
312  typename node_type::const_reference,
313  typename node_type::reference>::type;
314  using pointer = typename std::conditional<is_const, const value_type *,
315  value_type *>::type;
316 
317  skip_list_iterator() : node(nullptr)
318  {
319  }
320 
322  skip_list_iterator(const skip_list_iterator &other) : node(other.node)
323  {
324  }
325 
327  template <typename U = void,
328  typename = typename std::enable_if<is_const, U>::type>
329  skip_list_iterator(const skip_list_iterator<node_type, false> &other)
330  : node(other.node)
331  {
332  }
333 
334  reference operator*() const
335  {
336  return *(node->get());
337  }
338 
339  pointer operator->() const
340  {
341  return node->get();
342  }
343 
344  skip_list_iterator &
345  operator++()
346  {
347  assert(node != nullptr);
348  node = node->next(0).get();
349  return *this;
350  }
351 
352  skip_list_iterator
353  operator++(int)
354  {
355  skip_list_iterator tmp = *this;
356  ++*this;
357  return tmp;
358  }
359 
360  skip_list_iterator &
361  operator=(const skip_list_iterator &other)
362  {
363  node = other.node;
364  return *this;
365  }
366 
367 private:
368  explicit skip_list_iterator(node_type *n) : node(n)
369  {
370  }
371 
372  template <typename T = void,
373  typename = typename std::enable_if<is_const, T>::type>
374  explicit skip_list_iterator(const node_type *n) : node(n)
375  {
376  }
377 
378  node_ptr node;
379 
380  template <typename Traits>
381  friend class concurrent_skip_list;
382 
383  template <typename T, bool M, bool U>
384  friend bool operator==(const skip_list_iterator<T, M> &lhs,
385  const skip_list_iterator<T, U> &rhs);
386 
387  template <typename T, bool M, bool U>
388  friend bool operator!=(const skip_list_iterator<T, M> &lhs,
389  const skip_list_iterator<T, U> &rhs);
390 };
391 
392 template <typename T, bool M, bool U>
393 bool
394 operator==(const skip_list_iterator<T, M> &lhs,
395  const skip_list_iterator<T, U> &rhs)
396 {
397  return lhs.node == rhs.node;
398 }
399 
400 template <typename T, bool M, bool U>
401 bool
402 operator!=(const skip_list_iterator<T, M> &lhs,
403  const skip_list_iterator<T, U> &rhs)
404 {
405  return lhs.node != rhs.node;
406 }
407 
408 struct default_random_generator {
409  using gen_type = std::mt19937_64;
410  using result_type = typename gen_type::result_type;
411 
412  size_t
413  operator()()
414  {
415  static thread_local gen_type engine(
416  static_cast<size_t>(time(0)));
417 
418  return engine();
419  }
420 
421  static constexpr result_type
422  min()
423  {
424  return gen_type::min();
425  }
426 
427  static constexpr result_type
428  max()
429  {
430  return gen_type::max();
431  }
432 };
433 
434 template <typename RndGenerator, size_t MAX_LEVEL>
435 class geometric_level_generator {
436 public:
437  using rnd_generator_type = RndGenerator;
438 
439  static constexpr size_t max_level = MAX_LEVEL;
440 
441  size_t
442  operator()()
443  {
444  /* rnd_generator_type should be thread-safe random number
445  * generator. */
446  static rnd_generator_type gen;
447  /* std::geometric_distribution is not thread-safe. We mark it as
448  * a thread_local to avoid data races. */
449  static thread_local std::geometric_distribution<size_t> d;
450 
451  return (d(gen) % MAX_LEVEL) + 1;
452  }
453 };
454 
483 template <typename Traits>
485 protected:
486  using traits_type = Traits;
487  using key_type = typename traits_type::key_type;
488  using mapped_type = typename traits_type::mapped_type;
489  using value_type = typename traits_type::value_type;
490  using size_type = std::size_t;
491  using difference_type = std::ptrdiff_t;
492  using key_compare = typename traits_type::compare_type;
493  using allocator_type = typename traits_type::allocator_type;
494  using allocator_traits_type = std::allocator_traits<allocator_type>;
495 
496  using reference = value_type &;
497  using const_reference = const value_type &;
498  using pointer = typename allocator_traits_type::pointer;
499  using const_pointer = typename allocator_traits_type::const_pointer;
500 
501  using list_node_type = skip_list_node<value_type>;
502 
503  using iterator = skip_list_iterator<list_node_type, false>;
504  using const_iterator = skip_list_iterator<list_node_type, true>;
505 
506  static constexpr size_type MAX_LEVEL = traits_type::max_level;
507 
508  using random_level_generator_type = geometric_level_generator<
509  typename traits_type::random_generator_type, MAX_LEVEL>;
510  using node_allocator_type = typename std::allocator_traits<
511  allocator_type>::template rebind_alloc<uint8_t>;
512  using node_allocator_traits = typename std::allocator_traits<
513  allocator_type>::template rebind_traits<uint8_t>;
514  using node_ptr = list_node_type *;
515  using const_node_ptr = const list_node_type *;
516  using persistent_node_ptr =
518 
519  using prev_array_type = std::array<node_ptr, MAX_LEVEL>;
520  using next_array_type = std::array<persistent_node_ptr, MAX_LEVEL>;
521  using node_lock_type = typename list_node_type::lock_type;
522  using lock_array = std::array<node_lock_type, MAX_LEVEL>;
523 
524 public:
525  static constexpr bool allow_multimapping =
526  traits_type::allow_multimapping;
527 
537  {
538  check_tx_stage_work();
539  init();
540  }
541 
559  const key_compare &comp,
560  const allocator_type &alloc = allocator_type())
561  : _node_allocator(alloc), _compare(comp)
562  {
563  check_tx_stage_work();
564  init();
565  }
566 
590  template <class InputIt>
591  concurrent_skip_list(InputIt first, InputIt last,
592  const key_compare &comp = key_compare(),
593  const allocator_type &alloc = allocator_type())
594  : _node_allocator(alloc), _compare(comp)
595  {
596  check_tx_stage_work();
597  init();
598  while (first != last)
599  internal_unsafe_emplace(*first++);
600  }
601 
620  : _node_allocator(node_allocator_traits::
621  select_on_container_copy_construction(
622  other._node_allocator)),
623  _compare(other._compare),
624  _rnd_generator(other._rnd_generator)
625  {
626  check_tx_stage_work();
627  init();
628  internal_copy(other);
629  assert(_size == other._size);
630  }
631 
652  const allocator_type &alloc)
653  : _node_allocator(alloc),
654  _compare(other._compare),
655  _rnd_generator(other._rnd_generator)
656  {
657  check_tx_stage_work();
658  init();
659  internal_copy(other);
660  assert(_size == other._size);
661  }
662 
682  : _node_allocator(std::move(other._node_allocator)),
683  _compare(other._compare),
684  _rnd_generator(other._rnd_generator)
685  {
686  check_tx_stage_work();
687  init();
688  internal_move(std::move(other));
689  }
690 
711  const allocator_type &alloc)
712  : _node_allocator(alloc),
713  _compare(other._compare),
714  _rnd_generator(other._rnd_generator)
715  {
716  check_tx_stage_work();
717  init();
718  if (alloc == other.get_allocator()) {
719  internal_move(std::move(other));
720  } else {
721  init();
722  internal_copy(std::make_move_iterator(other.begin()),
723  std::make_move_iterator(other.end()));
724  }
725  }
726 
733  void
735  {
736  tls_restore();
737 
738  assert(this->size() ==
739  size_type(std::distance(this->begin(), this->end())));
740  }
741 
754  void
756  {
757  if (dummy_head == nullptr)
758  return;
759 
760  auto pop = get_pool_base();
761  obj::flat_transaction::run(pop, [&] {
762  clear();
763  delete_dummy_head();
764  });
765  }
766 
777  {
778  try {
779  free_data();
780  } catch (...) {
781  std::terminate();
782  }
783  }
784 
802  {
803  if (this == &other)
804  return *this;
805 
806  obj::pool_base pop = get_pool_base();
807  obj::flat_transaction::run(pop, [&] {
808  using pocca_t = typename node_allocator_traits::
809  propagate_on_container_copy_assignment;
810  clear();
811  allocator_copy_assignment(_node_allocator,
812  other._node_allocator,
813  pocca_t());
814  _compare = other._compare;
815  _rnd_generator = other._rnd_generator;
816 
817  internal_copy(other);
818  });
819  return *this;
820  }
821 
844  {
845  if (this == &other)
846  return *this;
847 
848  obj::pool_base pop = get_pool_base();
849  obj::flat_transaction::run(pop, [&] {
850  using pocma_t = typename node_allocator_traits::
851  propagate_on_container_move_assignment;
852  clear();
853  if (pocma_t::value ||
854  _node_allocator == other._node_allocator) {
855  delete_dummy_head();
856  allocator_move_assignment(_node_allocator,
857  other._node_allocator,
858  pocma_t());
859  _compare = other._compare;
860  _rnd_generator = other._rnd_generator;
861  internal_move(std::move(other));
862  } else {
863  internal_copy(
864  std::make_move_iterator(other.begin()),
865  std::make_move_iterator(other.end()));
866  }
867  });
868  return *this;
869  }
870 
883  operator=(std::initializer_list<value_type> il)
884  {
885  obj::pool_base pop = get_pool_base();
886  obj::flat_transaction::run(pop, [&] {
887  clear();
888  for (auto it = il.begin(); it != il.end(); ++it)
889  internal_unsafe_emplace(*it);
890  });
891  return *this;
892  }
893 
910  std::pair<iterator, bool>
911  insert(const value_type &value)
912  {
913  return internal_insert(value.first, value);
914  }
915 
934  template <typename P,
935  typename std::enable_if<
936  std::is_constructible<value_type, P &&>::value>::type>
937  std::pair<iterator, bool>
938  insert(P &&value)
939  {
940  return emplace(std::forward<P>(value));
941  }
942 
959  std::pair<iterator, bool>
960  insert(value_type &&value)
961  {
962  return internal_insert(value.first, std::move(value));
963  }
964 
982  iterator
983  insert(const_iterator hint, const_reference value)
984  {
985  /* Ignore hint */
986  return insert(value).first;
987  }
988 
1009  template <typename P,
1010  typename std::enable_if<
1011  std::is_constructible<value_type, P &&>::value>::type>
1012  iterator
1013  insert(const_iterator hint, P &&value)
1014  {
1015  return emplace_hint(hint, std::forward<P>(value));
1016  }
1017 
1032  template <typename InputIterator>
1033  void
1034  insert(InputIterator first, InputIterator last)
1035  {
1036  for (InputIterator it = first; it != last; ++it)
1037  insert(*it);
1038  }
1039 
1053  void
1054  insert(std::initializer_list<value_type> ilist)
1055  {
1056  insert(ilist.begin(), ilist.end());
1057  }
1058 
1086  template <typename... Args>
1087  std::pair<iterator, bool>
1088  emplace(Args &&... args)
1089  {
1090  return internal_emplace(std::forward<Args>(args)...);
1091  }
1092 
1123  template <typename... Args>
1124  iterator
1125  emplace_hint(const_iterator hint, Args &&... args)
1126  {
1127  /* Ignore hint */
1128  return emplace(std::forward<Args>(args)...).first;
1129  }
1130 
1154  template <typename... Args>
1155  std::pair<iterator, bool>
1156  try_emplace(const key_type &k, Args &&... args)
1157  {
1158  return internal_try_emplace(k, std::forward<Args>(args)...);
1159  }
1160 
1184  template <typename... Args>
1185  std::pair<iterator, bool>
1186  try_emplace(key_type &&k, Args &&... args)
1187  {
1188  return internal_try_emplace(std::move(k),
1189  std::forward<Args>(args)...);
1190  }
1191 
1218  template <typename K, typename... Args>
1219  typename std::enable_if<
1220  has_is_transparent<key_compare>::value &&
1221  std::is_constructible<key_type, K &&>::value,
1222  std::pair<iterator, bool>>::type
1223  try_emplace(K &&k, Args &&... args)
1224  {
1225  return internal_try_emplace(std::forward<K>(k),
1226  std::forward<Args>(args)...);
1227  }
1228 
1245  iterator
1246  unsafe_erase(iterator pos)
1247  {
1248  check_outside_tx();
1249  auto &size_diff = tls_data.local().size_diff;
1250  return internal_erase(pos, size_diff);
1251  }
1252 
1269  iterator
1270  unsafe_erase(const_iterator pos)
1271  {
1272  return unsafe_erase(get_iterator(pos));
1273  }
1274 
1289  iterator
1290  unsafe_erase(const_iterator first, const_iterator last)
1291  {
1292  check_outside_tx();
1293  obj::pool_base pop = get_pool_base();
1294  auto &size_diff = tls_data.local().size_diff;
1295 
1296  obj::flat_transaction::run(pop, [&] {
1297  while (first != last) {
1298  first = internal_erase(first, size_diff);
1299  }
1300  });
1301 
1302  return get_iterator(first);
1303  }
1304 
1317  size_type
1318  unsafe_erase(const key_type &key)
1319  {
1320  std::pair<iterator, iterator> range = equal_range(key);
1321  size_type sz = static_cast<size_type>(
1322  std::distance(range.first, range.second));
1323  unsafe_erase(range.first, range.second);
1324  return sz;
1325  }
1326 
1345  template <
1346  typename K,
1347  typename = typename std::enable_if<
1348  has_is_transparent<key_compare>::value &&
1349  !std::is_convertible<K, iterator>::value &&
1350  !std::is_convertible<K, const_iterator>::value,
1351  K>::type>
1352  size_type
1353  unsafe_erase(const K &key)
1354  {
1355  std::pair<iterator, iterator> range = equal_range(key);
1356  size_type sz = static_cast<size_type>(
1357  std::distance(range.first, range.second));
1358  unsafe_erase(range.first, range.second);
1359  return sz;
1360  }
1361 
1372  iterator
1373  lower_bound(const key_type &key)
1374  {
1375  return internal_get_bound(key, _compare);
1376  }
1377 
1388  const_iterator
1389  lower_bound(const key_type &key) const
1390  {
1391  return internal_get_bound(key, _compare);
1392  }
1393 
1407  template <typename K,
1408  typename = typename std::enable_if<
1409  has_is_transparent<key_compare>::value, K>::type>
1410  iterator
1411  lower_bound(const K &x)
1412  {
1413  return internal_get_bound(x, _compare);
1414  }
1415 
1429  template <typename K,
1430  typename = typename std::enable_if<
1431  has_is_transparent<key_compare>::value, K>::type>
1432  const_iterator
1433  lower_bound(const K &x) const
1434  {
1435  return internal_get_bound(x, _compare);
1436  }
1437 
1448  iterator
1449  find_higher_eq(const key_type &key)
1450  {
1451  return internal_get_bound(key, _compare);
1452  }
1453 
1464  const_iterator
1465  find_higher_eq(const key_type &key) const
1466  {
1467  return internal_get_bound(key, _compare);
1468  }
1469 
1484  template <typename K,
1485  typename = typename std::enable_if<
1486  has_is_transparent<key_compare>::value, K>::type>
1487  iterator
1488  find_higher_eq(const K &x)
1489  {
1490  return internal_get_bound(x, _compare);
1491  }
1492 
1507  template <typename K,
1508  typename = typename std::enable_if<
1509  has_is_transparent<key_compare>::value, K>::type>
1510  const_iterator
1511  find_higher_eq(const K &x) const
1512  {
1513  return internal_get_bound(x, _compare);
1514  }
1515 
1526  iterator
1527  upper_bound(const key_type &key)
1528  {
1529  return internal_get_bound(key, not_greater_compare(_compare));
1530  }
1531 
1542  const_iterator
1543  upper_bound(const key_type &key) const
1544  {
1545  return internal_get_bound(key, not_greater_compare(_compare));
1546  }
1547 
1561  template <typename K,
1562  typename = typename std::enable_if<
1563  has_is_transparent<key_compare>::value, K>::type>
1564  iterator
1565  upper_bound(const K &x)
1566  {
1567  return internal_get_bound(x, not_greater_compare(_compare));
1568  }
1569 
1583  template <typename K,
1584  typename = typename std::enable_if<
1585  has_is_transparent<key_compare>::value, K>::type>
1586  const_iterator
1587  upper_bound(const K &x) const
1588  {
1589  return internal_get_bound(x, not_greater_compare(_compare));
1590  }
1591 
1602  iterator
1603  find_higher(const key_type &key)
1604  {
1605  return internal_get_bound(key, not_greater_compare(_compare));
1606  }
1607 
1618  const_iterator
1619  find_higher(const key_type &key) const
1620  {
1621  return internal_get_bound(key, not_greater_compare(_compare));
1622  }
1623 
1637  template <typename K,
1638  typename = typename std::enable_if<
1639  has_is_transparent<key_compare>::value, K>::type>
1640  iterator
1641  find_higher(const K &x)
1642  {
1643  return internal_get_bound(x, not_greater_compare(_compare));
1644  }
1645 
1659  template <typename K,
1660  typename = typename std::enable_if<
1661  has_is_transparent<key_compare>::value, K>::type>
1662  const_iterator
1663  find_higher(const K &x) const
1664  {
1665  return internal_get_bound(x, not_greater_compare(_compare));
1666  }
1667 
1678  iterator
1679  find_lower(const key_type &key)
1680  {
1681  auto it = internal_get_biggest_less_than(key, _compare);
1682  return iterator(
1683  const_cast<typename iterator::node_ptr>(it.node));
1684  }
1685 
1696  const_iterator
1697  find_lower(const key_type &key) const
1698  {
1699  return internal_get_biggest_less_than(key, _compare);
1700  }
1701 
1715  template <typename K,
1716  typename = typename std::enable_if<
1717  has_is_transparent<key_compare>::value, K>::type>
1718  iterator
1719  find_lower(const K &key)
1720  {
1721  auto it = internal_get_biggest_less_than(key, _compare);
1722  return iterator(
1723  const_cast<typename iterator::node_ptr>(it.node));
1724  }
1725 
1739  template <typename K,
1740  typename = typename std::enable_if<
1741  has_is_transparent<key_compare>::value, K>::type>
1742  const_iterator
1743  find_lower(const K &key) const
1744  {
1745  return internal_get_biggest_less_than(key, _compare);
1746  }
1747 
1758  iterator
1759  find_lower_eq(const key_type &key)
1760  {
1761  auto it = internal_get_biggest_less_than(
1762  key, not_greater_compare(_compare));
1763  return iterator(
1764  const_cast<typename iterator::node_ptr>(it.node));
1765  }
1766 
1777  const_iterator
1778  find_lower_eq(const key_type &key) const
1779  {
1780  return internal_get_biggest_less_than(
1781  key, not_greater_compare(_compare));
1782  }
1783 
1797  template <typename K,
1798  typename = typename std::enable_if<
1799  has_is_transparent<key_compare>::value, K>::type>
1800  iterator
1801  find_lower_eq(const K &key)
1802  {
1803  auto it = internal_get_biggest_less_than(
1804  key, not_greater_compare(_compare));
1805  return iterator(
1806  const_cast<typename iterator::node_ptr>(it.node));
1807  }
1808 
1822  template <typename K,
1823  typename = typename std::enable_if<
1824  has_is_transparent<key_compare>::value, K>::type>
1825  const_iterator
1826  find_lower_eq(const K &key) const
1827  {
1828  return internal_get_biggest_less_than(
1829  key, not_greater_compare(_compare));
1830  }
1831 
1840  iterator
1841  find(const key_type &key)
1842  {
1843  return internal_find(key);
1844  }
1845 
1854  const_iterator
1855  find(const key_type &key) const
1856  {
1857  return internal_find(key);
1858  }
1859 
1872  template <typename K,
1873  typename = typename std::enable_if<
1874  has_is_transparent<key_compare>::value, K>::type>
1875  iterator
1876  find(const K &x)
1877  {
1878  return internal_find(x);
1879  }
1880 
1893  template <typename K,
1894  typename = typename std::enable_if<
1895  has_is_transparent<key_compare>::value, K>::type>
1896  const_iterator
1897  find(const K &x) const
1898  {
1899  return internal_find(x);
1900  }
1901 
1911  size_type
1912  count(const key_type &key) const
1913  {
1914  return internal_count(key);
1915  }
1916 
1929  template <typename K,
1930  typename = typename std::enable_if<
1931  has_is_transparent<key_compare>::value, K>::type>
1932  size_type
1933  count(const K &key) const
1934  {
1935  return internal_count(key);
1936  }
1937 
1946  bool
1947  contains(const key_type &key) const
1948  {
1949  return find(key) != end();
1950  }
1951 
1964  template <typename K,
1965  typename = typename std::enable_if<
1966  has_is_transparent<key_compare>::value, K>::type>
1967  bool
1968  contains(const K &x) const
1969  {
1970  return find(x) != end();
1971  }
1972 
1981  void
1983  {
1984  assert(dummy_head->height() > 0);
1985  obj::pool_base pop = get_pool_base();
1986 
1987  persistent_node_ptr current = dummy_head->next(0);
1988 
1989  obj::flat_transaction::run(pop, [&] {
1990  while (current) {
1991  assert(current->height() > 0);
1992  persistent_node_ptr next = current->next(0);
1993  delete_node(current);
1994  current = next;
1995  }
1996 
1997  node_ptr head = dummy_head.get();
1998  for (size_type i = 0; i < head->height(); ++i) {
1999  head->set_next_tx(i, nullptr);
2000  }
2001 
2002  on_init_size = 0;
2003  tls_data.clear();
2004  obj::flat_transaction::snapshot((size_t *)&_size);
2005  _size = 0;
2006  });
2007  }
2008 
2015  iterator
2017  {
2018  return iterator(dummy_head.get()->next(0).get());
2019  }
2020 
2027  const_iterator
2028  begin() const
2029  {
2030  return const_iterator(dummy_head.get()->next(0).get());
2031  }
2032 
2039  const_iterator
2040  cbegin() const
2041  {
2042  return const_iterator(dummy_head.get()->next(0).get());
2043  }
2044 
2052  iterator
2054  {
2055  return iterator(nullptr);
2056  }
2057 
2065  const_iterator
2066  end() const
2067  {
2068  return const_iterator(nullptr);
2069  }
2070 
2078  const_iterator
2079  cend() const
2080  {
2081  return const_iterator(nullptr);
2082  }
2083 
2090  size_type
2091  size() const
2092  {
2093  return _size.load(std::memory_order_relaxed);
2094  }
2095 
2103  size_type
2104  max_size() const
2105  {
2106  return std::numeric_limits<difference_type>::max();
2107  }
2108 
2115  bool
2116  empty() const
2117  {
2118  return 0 == size();
2119  }
2120 
2133  void
2135  {
2136  obj::pool_base pop = get_pool_base();
2137  obj::flat_transaction::run(pop, [&] {
2138  using pocs_t = typename node_allocator_traits::
2139  propagate_on_container_swap;
2140  allocator_swap(_node_allocator, other._node_allocator,
2141  pocs_t());
2142  std::swap(_compare, other._compare);
2143  std::swap(_rnd_generator, other._rnd_generator);
2144  std::swap(dummy_head, other.dummy_head);
2145  on_init_size.swap(other.on_init_size);
2146 
2147  obj::flat_transaction::snapshot((size_t *)&_size);
2149  (size_t *)&(other._size));
2150  _size = other._size.exchange(_size,
2151  std::memory_order_relaxed);
2152  });
2153  }
2154 
2174  std::pair<iterator, iterator>
2175  equal_range(const key_type &key)
2176  {
2177  return std::pair<iterator, iterator>(lower_bound(key),
2178  upper_bound(key));
2179  }
2180 
2200  std::pair<const_iterator, const_iterator>
2201  equal_range(const key_type &key) const
2202  {
2203  return std::pair<const_iterator, const_iterator>(
2204  lower_bound(key), upper_bound(key));
2205  }
2206 
2229  template <typename K,
2230  typename = typename std::enable_if<
2231  has_is_transparent<key_compare>::value, K>::type>
2232  std::pair<iterator, iterator>
2233  equal_range(const K &x)
2234  {
2235  return std::pair<iterator, iterator>(lower_bound(x),
2236  upper_bound(x));
2237  }
2238 
2261  template <typename K,
2262  typename = typename std::enable_if<
2263  has_is_transparent<key_compare>::value, K>::type>
2264  std::pair<const_iterator, const_iterator>
2265  equal_range(const K &key) const
2266  {
2267  return std::pair<const_iterator, const_iterator>(
2268  lower_bound(key), upper_bound(key));
2269  }
2270 
2276  const key_compare &
2277  key_comp() const
2278  {
2279  return _compare;
2280  }
2281 
2287  key_compare &
2289  {
2290  return _compare;
2291  }
2292 
2293 private:
2294  /* Status flags stored in insert_stage field */
2295  enum insert_stage_type : uint8_t { not_started = 0, in_progress = 1 };
2296  /*
2297  * Structure of thread local data.
2298  * Size should be 64 bytes.
2299  */
2300  struct tls_entry_type {
2301  persistent_node_ptr ptr;
2302  obj::p<difference_type> size_diff;
2303  obj::p<insert_stage_type> insert_stage;
2304 
2305  char reserved[64 - sizeof(decltype(ptr)) -
2306  sizeof(decltype(size_diff)) -
2307  sizeof(decltype(insert_stage))];
2308  };
2309  static_assert(sizeof(tls_entry_type) == 64,
2310  "The size of tls_entry_type should be 64 bytes.");
2311 
2319  void
2320  check_tx_stage_work() const
2321  {
2322  if (pmemobj_tx_stage() != TX_STAGE_WORK)
2324  "Function called out of transaction scope.");
2325  }
2326 
2327  /* Helper method which throws an exception when called in a tx */
2328  static inline void
2329  check_outside_tx()
2330  {
2331  if (pmemobj_tx_stage() != TX_STAGE_NONE)
2333  "Function called inside transaction scope.");
2334  }
2335 
2336  void
2337  init()
2338  {
2339  if (pool_uuid == 0)
2340  throw pmem::pool_error("Invalid pool handle.");
2341 
2342  _size = 0;
2343  on_init_size = 0;
2344  create_dummy_head();
2345  }
2346 
2347  void
2348  internal_move(concurrent_skip_list &&other)
2349  {
2350  assert(this->empty());
2351  assert(pmemobj_tx_stage() == TX_STAGE_WORK);
2352  dummy_head = other.dummy_head;
2353  other.dummy_head = nullptr;
2354  other.create_dummy_head();
2355 
2356  _size.store(other._size.load(std::memory_order_relaxed),
2357  std::memory_order_relaxed);
2358  on_init_size = other.on_init_size;
2359  }
2360 
2361  static const_reference
2362  get_val(const_node_ptr n)
2363  {
2364  assert(n);
2365  return *(n->get());
2366  }
2367 
2368  static reference
2369  get_val(node_ptr n)
2370  {
2371  assert(n);
2372  return *(n->get());
2373  }
2374 
2375  static const key_type &
2376  get_key(const_node_ptr n)
2377  {
2378  assert(n);
2379  return traits_type::get_key(get_val(n));
2380  }
2381 
2382  template <typename K>
2383  iterator
2384  internal_find(const K &key)
2385  {
2386  iterator it = lower_bound(key);
2387  return (it == end() || _compare(key, traits_type::get_key(*it)))
2388  ? end()
2389  : it;
2390  }
2391 
2392  template <typename K>
2393  const_iterator
2394  internal_find(const K &key) const
2395  {
2396  const_iterator it = lower_bound(key);
2397  return (it == end() || _compare(key, traits_type::get_key(*it)))
2398  ? end()
2399  : it;
2400  }
2401 
2402  template <typename K>
2403  size_type
2404  internal_count(const K &key) const
2405  {
2406  if (allow_multimapping) {
2407  std::pair<const_iterator, const_iterator> range =
2408  equal_range(key);
2409  return static_cast<size_type>(
2410  std::distance(range.first, range.second));
2411  }
2412  return (find(key) == end()) ? size_type(0) : size_type(1);
2413  }
2414 
2425  template <typename K, typename pointer_type, typename comparator>
2426  persistent_node_ptr
2427  internal_find_position(size_type level, pointer_type &prev,
2428  const K &key, const comparator &cmp) const
2429  {
2430  assert(level < prev->height());
2431  persistent_node_ptr next = prev->next(level);
2432  pointer_type curr = next.get();
2433 
2434  while (curr && cmp(get_key(curr), key)) {
2435  prev = curr;
2436  assert(level < prev->height());
2437  next = prev->next(level);
2438  curr = next.get();
2439  }
2440 
2441  return next;
2442  }
2443 
2454  template <typename K>
2455  void
2456  find_insert_pos(prev_array_type &prev_nodes,
2457  next_array_type &next_nodes, const K &key)
2458  {
2459  if (allow_multimapping) {
2460  fill_prev_next_arrays(prev_nodes, next_nodes, key,
2461  not_greater_compare(_compare));
2462  } else {
2463  fill_prev_next_arrays(prev_nodes, next_nodes, key,
2464  _compare);
2465  }
2466  }
2467 
2479  template <typename K, typename comparator>
2480  void
2481  fill_prev_next_arrays(prev_array_type &prev_nodes,
2482  next_array_type &next_nodes, const K &key,
2483  const comparator &cmp)
2484  {
2485  node_ptr prev = dummy_head.get();
2486  prev_nodes.fill(prev);
2487  next_nodes.fill(nullptr);
2488 
2489  for (size_type h = prev->height(); h > 0; --h) {
2490  persistent_node_ptr next =
2491  internal_find_position(h - 1, prev, key, cmp);
2492  prev_nodes[h - 1] = prev;
2493  next_nodes[h - 1] = next;
2494  }
2495  }
2496 
2497  template <typename K, typename... Args>
2498  std::pair<iterator, bool>
2499  internal_try_emplace(K &&key, Args &&... args)
2500  {
2501  return internal_insert(
2502  key, std::piecewise_construct,
2503  std::forward_as_tuple(std::forward<K>(key)),
2504  std::forward_as_tuple(std::forward<Args>(args)...));
2505  }
2506 
2507  template <typename... Args>
2508  std::pair<iterator, bool>
2509  internal_emplace(Args &&... args)
2510  {
2511  check_outside_tx();
2512  tls_entry_type &tls_entry = tls_data.local();
2513  obj::pool_base pop = get_pool_base();
2514 
2515  obj::flat_transaction::run(pop, [&] {
2516  assert(tls_entry.ptr == nullptr);
2517  tls_entry.ptr =
2518  create_node(std::forward<Args>(args)...);
2519  ++tls_entry.size_diff;
2520  tls_entry.insert_stage = not_started;
2521  });
2522 
2523  node_ptr n = tls_entry.ptr.get();
2524  size_type height = n->height();
2525 
2526  std::pair<iterator, bool> insert_result = internal_insert_node(
2527  get_key(n), height,
2528  [&](const next_array_type &next_nodes)
2529  -> persistent_node_ptr & {
2530  assert(tls_entry.insert_stage == not_started);
2531  assert(tls_entry.ptr != nullptr);
2532 
2533  n->set_nexts(pop, next_nodes.data(), height);
2534 
2535  tls_entry.insert_stage = in_progress;
2536  pop.persist(&(tls_entry.insert_stage),
2537  sizeof(tls_entry.insert_stage));
2538 
2539  return tls_entry.ptr;
2540  });
2541 
2542  if (!insert_result.second) {
2543  assert(tls_entry.ptr != nullptr);
2544  assert(tls_entry.insert_stage == not_started);
2545 
2546  obj::flat_transaction::run(pop, [&] {
2547  --tls_entry.size_diff;
2548  delete_node(tls_entry.ptr);
2549  tls_entry.ptr = nullptr;
2550  });
2551  }
2552 
2553  assert(tls_entry.ptr == nullptr);
2554  return insert_result;
2555  }
2556 
2561  template <typename... Args>
2562  std::pair<iterator, bool>
2563  internal_unsafe_emplace(Args &&... args)
2564  {
2565  check_tx_stage_work();
2566 
2567  persistent_node_ptr new_node =
2568  create_node(std::forward<Args>(args)...);
2569 
2570  node_ptr n = new_node.get();
2571  size_type height = n->height();
2572 
2573  std::pair<iterator, bool> insert_result = internal_insert_node(
2574  get_key(n), height,
2575  [&](const next_array_type &next_nodes)
2576  -> persistent_node_ptr & {
2577  assert(new_node != nullptr);
2578 
2579  n->set_nexts(next_nodes.data(), height);
2580 
2581  return new_node;
2582  });
2583 
2584  if (insert_result.second) {
2585  ++on_init_size;
2586  } else {
2587  assert(new_node != nullptr);
2588 
2589  delete_node(new_node);
2590  }
2591 
2592  return insert_result;
2593  }
2594 
2598  template <typename K, typename... Args>
2599  std::pair<iterator, bool>
2600  internal_insert(const K &key, Args &&... args)
2601  {
2602  check_outside_tx();
2603  tls_entry_type &tls_entry = tls_data.local();
2604  assert(tls_entry.ptr == nullptr);
2605 
2606  size_type height = random_level();
2607 
2608  std::pair<iterator, bool> insert_result = internal_insert_node(
2609  key, height,
2610  [&](const next_array_type &next_nodes)
2611  -> persistent_node_ptr & {
2612  obj::pool_base pop = get_pool_base();
2613 
2615  tls_entry.ptr = create_node(
2616  std::forward_as_tuple(
2617  height, next_nodes.data()),
2618  std::forward_as_tuple(
2619  std::forward<Args>(args)...));
2620 
2621  ++(tls_entry.size_diff);
2622  tls_entry.insert_stage = in_progress;
2624 
2625  assert(tls_entry.ptr != nullptr);
2626  return tls_entry.ptr;
2627  });
2628 
2629  assert(tls_entry.ptr == nullptr);
2630 
2631  return insert_result;
2632  }
2633 
2637  template <typename K, typename PrepareNode>
2638  std::pair<iterator, bool>
2639  internal_insert_node(const K &key, size_type height,
2640  PrepareNode &&prepare_new_node)
2641  {
2642  prev_array_type prev_nodes;
2643  next_array_type next_nodes;
2644  node_ptr n = nullptr;
2645 
2646  do {
2647  find_insert_pos(prev_nodes, next_nodes, key);
2648 
2649  node_ptr next = next_nodes[0].get();
2650  if (next && !allow_multimapping &&
2651  !_compare(key, get_key(next))) {
2652 
2653  return std::pair<iterator, bool>(iterator(next),
2654  false);
2655  }
2656 
2657  } while ((n = try_insert_node(prev_nodes, next_nodes, height,
2658  std::forward<PrepareNode>(
2659  prepare_new_node))) ==
2660  nullptr);
2661 
2662  assert(n);
2663  return std::pair<iterator, bool>(iterator(n), true);
2664  }
2665 
2671  template <typename PrepareNode>
2672  node_ptr
2673  try_insert_node(prev_array_type &prev_nodes,
2674  const next_array_type &next_nodes, size_type height,
2675  PrepareNode &&prepare_new_node)
2676  {
2677  assert(dummy_head->height() >= height);
2678 
2679  lock_array locks;
2680  if (!try_lock_nodes(height, prev_nodes, next_nodes, locks)) {
2681  return nullptr;
2682  }
2683 
2684  node_lock_type new_node_lock;
2685 
2686  persistent_node_ptr &new_node = prepare_new_node(next_nodes);
2687  assert(new_node != nullptr);
2688  node_ptr n = new_node.get();
2689 
2690  /*
2691  * We need to hold lock to the new node until changes
2692  * are committed to persistent domain. Otherwise, the
2693  * new node would be visible to concurrent inserts
2694  * before it is persisted.
2695  */
2696  new_node_lock = n->acquire();
2697 
2698  obj::pool_base pop = get_pool_base();
2699  /*
2700  * In the loop below we are linking a new node to all layers of
2701  * the skip list. Transaction is not required because in case of
2702  * failure the node is reachable via a pointer from persistent
2703  * TLS. During recovery, we will complete the insert. It is also
2704  * OK if concurrent readers will see not a fully-linked node
2705  * because during recovery the insert procedure will be
2706  * completed.
2707  */
2708  for (size_type level = 0; level < height; ++level) {
2709  assert(prev_nodes[level]->height() > level);
2710  assert(prev_nodes[level]->next(level) ==
2711  next_nodes[level]);
2712  assert(prev_nodes[level]->next(level) ==
2713  n->next(level));
2714  prev_nodes[level]->set_next(pop, level, new_node);
2715  }
2716 
2717 #ifndef NDEBUG
2718  try_insert_node_finish_marker();
2719 #endif
2720 
2721  new_node = nullptr;
2722  /* We need to persist the node pointer. Otherwise, on a restart,
2723  * this pointer might be not null but the node can be already
2724  * deleted. */
2725  pop.persist(&new_node, sizeof(new_node));
2726 
2727  ++_size;
2728 #if LIBPMEMOBJ_CPP_VG_PMEMCHECK_ENABLED
2729  VALGRIND_PMC_DO_FLUSH(&_size, sizeof(_size));
2730 #endif
2731 
2732  assert(n);
2733  return n;
2734  }
2735 
2740  bool
2741  check_prev_array(const prev_array_type &prevs, size_type height)
2742  {
2743  for (size_type l = 1; l < height; ++l) {
2744  if (prevs[l] == dummy_head.get()) {
2745  continue;
2746  }
2747 
2748  assert(prevs[l - 1] != dummy_head.get());
2749  assert(!_compare(get_key(prevs[l - 1]),
2750  get_key(prevs[l])));
2751  }
2752 
2753  return true;
2754  }
2755 
2756  bool
2757  try_lock_nodes(size_type height, prev_array_type &prevs,
2758  const next_array_type &nexts, lock_array &locks)
2759  {
2760  assert(check_prev_array(prevs, height));
2761 
2762  for (size_type l = 0; l < height; ++l) {
2763  if (l == 0 || prevs[l] != prevs[l - 1]) {
2764  locks[l] = prevs[l]->acquire();
2765  }
2766 
2767  persistent_node_ptr next = prevs[l]->next(l);
2768  if (next != nexts[l])
2769  /* Other thread inserted to this position and
2770  * modified the pointer before we acquired the
2771  * lock */
2772  return false;
2773  }
2774 
2775  return true;
2776  }
2777 
2789  template <typename K, typename comparator>
2790  const_iterator
2791  internal_get_bound(const K &key, const comparator &cmp) const
2792  {
2793  const_node_ptr prev = dummy_head.get();
2794  assert(prev->height() > 0);
2795  persistent_node_ptr next = nullptr;
2796 
2797  for (size_type h = prev->height(); h > 0; --h) {
2798  next = internal_find_position(h - 1, prev, key, cmp);
2799  }
2800 
2801  return const_iterator(next.get());
2802  }
2803 
2815  template <typename K, typename comparator>
2816  iterator
2817  internal_get_bound(const K &key, const comparator &cmp)
2818  {
2819  node_ptr prev = dummy_head.get();
2820  assert(prev->height() > 0);
2821  persistent_node_ptr next = nullptr;
2822 
2823  for (size_type h = prev->height(); h > 0; --h) {
2824  next = internal_find_position(h - 1, prev, key, cmp);
2825  }
2826 
2827  return iterator(next.get());
2828  }
2829 
2841  template <typename K, typename comparator>
2842  const_iterator
2843  internal_get_biggest_less_than(const K &key,
2844  const comparator &cmp) const
2845  {
2846  const_node_ptr prev = dummy_head.get();
2847  assert(prev->height() > 0);
2848 
2849  for (size_type h = prev->height(); h > 0; --h) {
2850  internal_find_position(h - 1, prev, key, cmp);
2851  }
2852 
2853  if (prev == dummy_head.get())
2854  return end();
2855 
2856  return const_iterator(prev);
2857  }
2858 
2859  iterator
2860  internal_erase(const_iterator pos, obj::p<difference_type> &size_diff)
2861  {
2862  assert(pos != end());
2863 
2864  obj::pool_base pop = get_pool_base();
2865 
2866  std::pair<persistent_node_ptr, persistent_node_ptr>
2867  extract_result(nullptr, nullptr);
2868 
2869  obj::flat_transaction::run(pop, [&] {
2870  extract_result = internal_extract(pos);
2871 
2872  /* Make sure that node was extracted */
2873  assert(extract_result.first != nullptr);
2874  delete_node(extract_result.first);
2875  --size_diff;
2876  obj::flat_transaction::snapshot((size_type *)&_size);
2877  --_size;
2878  });
2879 
2880  return iterator(extract_result.second.get());
2881  }
2882 
2886  std::pair<persistent_node_ptr, persistent_node_ptr>
2887  internal_extract(const_iterator it)
2888  {
2889  assert(dummy_head->height() > 0);
2890  assert(it != end());
2891  assert(pmemobj_tx_stage() == TX_STAGE_WORK);
2892 
2893  const key_type &key = traits_type::get_key(*it);
2894 
2895  prev_array_type prev_nodes;
2896  next_array_type next_nodes;
2897 
2898  fill_prev_next_arrays(prev_nodes, next_nodes, key, _compare);
2899 
2900  node_ptr erase_node = next_nodes[0].get();
2901  assert(erase_node != nullptr);
2902 
2903  if (!_compare(key, get_key(erase_node))) {
2904  /* XXX: this assertion will fail in case of multimap
2905  * because we take the first node with the same key.
2906  * Need to extend algorithm for mutimap. */
2907  assert(erase_node == it.node);
2908  return internal_extract_node(prev_nodes, next_nodes,
2909  erase_node);
2910  }
2911 
2912  return std::pair<persistent_node_ptr, persistent_node_ptr>(
2913  nullptr, nullptr);
2914  }
2915 
2916  std::pair<persistent_node_ptr, persistent_node_ptr>
2917  internal_extract_node(const prev_array_type &prev_nodes,
2918  const next_array_type &next_nodes,
2919  node_ptr erase_node)
2920  {
2921  assert(pmemobj_tx_stage() == TX_STAGE_WORK);
2922  assert(erase_node != nullptr);
2923  for (size_type level = 0; level < erase_node->height();
2924  ++level) {
2925  assert(prev_nodes[level]->height() > level);
2926  assert(next_nodes[level].get() == erase_node);
2927  prev_nodes[level]->set_next_tx(level,
2928  erase_node->next(level));
2929  }
2930 
2931  return std::pair<persistent_node_ptr, persistent_node_ptr>(
2932  next_nodes[0], erase_node->next(0));
2933  }
2934 
2939  obj::pool_base
2940  get_pool_base() const
2941  {
2942  PMEMobjpool *pop = pmemobj_pool_by_ptr(this);
2943  return obj::pool_base(pop);
2944  }
2945 
2946  void
2947  internal_copy(const concurrent_skip_list &other)
2948  {
2949  internal_copy(other.begin(), other.end());
2950  }
2951 
2952  template <typename Iterator>
2953  void
2954  internal_copy(Iterator first, Iterator last)
2955  {
2956  assert(pmemobj_tx_stage() == TX_STAGE_WORK);
2957 
2958  prev_array_type prev_nodes;
2959  prev_nodes.fill(dummy_head.get());
2960  size_type sz = 0;
2961 
2962  for (; first != last; ++first, ++sz) {
2963  persistent_node_ptr new_node = create_node(*first);
2964  node_ptr n = new_node.get();
2965  for (size_type level = 0; level < n->height();
2966  ++level) {
2967  prev_nodes[level]->set_next_tx(level, new_node);
2968  prev_nodes[level] = n;
2969  }
2970  }
2971 
2972  on_init_size = sz;
2973  /*
2974  * As internal_swap can only be called from one thread, and
2975  * there can be an outer transaction we must make sure that size
2976  * change is transactional
2977  */
2978  obj::flat_transaction::snapshot((size_type *)&_size);
2979  _size = sz;
2980  assert(std::is_sorted(
2981  this->begin(), this->end(),
2982  [&](const value_type &lhs, const value_type &rhs) {
2983  return lhs.first < rhs.first;
2984  }));
2985  }
2986 
2988  size_type
2989  random_level()
2990  {
2991  return _rnd_generator();
2992  }
2993 
2994  static size_type
2995  calc_node_size(size_type height)
2996  {
2997  return sizeof(list_node_type) +
2998  height * sizeof(typename list_node_type::node_pointer);
2999  }
3000 
3002  template <typename... Args>
3003  persistent_node_ptr
3004  create_node(Args &&... args)
3005  {
3006  size_type levels = random_level();
3007 
3008  return create_node(
3009  std::forward_as_tuple(levels),
3010  std::forward_as_tuple(std::forward<Args>(args)...));
3011  }
3012 
3013  template <typename... NodeArgs, typename... ValueArgs>
3014  persistent_node_ptr
3015  create_node(std::tuple<NodeArgs...> &&node_args,
3016  std::tuple<ValueArgs...> &&value_args)
3017  {
3018  assert(pmemobj_tx_stage() == TX_STAGE_WORK);
3019 
3020  persistent_node_ptr node = creates_dummy_node(
3021  std::forward<std::tuple<NodeArgs...>>(node_args),
3022  index_sequence_for<NodeArgs...>{});
3023 
3024  construct_value_type(
3025  node,
3026  std::forward<std::tuple<ValueArgs...>>(value_args),
3027  index_sequence_for<ValueArgs...>{});
3028 
3029  return node;
3030  }
3031 
3032  template <typename Tuple, size_t... I>
3033  void
3034  construct_value_type(persistent_node_ptr node, Tuple &&args,
3035  index_sequence<I...>)
3036  {
3037  node_ptr new_node = node.get();
3038 
3039  node_allocator_traits::construct(
3040  _node_allocator, new_node->get(),
3041  std::get<I>(std::forward<Tuple>(args))...);
3042  }
3043 
3049  void
3050  create_dummy_head()
3051  {
3052  dummy_head = creates_dummy_node(MAX_LEVEL);
3053  }
3054 
3055  template <typename Tuple, size_t... I>
3056  persistent_node_ptr
3057  creates_dummy_node(Tuple &&args, index_sequence<I...>)
3058  {
3059  return creates_dummy_node(
3060  std::get<I>(std::forward<Tuple>(args))...);
3061  }
3062 
3072  template <typename... Args>
3073  persistent_node_ptr
3074  creates_dummy_node(size_type height, Args &&... args)
3075  {
3076  assert(pmemobj_tx_stage() == TX_STAGE_WORK);
3077  size_type sz = calc_node_size(height);
3078 
3079  persistent_node_ptr n =
3080  node_allocator_traits::allocate(_node_allocator, sz)
3081  .raw();
3082 
3083  assert(n != nullptr);
3084 
3085  node_allocator_traits::construct(_node_allocator, n.get(),
3086  height,
3087  std::forward<Args>(args)...);
3088 
3089  return n;
3090  }
3091 
3092  template <bool is_dummy = false>
3093  void
3094  delete_node(persistent_node_ptr &node)
3095  {
3096  assert(pmemobj_tx_stage() == TX_STAGE_WORK);
3097  node_ptr n = node.get();
3098  size_type sz = calc_node_size(n->height());
3099 
3100  /* Destroy value */
3101  if (!is_dummy)
3102  node_allocator_traits::destroy(_node_allocator,
3103  n->get());
3104  /* Destroy node */
3105  node_allocator_traits::destroy(_node_allocator, n);
3106  /* Deallocate memory */
3107  deallocate_node(node, sz);
3108  node = nullptr;
3109  }
3110 
3111  void
3112  deallocate_node(persistent_node_ptr &node, size_type sz)
3113  {
3114  /*
3115  * Each node object has different size which depends on number
3116  * of layers the node is linked. Therefore, allocate/deallocate
3117  * just a raw byte array. persistent_ptr<uint8_t> is used as a
3118  * pointer to raw array of bytes.
3119  */
3120  obj::persistent_ptr<uint8_t> tmp =
3121  node.to_persistent_ptr().raw();
3122  node_allocator_traits::deallocate(_node_allocator, tmp, sz);
3123  }
3124 
3125  void
3126  delete_dummy_head()
3127  {
3128  assert(dummy_head != nullptr);
3129  delete_node<true>(dummy_head);
3130  assert(dummy_head == nullptr);
3131  }
3132 
3133  iterator
3134  get_iterator(const_iterator it)
3135  {
3136  return iterator(
3137  const_cast<typename iterator::node_ptr>(it.node));
3138  }
3139 
3141  void
3142  tls_restore()
3143  {
3144  int64_t last_run_size = 0;
3145  obj::pool_base pop = get_pool_base();
3146 
3147  for (auto &tls_entry : tls_data) {
3148  persistent_node_ptr &node = tls_entry.ptr;
3149  auto &size_diff = tls_entry.size_diff;
3150  if (node) {
3151  /*
3152  * We are completing inserts which were in
3153  * progress before the crash because readers
3154  * might saw incompleted inserts before the
3155  * crash. We set the in_progress flag inside
3156  * try_insert_node function when we locked the
3157  * predecessors for the new node, therefore,
3158  * only single node with the same key might have
3159  * the in_progress status.
3160  */
3161  if (tls_entry.insert_stage == in_progress) {
3162  complete_insert(tls_entry);
3163  } else {
3164  obj::flat_transaction::run(pop, [&] {
3165  --(tls_entry.size_diff);
3166  delete_node(node);
3167  node = nullptr;
3168  });
3169  }
3170  }
3171 
3172  assert(node == nullptr);
3173 
3174  last_run_size += size_diff;
3175  }
3176 
3177  /* Make sure that on_init_size + last_run_size >= 0 */
3178  assert(last_run_size >= 0 ||
3179  on_init_size >
3180  static_cast<size_type>(std::abs(last_run_size)));
3181  obj::flat_transaction::run(pop, [&] {
3182  tls_data.clear();
3183  on_init_size += static_cast<size_t>(last_run_size);
3184  });
3185  _size = on_init_size;
3186 #if LIBPMEMOBJ_CPP_VG_PMEMCHECK_ENABLED
3187  VALGRIND_PMC_DO_FLUSH(&_size, sizeof(_size));
3188 #endif
3189  }
3190 
3191  void
3192  complete_insert(tls_entry_type &tls_entry)
3193  {
3194  persistent_node_ptr &node = tls_entry.ptr;
3195  assert(node != nullptr);
3196  assert(tls_entry.insert_stage == in_progress);
3197  prev_array_type prev_nodes;
3198  next_array_type next_nodes;
3199  node_ptr n = node.get();
3200  const key_type &key = get_key(n);
3201  size_type height = n->height();
3202 
3203  fill_prev_next_arrays(prev_nodes, next_nodes, key, _compare);
3204  obj::pool_base pop = get_pool_base();
3205 
3206  /* Node was partially linked */
3207  for (size_type level = 0; level < height; ++level) {
3208  assert(prev_nodes[level]->height() > level);
3209  assert(prev_nodes[level]->next(level) ==
3210  next_nodes[level]);
3211 
3212  if (prev_nodes[level]->next(level) != node) {
3213  /* Otherwise, node already linked on
3214  * this layer */
3215  assert(n->next(level) == next_nodes[level]);
3216  prev_nodes[level]->set_next(pop, level, node);
3217  }
3218  }
3219 
3220  node = nullptr;
3221  pop.persist(&node, sizeof(node));
3222  }
3223 
3224  struct not_greater_compare {
3225  const key_compare &my_less_compare;
3226 
3227  not_greater_compare(const key_compare &less_compare)
3228  : my_less_compare(less_compare)
3229  {
3230  }
3231 
3232  template <typename K1, typename K2>
3233  bool
3234  operator()(const K1 &first, const K2 &second) const
3235  {
3236  return !my_less_compare(second, first);
3237  }
3238  };
3239 
3240  const uint64_t pool_uuid = pmemobj_oid(this).pool_uuid_lo;
3241  node_allocator_type _node_allocator;
3242  key_compare _compare;
3243  random_level_generator_type _rnd_generator;
3244  persistent_node_ptr dummy_head;
3245 
3246  enumerable_thread_specific<tls_entry_type> tls_data;
3247 
3248  std::atomic<size_type> _size;
3249 
3255  obj::p<size_type> on_init_size;
3256 }; /* class concurrent_skip_list */
3257 
3258 template <typename Key, typename Value, typename KeyCompare,
3259  typename RND_GENERATOR, typename Allocator, bool AllowMultimapping,
3260  size_t MAX_LEVEL>
3261 class map_traits {
3262 public:
3263  static constexpr size_t max_level = MAX_LEVEL;
3264  using random_generator_type = RND_GENERATOR;
3265  using key_type = Key;
3266  using mapped_type = Value;
3267  using compare_type = KeyCompare;
3268  using value_type = pair<const key_type, mapped_type>;
3269  using reference = value_type &;
3270  using const_reference = const value_type &;
3271  using allocator_type = Allocator;
3272 
3279  constexpr static bool allow_multimapping = AllowMultimapping;
3280 
3281  static const key_type &
3282  get_key(const_reference val)
3283  {
3284  return val.first;
3285  }
3286 }; /* class map_traits */
3287 
3288 } /* namespace detail */
3289 } /* namespace pmem */
3290 
3291 #endif /* PMEMOBJ_CONCURRENT_SKIP_LIST_IMPL_HPP */
Atomic specialization for self_relative_ptr.
Persistent memory aware implementation of the concurrent skip list.
Definition: concurrent_skip_list_impl.hpp:484
bool contains(const key_type &key) const
Checks if there is an element with key equivalent to key in the container.
Definition: concurrent_skip_list_impl.hpp:1947
iterator find_higher_eq(const K &x)
Returns an iterator pointing to the first element that compares not less (i.e.
Definition: concurrent_skip_list_impl.hpp:1488
const_iterator lower_bound(const key_type &key) const
Returns an iterator pointing to the first element that is not less than (i.e.
Definition: concurrent_skip_list_impl.hpp:1389
iterator find_higher(const key_type &key)
Returns an iterator pointing to the first element that is greater than key.
Definition: concurrent_skip_list_impl.hpp:1603
concurrent_skip_list & operator=(const concurrent_skip_list &other)
Copy assignment operator.
Definition: concurrent_skip_list_impl.hpp:801
const_iterator find_lower_eq(const K &key) const
Returns a const iterator pointing to the biggest element that is less than or equal to key.
Definition: concurrent_skip_list_impl.hpp:1826
std::pair< const_iterator, const_iterator > equal_range(const key_type &key) const
Returns a range containing all elements with the given key in the container.
Definition: concurrent_skip_list_impl.hpp:2201
std::pair< iterator, bool > insert(P &&value)
Inserts value.
Definition: concurrent_skip_list_impl.hpp:938
concurrent_skip_list(const concurrent_skip_list &other, const allocator_type &alloc)
Copy constructor.
Definition: concurrent_skip_list_impl.hpp:651
iterator find_higher_eq(const key_type &key)
Returns an iterator pointing to the first element that is not less than (i.e.
Definition: concurrent_skip_list_impl.hpp:1449
void swap(concurrent_skip_list &other)
XXX: Implement get_allocator() interface.
Definition: concurrent_skip_list_impl.hpp:2134
const_iterator find_lower(const K &key) const
Returns a const iterator pointing to the biggest element that is less than key.
Definition: concurrent_skip_list_impl.hpp:1743
const_iterator find(const K &x) const
Finds an element with key that compares equivalent to the value x.
Definition: concurrent_skip_list_impl.hpp:1897
~concurrent_skip_list()
Destructor.
Definition: concurrent_skip_list_impl.hpp:776
const_iterator cend() const
Returns an iterator to the element following the last element of the map.
Definition: concurrent_skip_list_impl.hpp:2079
std::pair< iterator, bool > emplace(Args &&... args)
Inserts a new element into the container constructed in-place with the given args if there is no elem...
Definition: concurrent_skip_list_impl.hpp:1088
concurrent_skip_list()
Default constructor.
Definition: concurrent_skip_list_impl.hpp:536
iterator upper_bound(const key_type &key)
Returns an iterator pointing to the first element that is greater than key.
Definition: concurrent_skip_list_impl.hpp:1527
const_iterator cbegin() const
Returns an iterator to the first element of the container.
Definition: concurrent_skip_list_impl.hpp:2040
iterator lower_bound(const key_type &key)
Returns an iterator pointing to the first element that is not less than (i.e.
Definition: concurrent_skip_list_impl.hpp:1373
const_iterator begin() const
Returns an iterator to the first element of the container.
Definition: concurrent_skip_list_impl.hpp:2028
iterator begin()
Returns an iterator to the first element of the container.
Definition: concurrent_skip_list_impl.hpp:2016
const key_compare & key_comp() const
Returns a const reference to the object that compares the keys.
Definition: concurrent_skip_list_impl.hpp:2277
concurrent_skip_list(concurrent_skip_list &&other)
Move constructor.
Definition: concurrent_skip_list_impl.hpp:681
std::pair< iterator, iterator > equal_range(const K &x)
Returns a range containing all elements with the given key in the container.
Definition: concurrent_skip_list_impl.hpp:2233
iterator find_lower_eq(const K &key)
Returns an iterator pointing to the biggest element that is less than or equal to key.
Definition: concurrent_skip_list_impl.hpp:1801
size_type max_size() const
Returns the maximum number of elements the container is able to hold due to system or library impleme...
Definition: concurrent_skip_list_impl.hpp:2104
iterator find_lower(const key_type &key)
Returns an iterator pointing to the biggest element that is less than key.
Definition: concurrent_skip_list_impl.hpp:1679
const_iterator find_lower(const key_type &key) const
Returns a const iterator pointing to the biggest element that is less than key.
Definition: concurrent_skip_list_impl.hpp:1697
iterator insert(const_iterator hint, const_reference value)
Inserts value in the position as close as possible, just prior to hint.
Definition: concurrent_skip_list_impl.hpp:983
std::enable_if< has_is_transparent< key_compare >::value &&std::is_constructible< key_type, K && >::value, std::pair< iterator, bool > >::type try_emplace(K &&k, Args &&... args)
If a key equivalent to k already exists in the container, does nothing.
Definition: concurrent_skip_list_impl.hpp:1223
concurrent_skip_list(const key_compare &comp, const allocator_type &alloc=allocator_type())
Constructs an empty container.
Definition: concurrent_skip_list_impl.hpp:558
const_iterator find_higher_eq(const key_type &key) const
Returns an iterator pointing to the first element that is not less than (i.e.
Definition: concurrent_skip_list_impl.hpp:1465
const_iterator find_higher(const key_type &key) const
Returns an iterator pointing to the first element that is greater than key.
Definition: concurrent_skip_list_impl.hpp:1619
iterator insert(const_iterator hint, P &&value)
Inserts value in the position as close as possible, just prior to hint.
Definition: concurrent_skip_list_impl.hpp:1013
size_type count(const K &key) const
Returns the number of elements with key that compares equivalent to the specified argument.
Definition: concurrent_skip_list_impl.hpp:1933
std::pair< iterator, iterator > equal_range(const key_type &key)
Returns a range containing all elements with the given key in the container.
Definition: concurrent_skip_list_impl.hpp:2175
size_type unsafe_erase(const K &key)
Removes the element (if one exists) with the key equivalent to key.
Definition: concurrent_skip_list_impl.hpp:1353
concurrent_skip_list(const concurrent_skip_list &other)
Copy constructor.
Definition: concurrent_skip_list_impl.hpp:619
key_compare & key_comp()
Returns a reference to the object that compares the keys.
Definition: concurrent_skip_list_impl.hpp:2288
std::pair< const_iterator, const_iterator > equal_range(const K &key) const
Returns a range containing all elements with the given key in the container.
Definition: concurrent_skip_list_impl.hpp:2265
bool contains(const K &x) const
Checks if there is an element with key that compares equivalent to the value x.
Definition: concurrent_skip_list_impl.hpp:1968
iterator end()
Returns an iterator to the element following the last element of the map.
Definition: concurrent_skip_list_impl.hpp:2053
iterator find(const key_type &key)
Finds an element with key equivalent to key.
Definition: concurrent_skip_list_impl.hpp:1841
const_iterator find(const key_type &key) const
Finds an element with key equivalent to key.
Definition: concurrent_skip_list_impl.hpp:1855
iterator lower_bound(const K &x)
Returns an iterator pointing to the first element that compares not less (i.e.
Definition: concurrent_skip_list_impl.hpp:1411
concurrent_skip_list & operator=(std::initializer_list< value_type > il)
Replaces the contents with those identified by initializer list il.
Definition: concurrent_skip_list_impl.hpp:883
concurrent_skip_list(InputIt first, InputIt last, const key_compare &comp=key_compare(), const allocator_type &alloc=allocator_type())
Constructs the container with the contents of the range [first, last).
Definition: concurrent_skip_list_impl.hpp:591
std::pair< iterator, bool > insert(const value_type &value)
Inserts value in a thread-safe way.
Definition: concurrent_skip_list_impl.hpp:911
iterator unsafe_erase(const_iterator pos)
Removes the element at pos from the container.
Definition: concurrent_skip_list_impl.hpp:1270
iterator find(const K &x)
Finds an element with key that compares equivalent to the value x.
Definition: concurrent_skip_list_impl.hpp:1876
const_iterator end() const
Returns an iterator to the element following the last element of the map.
Definition: concurrent_skip_list_impl.hpp:2066
void clear()
Erases all elements from the container transactionally.
Definition: concurrent_skip_list_impl.hpp:1982
concurrent_skip_list(concurrent_skip_list &&other, const allocator_type &alloc)
Move constructor.
Definition: concurrent_skip_list_impl.hpp:710
const_iterator find_lower_eq(const key_type &key) const
Returns a const iterator pointing to the biggest element that is less than or equal to key.
Definition: concurrent_skip_list_impl.hpp:1778
const_iterator find_higher_eq(const K &x) const
Returns an iterator pointing to the first element that compares not less (i.e.
Definition: concurrent_skip_list_impl.hpp:1511
concurrent_skip_list & operator=(concurrent_skip_list &&other)
Move assignment operator.
Definition: concurrent_skip_list_impl.hpp:843
size_type unsafe_erase(const key_type &key)
Removes the element (if one exists) with the key equivalent to key.
Definition: concurrent_skip_list_impl.hpp:1318
iterator emplace_hint(const_iterator hint, Args &&... args)
Inserts a new element to the container as close as possible to the position just before hint.
Definition: concurrent_skip_list_impl.hpp:1125
void insert(InputIterator first, InputIterator last)
Inserts elements from range [first, last).
Definition: concurrent_skip_list_impl.hpp:1034
iterator upper_bound(const K &x)
Returns an iterator pointing to the first element that compares greater to the value x.
Definition: concurrent_skip_list_impl.hpp:1565
iterator find_lower_eq(const key_type &key)
Returns an iterator pointing to the biggest element that is less than or equal to key.
Definition: concurrent_skip_list_impl.hpp:1759
void runtime_initialize()
Initialize concurrent_skip_list after process restart.
Definition: concurrent_skip_list_impl.hpp:734
size_type count(const key_type &key) const
Returns the number of elements with key that compares equivalent to the specified argument.
Definition: concurrent_skip_list_impl.hpp:1912
const_iterator lower_bound(const K &x) const
Returns an iterator pointing to the first element that compares not less (i.e.
Definition: concurrent_skip_list_impl.hpp:1433
iterator unsafe_erase(iterator pos)
Removes the element at pos from the container.
Definition: concurrent_skip_list_impl.hpp:1246
void free_data()
Should be called before concurrent_skip_list destructor is called.
Definition: concurrent_skip_list_impl.hpp:755
std::pair< iterator, bool > try_emplace(const key_type &k, Args &&... args)
If a key equivalent to k already exists in the container, does nothing.
Definition: concurrent_skip_list_impl.hpp:1156
const_iterator upper_bound(const K &x) const
Returns an iterator pointing to the first element that compares greater to the value x.
Definition: concurrent_skip_list_impl.hpp:1587
std::pair< iterator, bool > try_emplace(key_type &&k, Args &&... args)
If a key equivalent to k already exists in the container, does nothing.
Definition: concurrent_skip_list_impl.hpp:1186
const_iterator find_higher(const K &x) const
Returns an iterator pointing to the first element that compares greater to the value x.
Definition: concurrent_skip_list_impl.hpp:1663
bool empty() const
Checks if the container has no elements, i.e.
Definition: concurrent_skip_list_impl.hpp:2116
size_type size() const
Returns the number of elements in the container, i.e.
Definition: concurrent_skip_list_impl.hpp:2091
const_iterator upper_bound(const key_type &key) const
Returns an iterator pointing to the first element that is greater than key.
Definition: concurrent_skip_list_impl.hpp:1543
iterator find_lower(const K &key)
Returns an iterator pointing to the biggest element that is less than key.
Definition: concurrent_skip_list_impl.hpp:1719
iterator find_higher(const K &x)
Returns an iterator pointing to the first element that compares greater to the value x.
Definition: concurrent_skip_list_impl.hpp:1641
void insert(std::initializer_list< value_type > ilist)
Inserts elements from initializer list ilist.
Definition: concurrent_skip_list_impl.hpp:1054
iterator unsafe_erase(const_iterator first, const_iterator last)
Removes the elements in the range [first; last), which must be a valid range in *this.
Definition: concurrent_skip_list_impl.hpp:1290
std::pair< iterator, bool > insert(value_type &&value)
Inserts value using move semantic.
Definition: concurrent_skip_list_impl.hpp:960
void clear()
Removes all elements from the container.
Definition: enumerable_thread_specific.hpp:345
reference local()
Returns data reference for the current thread.
Definition: enumerable_thread_specific.hpp:305
static void commit()
Manually commit a transaction.
Definition: transaction.hpp:330
static void snapshot(const T *addr, size_t num=1)
Takes a “snapshot” of given elements of type T number (1 by default), located at the given address pt...
Definition: transaction.hpp:437
Persistent self-relative pointer class.
Definition: self_relative_ptr.hpp:82
element_type * get() const noexcept
Get the direct pointer.
Definition: self_relative_ptr.hpp:197
typename detail::transaction_base< true >::manual manual
C++ manual scope transaction class.
Definition: transaction.hpp:745
static void run(obj::pool_base &pool, std::function< void()> tx, Locks &... locks)
Execute a closure-like transaction and lock locks.
Definition: transaction.hpp:810
Persistent memory resident mutex implementation.
Definition: mutex.hpp:33
void swap(p &other)
Swaps two p objects of the same type.
Definition: p.hpp:141
The non-template pool base class.
Definition: pool.hpp:51
Custom pool error class.
Definition: pexceptions.hpp:84
Custom transaction error class.
Definition: pexceptions.hpp:167
Commonly used functionality.
A persistent version of thread-local storage.
Functions for lifetime management.
Pmem-resident mutex.
void allocator_move_assignment(MyAlloc &my_allocator, OtherAlloc &other_allocator, std::true_type)
Move assignment implementation for allocator if propagate_on_container_move_assignment == true_type.
Definition: concurrent_skip_list_impl.hpp:85
void allocator_copy_assignment(MyAlloc &my_allocator, OtherAlloc &other_allocator, std::true_type)
Copy assignment implementation for allocator if propagate_on_container_copy_assignment == true_type.
Definition: concurrent_skip_list_impl.hpp:63
void allocator_swap(MyAlloc &my_allocator, OtherAlloc &other_allocator, std::true_type)
Swap implementation for allocators if propagate_on_container_swap == true_type.
Definition: concurrent_skip_list_impl.hpp:107
Persistent memory namespace.
Definition: allocation_flag.hpp:15
Pair implementation.
Persistent smart pointer.
C++ pmemobj pool.
Persistent self-relative smart pointer.
Commonly used SFINAE helpers.
C++ pmemobj transactions.