LogIt++
Header-only, macro-first C++ logging library
Loading...
Searching...
No Matches
MpscRingAny.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_MPSCRINGANY_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_MPSCRINGANY_HPP_INCLUDED
4
5#include <atomic>
6#include <cstddef>
7#include <cstdint>
8#include <memory>
9#include <new>
10#include <type_traits>
11#include <utility>
12
13namespace logit { namespace detail {
14
19 template <class T>
21 static_assert(std::is_nothrow_move_constructible<T>::value &&
22 std::is_nothrow_move_assignable<T>::value &&
23 std::is_nothrow_destructible<T>::value,
24 "MpscRingAny requires no-throw move and destruction operations");
25
26 private:
28 struct Cell {
29 std::atomic<std::size_t> m_seq;
30 typename std::aligned_storage<sizeof(T), alignof(T)>::type m_storage;
31 };
32
33 public:
35 explicit MpscRingAny(std::size_t capacity)
36 : m_cap(capacity < 2 ? 2 : capacity),
37 m_cells(new Cell[m_cap]),
39 m_dequeue_pos(0) {
40 for (std::size_t i = 0; i < m_cap; ++i) {
41 m_cells[i].m_seq.store(i, std::memory_order_relaxed);
42 }
43 }
44
48 }
49
50 MpscRingAny(const MpscRingAny&) = delete;
52
54 MpscRingAny(MpscRingAny&& other) noexcept
55 : m_cap(other.m_cap),
56 m_cells(std::move(other.m_cells)),
57 m_enqueue_pos(other.m_enqueue_pos.load(std::memory_order_relaxed)),
58 m_dequeue_pos(other.m_dequeue_pos.load(std::memory_order_relaxed)) {
59 other.m_cap = 0;
60 other.m_enqueue_pos.store(0, std::memory_order_relaxed);
61 other.m_dequeue_pos.store(0, std::memory_order_relaxed);
62 }
63
65 MpscRingAny& operator=(MpscRingAny&& other) noexcept {
66 if (this != &other) {
68 m_cap = other.m_cap;
69 m_cells = std::move(other.m_cells);
70 m_enqueue_pos.store(other.m_enqueue_pos.load(std::memory_order_relaxed),
71 std::memory_order_relaxed);
72 m_dequeue_pos.store(other.m_dequeue_pos.load(std::memory_order_relaxed),
73 std::memory_order_relaxed);
74 other.m_cap = 0;
75 other.m_enqueue_pos.store(0, std::memory_order_relaxed);
76 other.m_dequeue_pos.store(0, std::memory_order_relaxed);
77 }
78 return *this;
79 }
80
82 std::size_t capacity() const noexcept { return m_cap; }
83
86 template <class U>
87 bool try_push(U&& v) noexcept {
88 static_assert(std::is_nothrow_constructible<T, U&&>::value,
89 "MpscRingAny::try_push requires no-throw construction; pass a movable value");
90 std::size_t pos = m_enqueue_pos.load(std::memory_order_relaxed);
91 for (;;) {
92 Cell& c = m_cells[pos % m_cap];
93 std::size_t seq = c.m_seq.load(std::memory_order_acquire);
94
95 // When free, seq == pos
96 std::intptr_t diff =
97 static_cast<std::intptr_t>(seq) - static_cast<std::intptr_t>(pos);
98
99 if (diff == 0) {
100 if (m_enqueue_pos.compare_exchange_weak(
101 pos, pos + 1,
102 std::memory_order_relaxed,
103 std::memory_order_relaxed)) {
104 // We own the cell; construct T in-place.
105 new (&c.m_storage) T(std::forward<U>(v));
106 // Publish element.
107 c.m_seq.store(pos + 1, std::memory_order_release);
108 return true;
109 }
110 // CAS failed; 'pos' updated, retry.
111 } else if (diff < 0) {
112 // Full.
113 return false;
114 } else {
115 // Another producer advanced 'pos'; reload.
116 pos = m_enqueue_pos.load(std::memory_order_relaxed);
117 }
118 }
119 }
120
123 bool try_pop(T& out) noexcept {
124 for (;;) {
125 std::size_t pos = m_dequeue_pos.load(std::memory_order_relaxed);
126 Cell& c = m_cells[pos % m_cap];
127 std::size_t seq = c.m_seq.load(std::memory_order_acquire);
128
129 // When ready, seq == pos + 1
130 std::intptr_t diff =
131 static_cast<std::intptr_t>(seq) - static_cast<std::intptr_t>(pos + 1);
132
133 if (diff == 0) {
134 if (!m_dequeue_pos.compare_exchange_weak(
135 pos, pos + 1,
136 std::memory_order_relaxed,
137 std::memory_order_relaxed)) {
138 // Spurious failure: retry until we own the slot.
139 continue;
140 }
141
142 T* p = reinterpret_cast<T*>(&c.m_storage);
143 out = std::move(*p);
144 p->~T();
145
146 // Mark cell free for next cycle.
147 c.m_seq.store(pos + m_cap, std::memory_order_release);
148 return true;
149 }
150
151 return false; // Empty or not yet published.
152 }
153 }
154
156 bool empty() const noexcept {
157 if (!m_cells || m_cap == 0) {
158 return true;
159 }
160 std::size_t pos = m_dequeue_pos.load(std::memory_order_acquire);
161 const Cell& c = m_cells[pos % m_cap];
162 std::size_t seq = c.m_seq.load(std::memory_order_acquire);
163 std::intptr_t diff =
164 static_cast<std::intptr_t>(seq) - static_cast<std::intptr_t>(pos + 1);
165 return diff != 0;
166 }
167
168 private:
169 void destroy_all_() noexcept {
170 if (!m_cells || m_cap == 0) {
171 return;
172 }
173
174 // Destruction is called only after producers and the consumer have
175 // stopped. Inspect the outstanding sequence range and destroy
176 // live objects in place; this keeps the ring usable with move-only
177 // or non-default-constructible payload types.
178 const std::size_t begin = m_dequeue_pos.load(std::memory_order_relaxed);
179 const std::size_t end = m_enqueue_pos.load(std::memory_order_relaxed);
180 for (std::size_t pos = begin; pos != end; ++pos) {
181 Cell& c = m_cells[pos % m_cap];
182 const std::size_t seq = c.m_seq.load(std::memory_order_relaxed);
183 if (seq == pos + 1) {
184 reinterpret_cast<T*>(&c.m_storage)->~T();
185 }
186 }
187 }
188
189 std::size_t m_cap;
190 std::unique_ptr<Cell[]> m_cells;
191 alignas(64) std::atomic<std::size_t> m_enqueue_pos;
192 alignas(64) std::atomic<std::size_t> m_dequeue_pos;
193 };
194
195}} // namespace logit::detail
196
197#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_MPSCRINGANY_HPP_INCLUDED
Bounded MPSC ring buffer with arbitrary capacity (C++11).
MpscRingAny & operator=(MpscRingAny &&other) noexcept
Move assign ring, draining existing payload.
bool empty() const noexcept
Lightweight emptiness check for current consumer position.
std::atomic< std::size_t > m_enqueue_pos
bool try_pop(T &out) noexcept
Try to dequeue value into out.
bool try_push(U &&v) noexcept
Try to enqueue value.
MpscRingAny(const MpscRingAny &)=delete
std::atomic< std::size_t > m_dequeue_pos
MpscRingAny & operator=(const MpscRingAny &)=delete
std::unique_ptr< Cell[]> m_cells
MpscRingAny(std::size_t capacity)
Construct ring with given capacity (>= 2).
~MpscRingAny()
Destroy remaining elements, if any.
MpscRingAny(MpscRingAny &&other) noexcept
Move construct ring, adopting producer/consumer indices.
std::size_t capacity() const noexcept
Capacity of the ring.
The primary namespace for the LogIt++ library.
Single cell storing sequence number and raw storage for T.
std::aligned_storage< sizeof(T), alignof(T)>::type m_storage
std::atomic< std::size_t > m_seq