1+ // include/beman/execution26/detail/atomic_intrusive_stack.hpp -*-C++-*-
2+ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+ #ifndef INCLUDED_BEMAN_EXECUTION26_DETAIL_ATOMIC_INTRUSIVE_STACK
5+ #define INCLUDED_BEMAN_EXECUTION26_DETAIL_ATOMIC_INTRUSIVE_STACK
6+
7+ #include < beman/execution26/detail/intrusive_stack.hpp>
8+
9+ #include < atomic>
10+ #include < cassert>
11+ #include < optional>
12+
13+ namespace beman ::execution26::detail {
14+
15+ template <auto Next>
16+ class atomic_intrusive_stack ;
17+
18+ // ! @brief This data structure is an intrusive stack that can be used in a lock-free manner.
19+ // !
20+ // ! The stack is implemented as a singly linked list where the head is an atomic pointer to the first item in
21+ // ! the stack.
22+ // ! try_push() is a lock-free operation that tries to push an item to the stack. If the stack is empty, it
23+ // ! returns nullptr and the item is pushed to the stack.
24+ // ! This stack has a closed state, which is indicated by the head pointing to the stack itself. In this state,
25+ // ! try_push() returns std::nullopt and the stack is not modified.
26+ // ! pop_all_and_shutdown() is a lock-free operation that pops all items from the stack and returns them in a
27+ // ! queue. If the stack is empty, it returns an empty queue.
28+ // !
29+ // ! We use this stack in the split implementation to store the listeners that are waiting for the operation to
30+ // ! complete.
31+ // !
32+ // ! @tparam Item The type of the item in the stack.
33+ // ! @tparam Next The pointer to the next item in the stack.
34+ template <class Item , Item* Item::*Next>
35+ class atomic_intrusive_stack <Next> {
36+ public:
37+ atomic_intrusive_stack () = default ;
38+ ~atomic_intrusive_stack () { assert (!head_ || head_ == this ); }
39+ atomic_intrusive_stack (const atomic_intrusive_stack&) = delete ;
40+ auto operator =(const atomic_intrusive_stack&) -> atomic_intrusive_stack& = delete ;
41+ atomic_intrusive_stack (atomic_intrusive_stack&&) noexcept = delete ;
42+ auto operator =(atomic_intrusive_stack&&) noexcept -> atomic_intrusive_stack& = delete ;
43+
44+ // ! @brief Tries to push an item to the stack.
45+ // !
46+ // ! @param item The item to push to the stack.
47+ // !
48+ // ! @return If the stack is empty, returns nullptr and pushes the item to the stack.
49+ // ! If the stack is in the closed state, returns std::nullopt.
50+ auto try_push (Item* item) noexcept -> std::optional<Item*> {
51+ void * ptr = head_.load ();
52+ if (ptr == this ) {
53+ return std::nullopt ;
54+ }
55+ item->*Next = static_cast <Item*>(ptr);
56+ while (!head_.compare_exchange_weak (ptr, item)) {
57+ if (ptr == this ) {
58+ return std::nullopt ;
59+ }
60+ item->*Next = static_cast <Item*>(ptr);
61+ }
62+ return static_cast <Item*>(ptr);
63+ }
64+
65+ // ! @brief Tests if the stack is empty and not in the closed state.
66+ auto empty_and_not_shutdown () const noexcept -> bool { return head_.load () == nullptr ; }
67+
68+ // ! @brief Pops all items from the stack, returns them and puts this stack into the closed state.
69+ // !
70+ // ! @return If the stack is empty, returns an empty stack.
71+ auto pop_all_and_shutdown () noexcept -> ::beman::execution26::detail::intrusive_stack<Next> {
72+ auto stack = ::beman::execution26::detail::intrusive_stack<Next>{};
73+ void * ptr = head_.exchange (this );
74+ if (ptr == this ) {
75+ return stack;
76+ }
77+ auto item = static_cast <Item*>(ptr);
78+ stack.head_ = item;
79+ return stack;
80+ }
81+
82+ private:
83+ ::std::atomic<void *> head_{nullptr };
84+ };
85+
86+ } // namespace beman::execution26::detail
87+
88+ #endif
0 commit comments