18 #ifndef CSLIBGUARDED_RCU_LIST_H
19 #define CSLIBGUARDED_RCU_LIST_H
25 #include <initializer_list>
51 template <
typename T,
typename M = std::mutex,
typename Alloc = std::allocator<T>>
56 using allocator_type = Alloc;
57 using size_type = std::ptrdiff_t;
58 using reference = value_type &;
59 using const_reference =
const value_type &;
60 using pointer =
typename std::allocator_traits<Alloc>::pointer;
61 using const_pointer =
typename std::allocator_traits<Alloc>::const_pointer;
65 class reverse_iterator;
66 class const_reverse_iterator;
68 class end_reverse_iterator;
71 using rcu_write_guard = rcu_guard;
72 using rcu_read_guard = rcu_guard;
75 explicit rcu_list(
const Alloc &alloc);
77 rcu_list(
const rcu_list &) =
delete;
78 rcu_list(rcu_list &&) =
delete;
80 rcu_list &operator=(
const rcu_list &) =
delete;
81 rcu_list &operator=(rcu_list &&) =
delete;
85 [[nodiscard]] iterator begin();
86 [[nodiscard]] end_iterator end();
87 [[nodiscard]] const_iterator begin()
const;
88 [[nodiscard]] end_iterator end()
const;
89 [[nodiscard]] const_iterator cbegin()
const;
90 [[nodiscard]] end_iterator cend()
const;
94 iterator insert(const_iterator pos, T value);
95 iterator insert(const_iterator pos, size_type count,
const T &value);
97 template <
typename InputIter>
98 iterator insert(const_iterator pos, InputIter first, InputIter last);
99 iterator insert(const_iterator pos, std::initializer_list<T> ilist);
101 template <
typename... Us>
102 iterator emplace(const_iterator pos, Us &&... vs);
104 void push_front(T value);
105 void push_back(T value);
107 template <
typename... Us>
108 void emplace_front(Us &&... vs);
110 template <
typename... Us>
111 void emplace_back(Us &&... vs);
113 iterator erase(const_iterator pos);
118 node(
const node &) =
delete;
119 node(node &&) =
delete;
121 node &operator=(
const node &) =
delete;
122 node &operator=(node &&) =
delete;
124 template <
typename... Us>
125 explicit node(Us &&... vs)
126 : data(std::forward<Us>(vs)...)
130 std::atomic<node *> next{
nullptr};
131 std::atomic<node *> back{
nullptr};
136 struct zombie_list_node {
137 zombie_list_node(node *n)
noexcept
142 zombie_list_node(rcu_guard *g)
noexcept
148 zombie_list_node(
const zombie_list_node &) =
delete;
149 zombie_list_node(zombie_list_node &&) =
delete;
151 zombie_list_node &operator=(
const zombie_list_node &) =
delete;
152 zombie_list_node &operator=(zombie_list_node &&) =
delete;
154 std::atomic<zombie_list_node *> next{
nullptr};
155 std::atomic<rcu_guard *> owner{
nullptr};
156 node *zombie_node{
nullptr};
159 using alloc_trait = std::allocator_traits<Alloc>;
160 using node_alloc_t =
typename alloc_trait::
template rebind_alloc<node>;
161 using node_alloc_trait = std::allocator_traits<node_alloc_t>;
162 using zombie_alloc_t =
typename alloc_trait::
template rebind_alloc<zombie_list_node>;
163 using zombie_alloc_trait = std::allocator_traits<zombie_alloc_t>;
165 std::atomic<node *> m_head{
nullptr};
166 std::atomic<node *> m_tail{
nullptr};
168 mutable std::atomic<zombie_list_node *> m_zombie_head{
nullptr};
172 mutable node_alloc_t m_node_alloc;
173 mutable zombie_alloc_t m_zombie_alloc;
182 template <
typename Alloc>
185 using allocator_type = Alloc;
186 using allocator_traits = std::allocator_traits<allocator_type>;
187 using pointer =
typename allocator_traits::pointer;
189 allocator_type m_alloc;
192 explicit deallocator(
const allocator_type &alloc)
noexcept
197 void operator()(pointer p) {
199 allocator_traits::destroy(m_alloc, p);
200 allocator_traits::deallocate(m_alloc, p, 1);
206 template <
typename T,
typename Alloc,
typename... Args>
207 std::unique_ptr<T, deallocator<Alloc>> allocate_unique(Alloc &alloc, Args &&... args)
209 using allocator_traits = std::allocator_traits<Alloc>;
211 auto p = allocator_traits::allocate(alloc, 1);
214 allocator_traits::construct(alloc, p, std::forward<Args>(args)...);
215 return {p, deallocator<Alloc>{alloc}};
218 allocator_traits::deallocate(alloc, p, 1);
227 template <
typename T,
typename M,
typename Alloc>
228 class rcu_list<T, M, Alloc>::rcu_guard
231 rcu_guard() =
default;
233 rcu_guard(
const rcu_guard &other) =
delete;
234 rcu_guard &operator=(
const rcu_guard &other) =
delete;
236 rcu_guard(rcu_guard &&other) {
237 m_zombie = other.m_zombie;
238 m_list = other.m_list;
240 other.m_zombie =
nullptr;
241 other.m_list =
nullptr;
244 rcu_guard &operator=(rcu_guard &&other) {
245 m_zombie = other.m_zombie;
246 m_list = other.m_list;
248 other.m_zombie =
nullptr;
249 other.m_list =
nullptr;
252 void rcu_read_lock(
const rcu_list<T, M, Alloc> &list);
253 void rcu_read_unlock(
const rcu_list<T, M, Alloc> &list);
255 void rcu_write_lock(rcu_list<T, M, Alloc> &list);
256 void rcu_write_unlock(rcu_list<T, M, Alloc> &list);
261 zombie_list_node *m_zombie;
262 const rcu_list<T, M, Alloc> *m_list;
265 template <
typename T,
typename M,
typename Alloc>
266 void rcu_list<T, M, Alloc>::rcu_guard::rcu_read_lock(
const rcu_list<T, M, Alloc> &list)
269 m_zombie = zombie_alloc_trait::allocate(list.m_zombie_alloc, 1);
270 zombie_alloc_trait::construct(list.m_zombie_alloc, m_zombie,
this);
271 zombie_list_node *oldNext = list.m_zombie_head.load(std::memory_order_relaxed);
274 m_zombie->next.store(oldNext, std::memory_order_relaxed);
275 }
while (!list.m_zombie_head.compare_exchange_weak(oldNext, m_zombie));
278 template <
typename T,
typename M,
typename Alloc>
279 void rcu_list<T, M, Alloc>::rcu_guard::rcu_read_unlock(
const rcu_list<T, M, Alloc> &)
284 template <
typename T,
typename M,
typename Alloc>
285 void rcu_list<T, M, Alloc>::rcu_guard::unlock()
287 zombie_list_node *cached_next = m_zombie->next.load();
288 zombie_list_node *n = cached_next;
293 if (n->owner.load() !=
nullptr) {
305 node *deadNode = n->zombie_node;
307 if (deadNode !=
nullptr) {
308 node_alloc_trait::destroy(m_list->m_node_alloc, deadNode);
309 node_alloc_trait::deallocate(m_list->m_node_alloc, deadNode, 1);
312 zombie_list_node *oldnode = n;
315 if (oldnode !=
nullptr) {
316 zombie_alloc_trait::destroy(m_list->m_zombie_alloc, oldnode);
317 zombie_alloc_trait::deallocate(m_list->m_zombie_alloc, oldnode, 1);
321 m_zombie->next.store(n);
324 m_zombie->owner.store(
nullptr);
327 template <
typename T,
typename M,
typename Alloc>
328 void rcu_list<T, M, Alloc>::rcu_guard::rcu_write_lock(rcu_list<T, M, Alloc> &list)
331 list.m_write_mutex.lock();
334 template <
typename T,
typename M,
typename Alloc>
335 void rcu_list<T, M, Alloc>::rcu_guard::rcu_write_unlock(rcu_list<T, M, Alloc> &list)
337 list.m_write_mutex.unlock();
338 rcu_read_unlock(list);
343 template <
typename T,
typename M,
typename Alloc>
344 class rcu_list<T, M, Alloc>::iterator
347 using iterator_category = std::forward_iterator_tag;
348 using value_type =
const T;
349 using pointer =
const T *;
350 using reference =
const T &;
351 using difference_type = size_t;
358 const T &operator*()
const {
359 return m_current->data;
362 const T *operator->()
const {
363 return &(m_current->data);
366 bool operator==(
const end_iterator &)
const {
367 return m_current ==
nullptr;
370 bool operator!=(
const end_iterator &)
const {
371 return m_current !=
nullptr;
374 iterator &operator++() {
375 m_current = m_current->next.load();
379 iterator &operator--() {
380 m_current = m_current->prev.load();
384 iterator operator++(
int) {
390 iterator operator--(
int) {
397 friend rcu_list<T, M, Alloc>;
398 friend rcu_list<T, M, Alloc>::const_iterator;
400 explicit iterator(
const typename rcu_list<T, M, Alloc>::const_iterator &it)
401 : m_current(it.m_current)
405 explicit iterator(node *n)
415 template <
typename T,
typename M,
typename Alloc>
416 class rcu_list<T, M, Alloc>::const_iterator
419 using iterator_category = std::forward_iterator_tag;
420 using value_type =
const T;
421 using pointer =
const T *;
422 using reference =
const T &;
423 using difference_type = size_t;
430 const_iterator(
const typename rcu_list<T, M, Alloc>::iterator &it)
431 : m_current(it.m_current)
435 const T &operator*()
const {
436 return m_current->data;
439 const T *operator->()
const {
440 return &(m_current->data);
443 bool operator==(
const end_iterator &)
const {
444 return m_current ==
nullptr;
447 bool operator!=(
const end_iterator &)
const {
448 return m_current !=
nullptr;
451 const_iterator &operator++() {
452 m_current = m_current->next.load();
456 const_iterator &operator--() {
457 m_current = m_current->prev.load();
461 const_iterator operator++(
int) {
462 const_iterator old(*
this);
467 const_iterator operator--(
int) {
468 const_iterator old(*
this);
474 friend rcu_list<T, M, Alloc>;
476 explicit const_iterator(node *n)
486 template <
typename T,
typename M,
typename Alloc>
487 class rcu_list<T, M, Alloc>::end_iterator
490 bool operator==(iterator iter)
const {
491 return iter == *
this;
494 bool operator!=(iterator iter)
const {
495 return iter != *
this;
498 bool operator==(const_iterator iter)
const {
499 return iter == *
this;
502 bool operator!=(const_iterator iter)
const {
503 return iter != *
this;
509 template <
typename T,
typename M,
typename Alloc>
510 rcu_list<T, M, Alloc>::rcu_list()
512 m_head.store(
nullptr);
513 m_tail.store(
nullptr);
516 template <
typename T,
typename M,
typename Alloc>
517 rcu_list<T, M, Alloc>::rcu_list(
const Alloc &alloc)
518 : m_node_alloc(alloc), m_zombie_alloc(alloc)
522 template <
typename T,
typename M,
typename Alloc>
523 rcu_list<T, M, Alloc>::~rcu_list()
525 node *n = m_head.load();
527 while (n !=
nullptr) {
531 if (current !=
nullptr) {
532 node_alloc_trait::destroy(m_node_alloc, current);
533 node_alloc_trait::deallocate(m_node_alloc, current, 1);
537 zombie_list_node *zn = m_zombie_head.load();
539 while (zn !=
nullptr && zn->owner.load() ==
nullptr) {
540 zombie_list_node *current = zn;
541 zn = zn->next.load();
543 if (current->zombie_node !=
nullptr) {
544 node_alloc_trait::destroy(m_node_alloc, current->zombie_node);
545 node_alloc_trait::deallocate(m_node_alloc, current->zombie_node, 1);
548 if (current !=
nullptr) {
549 zombie_alloc_trait::destroy(m_zombie_alloc, current);
550 zombie_alloc_trait::deallocate(m_zombie_alloc, current, 1);
555 template <
typename T,
typename M,
typename Alloc>
556 auto rcu_list<T, M, Alloc>::begin() -> iterator
558 return iterator(m_head.load());
561 template <
typename T,
typename M,
typename Alloc>
562 auto rcu_list<T, M, Alloc>::end() -> end_iterator
564 return end_iterator();
567 template <
typename T,
typename M,
typename Alloc>
568 auto rcu_list<T, M, Alloc>::begin()
const -> const_iterator
570 return const_iterator(m_head.load());
573 template <
typename T,
typename M,
typename Alloc>
574 auto rcu_list<T, M, Alloc>::end()
const -> end_iterator
576 return end_iterator();
579 template <
typename T,
typename M,
typename Alloc>
580 template <
typename... Us>
581 auto rcu_list<T, M, Alloc>::emplace(const_iterator iter, Us &&...vs) -> iterator
583 auto newNode = detail::allocate_unique<node>(m_node_alloc, std::forward<Us>(vs)...);
585 node *oldHead = m_head.load();
586 node *oldTail = m_tail.load();
588 if (oldHead ==
nullptr) {
590 m_head.store(newNode.get());
591 m_tail.store(newNode.get());
593 }
else if (oldHead == iter.m_current) {
595 newNode->next.store(oldHead);
596 oldHead->back.store(newNode.get());
597 m_head.store(newNode.get());
599 }
else if (oldTail == iter.m_current) {
601 newNode->back.store(oldTail);
602 oldTail->next.store(newNode.get());
603 m_tail.store(newNode.get());
607 node *oldBack = iter.m_current->back.load();
609 newNode->next.store(iter.m_current);
610 newNode->back.store(oldBack);
611 iter.m_current->back.store(newNode.get());
613 if (oldBack !=
nullptr) {
614 oldBack->next.store(newNode.get());
618 return iterator(newNode.release());
621 template <
typename T,
typename M,
typename Alloc>
622 void rcu_list<T, M, Alloc>::push_front(T data)
624 auto newNode = detail::allocate_unique<node>(m_node_alloc, std::move(data));
626 node *oldHead = m_head.load();
628 if (oldHead ==
nullptr) {
629 m_head.store(newNode.get());
630 m_tail.store(newNode.release());
632 newNode->next.store(oldHead);
633 oldHead->back.store(newNode.get());
634 m_head.store(newNode.release());
638 template <
typename T,
typename M,
typename Alloc>
639 template <
typename... Us>
640 void rcu_list<T, M, Alloc>::emplace_front(Us &&... vs)
642 auto newNode = detail::allocate_unique<node>(m_node_alloc, std::forward<Us>(vs)...);
644 node *oldHead = m_head.load();
646 if (oldHead ==
nullptr) {
647 m_head.store(newNode.get());
648 m_tail.store(newNode.release());
650 newNode->next.store(oldHead);
651 oldHead->back.store(newNode.get());
652 m_head.store(newNode.release());
656 template <
typename T,
typename M,
typename Alloc>
657 void rcu_list<T, M, Alloc>::push_back(T data)
659 auto newNode = detail::allocate_unique<node>(m_node_alloc, std::move(data));
661 node *oldTail = m_tail.load(std::memory_order_relaxed);
663 if (oldTail ==
nullptr) {
664 m_head.store(newNode.get());
665 m_tail.store(newNode.release());
667 newNode->back.store(oldTail);
668 oldTail->next.store(newNode.get());
669 m_tail.store(newNode.release());
673 template <
typename T,
typename M,
typename Alloc>
674 template <
typename... Us>
675 void rcu_list<T, M, Alloc>::emplace_back(Us &&... vs)
677 auto newNode = detail::allocate_unique<node>(m_node_alloc, std::forward<Us>(vs)...);
679 node *oldTail = m_tail.load(std::memory_order_relaxed);
681 if (oldTail ==
nullptr) {
682 m_head.store(newNode.get());
683 m_tail.store(newNode.release());
685 newNode->back.store(oldTail);
686 oldTail->next.store(newNode.get());
687 m_tail.store(newNode.release());
691 template <
typename T,
typename M,
typename Alloc>
692 auto rcu_list<T, M, Alloc>::erase(const_iterator iter) -> iterator
695 node *oldNext = iter.m_current->next.load();
697 if (! iter.m_current->deleted) {
698 iter.m_current->deleted =
true;
700 node *oldPrev = iter.m_current->back.load();
703 oldPrev->next.store(oldNext);
706 m_head.store(oldNext);
710 oldNext->back.store(oldPrev);
713 m_tail.store(oldPrev);
716 auto newZombie = zombie_alloc_trait::allocate(m_zombie_alloc, 1);
717 zombie_alloc_trait::construct(m_zombie_alloc, newZombie, iter.m_current);
719 zombie_list_node *oldZombie = m_zombie_head.load();
722 newZombie->next = oldZombie;
723 }
while (! m_zombie_head.compare_exchange_weak(oldZombie, newZombie));
726 return iterator(oldNext);
729 template <
typename T>
730 using SharedList = rcu_guarded<rcu_list<T>>;