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>>;