Skip to content

Commit daa9703

Browse files
authored
exec.split: implementation and test files (#81)
1 parent 8420165 commit daa9703

File tree

5 files changed

+706
-0
lines changed

5 files changed

+706
-0
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// include/beman/execution26/detail/intrusive_queue.hpp -*-C++-*-
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#ifndef INCLUDED_BEMAN_EXECUTION26_DETAIL_INTRUSIVE_QUEUE
5+
#define INCLUDED_BEMAN_EXECUTION26_DETAIL_INTRUSIVE_QUEUE
6+
7+
#include <cassert>
8+
#include <utility>
9+
10+
namespace beman::execution26::detail {
11+
12+
template <auto Next>
13+
class atomic_intrusive_stack;
14+
15+
template <auto Next>
16+
class intrusive_stack;
17+
18+
//! @brief This data structure is an intrusive queue that is not thread-safe.
19+
template <class Item, Item* Item::*Next>
20+
class intrusive_stack<Next> {
21+
public:
22+
//! @brief Pushes an item to the queue.
23+
auto push(Item* item) noexcept -> void { item->*Next = std::exchange(head_, item); }
24+
25+
//! @brief Pops one item from the queue.
26+
//!
27+
//! @return The item that was popped from the queue, or nullptr if the queue is empty.
28+
auto pop() noexcept -> Item* {
29+
if (head_) {
30+
auto item = head_;
31+
head_ = std::exchange(item->*Next, nullptr);
32+
return item;
33+
}
34+
return nullptr;
35+
}
36+
37+
//! @brief Tests if the queue is empty.
38+
auto empty() const noexcept -> bool { return !head_; }
39+
40+
private:
41+
friend class atomic_intrusive_stack<Next>;
42+
Item* head_{nullptr};
43+
};
44+
45+
} // namespace beman::execution26::detail
46+
47+
#endif

0 commit comments

Comments
 (0)