btllib

order_queue.hpp
1#ifndef BTLLIB_ORDER_QUEUE_HPP
2#define BTLLIB_ORDER_QUEUE_HPP
3
4#include <algorithm>
5#include <atomic>
6#include <condition_variable>
7#include <mutex>
8#include <string>
9#include <utility>
10#include <vector>
11
12 namespace btllib {
13
14 template<typename T>
15 class OrderQueue
16{
17
18 public:
19 struct Block
20 {
21
22 Block(const size_t block_size)
23 : data(block_size)
24 {}
25
26 Block(const Block& block) = default;
27
28 Block(Block&& block) noexcept
29 : current(block.current)
30 , count(block.count)
31 , num(block.num)
32 {
33 std::swap(data, block.data);
34 block.current = 0;
35 block.count = 0;
36 block.num = 0;
37 }
38
39 Block& operator=(const Block& block) = default;
40
41 Block& operator=(Block&& block) noexcept
42 {
43 std::swap(data, block.data);
44 current = block.current;
45 count = block.count;
46 num = block.num;
47 block.current = 0;
48 block.count = 0;
49 block.num = 0;
50 return *this;
51 }
52
53 std::vector<T> data;
54 size_t current = 0;
55 size_t count = 0;
56 size_t num = 0;
57 };
58
59 // Surrounds pieces of data in the buffer with a busy mutex
60 // for exclusive access
61 struct Slot
62 {
63 Slot(size_t block_size)
64 : block(block_size)
65 {}
66 Slot(const Slot& slot)
67 : block(slot.block)
68 , occupied(slot.occupied)
69 , last_tenant(slot.last_tenant)
70 {}
71 Slot(Slot&& slot) noexcept
72 : block(slot.block)
73 , occupied(slot.occupied)
74 , last_tenant(slot.last_tenant)
75 {}
76
77 Slot& operator=(const Slot& slot)
78 {
79 if (this == &slot) {
80 return *this;
81 }
82 block = slot.block;
83 occupied = slot.occupied;
84 last_tenant = slot.last_tenant;
85 return *this;
86 }
87 Slot& operator=(Slot&& slot) noexcept
88 {
89 block = slot.block;
90 occupied = slot.occupied;
91 last_tenant = slot.last_tenant;
92 return *this;
93 }
94
95 typename OrderQueue<T>::Block block;
96 std::mutex busy;
97 bool occupied = false;
98 std::condition_variable occupancy_changed;
99 size_t last_tenant = -1; // Required to ensure read order
100 };
101
102 size_t elements() const { return element_count; }
103
104 void close()
105 {
106 bool closed_expected = false;
107 if (closed.compare_exchange_strong(closed_expected, true)) {
108 for (auto& slot : this->slots) {
109 std::unique_lock<std::mutex> busy_lock(slot.busy);
110 slot.occupancy_changed.notify_all();
111 }
112 }
113 }
114
115 bool is_closed() const { return closed; }
116
117 OrderQueue(const size_t queue_size, const size_t block_size)
118 : slots(queue_size, Slot(block_size))
119 , queue_size(queue_size)
120 , block_size(block_size)
121 {}
122
123 OrderQueue(const OrderQueue&) = delete;
124 OrderQueue(OrderQueue&&) = delete;
125
126 protected:
127 std::vector<Slot> slots;
128 size_t queue_size, block_size;
129 size_t read_counter = 0;
130 std::atomic<size_t> element_count{ 0 };
131 std::atomic<bool> closed{ false };
132};
133
134#define ORDER_QUEUE_XPXC(SUFFIX, \
135 PRE_WRITE_LOCK, \
136 EXTRA_WRITE_LOCK_CONDS, \
137 POST_WRITE_LOCK, \
138 NOTIFY_WRITE, \
139 PRE_READ_LOCK, \
140 EXTRA_READ_LOCK_CONDS, \
141 POST_READ_LOCK, \
142 NOTIFY_READ, \
143 MEMBERS) \
144 template<typename T> \
145 class OrderQueue##SUFFIX : public OrderQueue<T> \
146 { \
147 \
148 public: \
149 OrderQueue##SUFFIX(const size_t queue_size, const size_t block_size) \
150 : OrderQueue<T>(queue_size, block_size) \
151 {} \
152 \
153 using Block = typename OrderQueue<T>::Block; \
154 using Slot = typename OrderQueue<T>::Slot; \
155 \
156 void write(Block& block) \
157 { \
158 PRE_WRITE_LOCK; \
159 const auto num = block.num; \
160 auto& target = this->slots[num % this->queue_size]; \
161 std::unique_lock<std::mutex> busy_lock(target.busy); \
162 target.occupancy_changed.wait(busy_lock, [&] { \
163 return (!target.occupied EXTRA_WRITE_LOCK_CONDS) || this->closed; \
164 }); \
165 if (this->closed) { \
166 return; \
167 } \
168 POST_WRITE_LOCK; /* NOLINT */ \
169 target.block = std::move(block); \
170 target.occupied = true; \
171 target.occupancy_changed.NOTIFY_WRITE(); \
172 ++(this->element_count); \
173 } \
174 \
175 void read(Block& block) \
176 { \
177 PRE_READ_LOCK; \
178 auto& target = this->slots[this->read_counter % this->queue_size]; \
179 std::unique_lock<std::mutex> busy_lock(target.busy); \
180 target.occupancy_changed.wait(busy_lock, [&] { \
181 return (target.occupied EXTRA_READ_LOCK_CONDS) || this->closed; \
182 }); \
183 if (this->closed) { \
184 return; \
185 } \
186 ++(this->read_counter); \
187 POST_READ_LOCK; \
188 block = std::move(target.block); \
189 target.occupied = false; \
190 target.occupancy_changed.NOTIFY_READ(); \
191 --(this->element_count); \
192 } \
193 \
194 private: \
195 MEMBERS /* NOLINT */ \
196 };
197
198ORDER_QUEUE_XPXC(SPSC, , , , notify_one, , , , notify_one, )
199ORDER_QUEUE_XPXC(MPSC,
200 ,
201 &&(num - target.last_tenant <= this->queue_size),
202 target.last_tenant = num,
203 notify_all,
204 ,
205 ,
206 ,
207 notify_all, )
208ORDER_QUEUE_XPXC(SPMC,
209 ,
210 ,
211 ,
212 notify_one,
213 std::unique_lock<std::mutex> read_lock(read_mutex),
214 ,
215 read_lock.unlock(),
216 notify_one,
217 std::mutex read_mutex;)
218ORDER_QUEUE_XPXC(MPMC,
219 ,
220 &&(num - target.last_tenant <= this->queue_size),
221 target.last_tenant = num,
222 notify_all,
223 std::unique_lock<std::mutex> read_lock(read_mutex),
224 ,
225 read_lock.unlock(),
226 notify_all,
227 std::mutex read_mutex;)
228
229#undef ORDER_QUEUE_XPXC
230
231} // namespace btllib
232
233#endif
btllib::OrderQueue
Definition: order_queue.hpp:16
btllib
Definition: bloom_filter.hpp:18
btllib::OrderQueue::Block
Definition: order_queue.hpp:20