Library of Assembled Shared Sources
lock_free_spmc_ring_buffer.inl
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2023 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
44
45namespace lass
46{
47namespace stde
48{
49namespace impl
50{
51
52// --- lock_free_spmc_ring_buffer_base -------------------------------------------------------------
53
54inline lock_free_spmc_ring_buffer_base::lock_free_spmc_ring_buffer_base(size_t capacity):
55 head_(tagged_index_t(0, 0)),
56 tail_(tagged_index_t(0, 0)),
57 ring_size_(static_cast<index_type>(enforce_valid_size(capacity)))
58{
59#if defined(__cpp_lib_atomic_is_always_lock_free)
60 static_assert(std::atomic<tagged_index_t>::is_always_lock_free);
61#else
62 LASS_ENFORCE(head_.is_lock_free());
63 LASS_ENFORCE(tail_.is_lock_free());
64#endif
65}
66
67
68/** Return true if ring buffer is empty
69 */
70inline bool lock_free_spmc_ring_buffer_base::empty() const
71{
72 return tagged_index_t::empty(head_.load(std::memory_order_acquire), tail_.load(std::memory_order_acquire));
73}
74
75
76
77/** Return true if ring buffer is empty
78 */
79inline bool lock_free_spmc_ring_buffer_base::full() const
80{
81 return tagged_index_t::full(head_.load(std::memory_order_acquire), tail_.load(std::memory_order_acquire));
82}
83
84
85
86lock_free_spmc_ring_buffer_base::tagged_index_t
87inline lock_free_spmc_ring_buffer_base::next_index(tagged_index_t index) const
88{
89 const index_type i = ++index.index;
90 return i < ring_size_
91 ? tagged_index_t(i, index.tag)
92 : tagged_index_t(0, index.tag + 1);
93}
94
95
96
97inline size_t lock_free_spmc_ring_buffer_base::enforce_valid_size(size_t size)
98{
99 if (size == 0)
100 {
101 throw std::length_error("ring buffer capacitance cannot be zero");
102 }
103 if (size > num::NumTraits<index_type>::max)
104 {
105 throw std::length_error("exceeded maximum ring buffer capacitance");
106 }
107 return size;
108}
109
110
111
112// --- lock_free_spmc_value_ring_buffer ------------------------------------------------------------
113
114template <typename T>
115lock_free_spmc_value_ring_buffer<T>::lock_free_spmc_value_ring_buffer(size_t capacity):
116 lock_free_spmc_ring_buffer_base(capacity),
117 ring_(capacity)
118{
119}
120
121
122
123/** Try to push a value x on the front.
124 * @return false if buffer was full and x could not be pushed.
125 */
126template <typename T>
127bool lock_free_spmc_value_ring_buffer<T>::try_push(const value_type& x)
128{
129 const tagged_index_t head = head_.load(std::memory_order_relaxed);
130 const tagged_index_t tail = tail_.load(std::memory_order_acquire);
131 if (tagged_index_t::full(head, tail))
132 {
133 LASS_ASSERT(head.tag == tail.tag + 1);
134 return false;
135 }
136 ring_[head.index].store(x, std::memory_order_release);
137 head_.store(next_index(head), std::memory_order_release);
138 return true;
139}
140
141
142
143/** Try to push a value x on the front.
144 * @return false if buffer was full and x could not be pushed.
145 */
146template <typename T>
147bool lock_free_spmc_value_ring_buffer<T>::try_push(value_type&& x)
148{
149 const tagged_index_t head = head_.load(std::memory_order_relaxed);
150 const tagged_index_t tail = tail_.load(std::memory_order_acquire);
151 if (tagged_index_t::full(head, tail))
152 {
153 LASS_ASSERT(head.tag == tail.tag + 1);
154 return false;
155 }
156 ring_[head.index].store(std::move(x), std::memory_order_release);
157 head_.store(next_index(head), std::memory_order_release);
158 return true;
159}
160
161
162
163/** Try to emplace a value on the front.
164 * @return false if buffer was full and value could not be pushed.
165 */
166template <typename T>
167template <class... Args>
168bool lock_free_spmc_value_ring_buffer<T>::try_emplace(Args&&... args)
169{
170 const tagged_index_t head = head_.load(std::memory_order_relaxed);
171 const tagged_index_t tail = tail_.load(std::memory_order_acquire);
172 if (tagged_index_t::full(head, tail))
173 {
174 LASS_ASSERT(head.tag == tail.tag + 1);
175 return false;
176 }
177 ring_[head.index].store(value_type(std::forward<Args...>(args...)), std::memory_order_release);
178 head_.store(next_index(head), std::memory_order_release);
179 return true;
180}
181
182
183
184/** Try to pop a value from the back and store it in x.
185 * @return false if buffer was empty and no element could be popped.
186 */
187template <typename T>
188bool lock_free_spmc_value_ring_buffer<T>::try_pop(value_type& x)
189{
190 tagged_index_t tail = tail_.load(std::memory_order_acquire);
191 while (true)
192 {
193 const tagged_index_t head = head_.load(std::memory_order_acquire);
194 if (tagged_index_t::empty(head, tail))
195 {
196 return false;
197 }
198 x = ring_[tail.index].load(std::memory_order_acquire);
199 if (tail_.compare_exchange_weak(tail, next_index(tail)))
200 {
201 return true;
202 }
203 }
204}
205
206
207
208// --- lock_free_spmc_object_ring_buffer ----------------------------------------------------------
209
210template <typename T, typename A>
211lock_free_spmc_object_ring_buffer<T, A>::lock_free_spmc_object_ring_buffer(size_t capacity):
212 lock_free_spmc_ring_buffer_base(capacity),
213 value_allocator_(sizeof(T)),
214 ring_(capacity)
215{
216}
217
218
219
220template <typename T, typename A>
221lock_free_spmc_object_ring_buffer<T, A>::~lock_free_spmc_object_ring_buffer()
222{
223 const tagged_index_t head = head_.load(std::memory_order_acquire);
224 const tagged_index_t tail = tail_.load(std::memory_order_acquire);
225 for (tagged_index_t i = tail; !tagged_index_t::empty(i, head); i = next_index(i))
226 {
227 value_type* p = ring_[i.index].load(std::memory_order_relaxed);
228 p->~value_type();
229 value_allocator_.deallocate(p);
230 }
231}
232
233
234
235/** Try to push a value x on the front.
236 * @return false if buffer was full and x could not be pushed.
237 */
238template <typename T, typename A>
239bool lock_free_spmc_object_ring_buffer<T, A>::try_push(const value_type& x)
240{
241 const tagged_index_t head = head_.load(std::memory_order_relaxed);
242 const tagged_index_t tail = tail_.load(std::memory_order_acquire);
243 if (tagged_index_t::full(head, tail))
244 {
245 return false;
246 }
247 void* p = value_allocator_.allocate();
248 try
249 {
250 ring_[head.index] = new (p) value_type(x);
251 }
252 catch (...)
253 {
254 value_allocator_.deallocate(p);
255 throw;
256 }
257 head_.store(next_index(head), std::memory_order_release);
258 return true;
259}
260
261
262
263/** Try to push a value x on the front.
264 * @return false if buffer was full and x could not be pushed.
265 */
266template <typename T, typename A>
267bool lock_free_spmc_object_ring_buffer<T, A>::try_push(value_type&& x)
268{
269 const tagged_index_t head = head_.load(std::memory_order_relaxed);
270 const tagged_index_t tail = tail_.load(std::memory_order_acquire);
271 if (tagged_index_t::full(head, tail))
272 {
273 return false;
274 }
275 void* p = value_allocator_.allocate();
276 try
277 {
278 ring_[head.index] = new (p) value_type(std::move(x));
279 }
280 catch (...)
281 {
282 value_allocator_.deallocate(p);
283 throw;
284 }
285 head_.store(next_index(head), std::memory_order_release);
286 return true;
287}
288
289
290
291/** Try to push a value x on the front.
292 * @return false if buffer was full and x could not be pushed.
293 */
294template <typename T, typename A>
295template <class... Args>
296bool lock_free_spmc_object_ring_buffer<T, A>::try_emplace(Args&&... args)
297{
298 const tagged_index_t head = head_.load(std::memory_order_relaxed);
299 const tagged_index_t tail = tail_.load(std::memory_order_acquire);
300 if (tagged_index_t::full(head, tail))
301 {
302 return false;
303 }
304 void* p = value_allocator_.allocate();
305 try
306 {
307 ring_[head.index] = new (p) value_type(std::forward<Args...>(args...));
308 }
309 catch (...)
310 {
311 value_allocator_.deallocate(p);
312 throw;
313 }
314 head_.store(next_index(head), std::memory_order_release);
315 return true;
316}
317
318
319/** Try to pop a value from the back and store it in x.
320 * @return false if buffer was empty and no element could be popped.
321 */
322template <typename T, typename A>
323bool lock_free_spmc_object_ring_buffer<T, A>::try_pop(value_type& x)
324{
325 tagged_index_t tail = tail_.load(std::memory_order_acquire);
326 while (true)
327 {
328 const tagged_index_t head = head_.load(std::memory_order_acquire);
329 if (tagged_index_t::empty(head, tail))
330 {
331 return false;
332 }
333 value_type* p = ring_[tail.index];
334 if (tail_.compare_exchange_weak(tail, next_index(tail)))
335 {
336 x = std::move(*p);
337 p->~value_type();
338 value_allocator_.deallocate(p);
339 return true;
340 }
341 }
342}
343
344
345}
346}
347}
348
349// EOF
lass extensions to the standard library
Library for Assembled Shared Sources.
Definition config.h:53