DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
IterableQueueModel.hpp
Go to the documentation of this file.
1
17
18// @author Bo Hu (bhu@fb.com)
19// @author Jordan DeLong (delong.j@fb.com)
20
21// Modification by Roland Sipos and Florian Till Groetschla
22// for DUNE-DAQ software framework
23
24#ifndef DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_MODELS_ITERABLEQUEUEMODEL_HPP_
25#define DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_MODELS_ITERABLEQUEUEMODEL_HPP_
26
29
31
32#include "logging/Logging.hpp"
33
34#include <folly/lang/Align.h>
35
36#include <atomic>
37#include <cassert>
38#include <cstddef>
39#include <cstdlib>
40#include <cxxabi.h>
41#include <iomanip>
42#include <iostream>
43#include <limits>
44#include <memory>
45#include <mutex>
46#include <new>
47#include <stdexcept>
48#include <thread>
49#include <type_traits>
50#include <utility>
51
52#include <xmmintrin.h>
53
54#ifdef WITH_LIBNUMA_SUPPORT
55#include <numa.h>
56#endif
57
58namespace dunedaq {
59namespace datahandlinglibs {
60
70template<class T>
72{
73 typedef T value_type;
74
75 static constexpr bool expects_order = true;
76
79
80 // Default constructor
83 , numa_aware_(false)
84 , numa_node_(0)
88 , prefill_ready_(false)
89 , prefill_done_(false)
90 , size_(2)
91 , records_(static_cast<T*>(std::malloc(sizeof(T) * 2)))
92 , readIndex_(0)
93 , writeIndex_(0)
94 {}
95
96 // Constructor with alignment strategies
97 IterableQueueModel(std::size_t size, // size must be >= 2
98 bool numa_aware = false,
99 uint8_t numa_node = 0, // NOLINT (build/unsigned)
100 bool intrinsic_allocator = false,
101 std::size_t alignment_size = 0)
102 : LatencyBufferConcept<T>() // NOLINT(build/unsigned)
103 , numa_aware_(numa_aware)
104 , numa_node_(numa_node)
105 , intrinsic_allocator_(intrinsic_allocator)
106 , alignment_size_(alignment_size)
108 , prefill_ready_(false)
109 , prefill_done_(false)
110 , size_(size)
111 , readIndex_(0)
112 , writeIndex_(0)
113 {
114 assert(size >= 2);
115 allocate_memory(size, numa_aware, numa_node, intrinsic_allocator, alignment_size);
116
117 if (!records_) {
118 throw std::bad_alloc();
119 }
120#if 0
121 ptrlogger = std::thread([&](){
122 while(true) {
123 auto const currentRead = readIndex_.load(std::memory_order_relaxed);
124 auto const currentWrite = writeIndex_.load(std::memory_order_relaxed);
125 TLOG() << "BEG:" << std::hex << &records_[0] << " END:" << &records_[size] << std::dec
126 << " R:" << currentRead << " - W:" << currentWrite
127 << " OFLOW:" << overflow_ctr;
128 std::this_thread::sleep_for(std::chrono::milliseconds(100));
129 }
130 });
131#endif
132 }
133
134 // Destructor
136
137 // Free allocated memory that is different for alignment strategies and allocation policies
138 void free_memory();
139
140 // Allocate memory based on different alignment strategies and allocation policies
141 void allocate_memory(std::size_t size,
142 bool numa_aware /*= false*/,
143 uint8_t numa_node = 0, // NOLINT (build/unsigned)
144 bool intrinsic_allocator = false,
145 std::size_t alignment_size = 0);
146
147 void allocate_memory(std::size_t size) override { allocate_memory(size,false); }
148
149 // Task that fills up the LB.
150 void prefill_task();
151
152 // Issue fre-fill task
153 void force_pagefault();
154
155 // Write element into the queue
156 bool write(T&& record) override;
157
158 // Read element from a queue (move or copy the value at the front of the queue to given variable)
159 bool read(T& record) override;
160
161 // Pop element on front of queue
162 void popFront();
163
164 // Pop number of elements (X) from the front of the queue
165 void pop(std::size_t x);
166
167 // Returns true if the queue is empty
168 bool isEmpty() const;
169
170 // Returns true if write index reached read index
171 bool isFull() const;
172
173 // Returns a good-enough guess on current occupancy:
174 // * If called by consumer, then true size may be more (because producer may
175 // be adding items concurrently).
176 // * If called by producer, then true size may be less (because consumer may
177 // be removing items concurrently).
178 // * It is undefined to call this from any other thread.
179 std::size_t occupancy() const override;
180
181 // The size of the underlying buffer, not the amount of usable slots
182 std::size_t size() const { return size_; }
183
184 // Maximum number of items in the queue.
185 std::size_t capacity() const { return size_ - 1; }
186
187 // Gives a pointer to the current read index
188 const T* front() override;
189
190 // Gives a pointer to the last written element
191 const T* back() override;
192
193 // Gives a pointer to the first available slot of the queue
194 T* start_of_buffer() { return &records_[0]; }
195
196 // Gives a pointer to the last available slot of the queue
197 T* end_of_buffer() { return &records_[size_]; }
198
199 // Configures the model
200 void conf(const appmodel::LatencyBuffer* cfg) override;
201
202 // Unconfigures the model
203 void scrap(const appfwk::DAQModule::CommandData_t& /*cfg*/) override;
204
205 // Flushes the elements from the queue
206 void flush() override { pop(occupancy()); }
207
208 // Returns the current memory alignment size
209 std::size_t get_alignment_size() { return alignment_size_; }
210
211 // Iterator for elements in the queue
212 struct Iterator
213 {
214 using iterator_category = std::forward_iterator_tag;
215 using difference_type = std::ptrdiff_t;
216 using value_type = T;
217 using pointer = T*;
218 using reference = T&;
219
220 Iterator(IterableQueueModel<T>& queue, uint32_t index) // NOLINT(build/unsigned)
221 : m_queue(queue)
222 , m_index(index)
223 {}
224
225 reference operator*() const { return m_queue.records_[m_index]; }
226 pointer operator->() { return &m_queue.records_[m_index]; }
227 Iterator& operator++() // NOLINT(runtime/increment_decrement) :)
228 {
229 if (good()) {
230 m_index++;
231 if (m_index == m_queue.size_) {
232 m_index = 0;
233 }
234 }
235 if (!good()) {
236 m_index = std::numeric_limits<uint32_t>::max(); // NOLINT(build/unsigned)
237 }
238 return *this;
239 }
240 Iterator operator++(int amount) // NOLINT(runtime/increment_decrement) :)
241 {
242 Iterator tmp = *this;
243 for (int i = 0; i < amount; ++i) {
244 ++(*this);
245 }
246 return tmp;
247 }
248 friend bool operator==(const Iterator& a, const Iterator& b) { return a.m_index == b.m_index; }
249 friend bool operator!=(const Iterator& a, const Iterator& b) { return a.m_index != b.m_index; }
250
251 bool good()
252 {
253 auto const currentRead = m_queue.readIndex_.load(std::memory_order_relaxed);
254 auto const currentWrite = m_queue.writeIndex_.load(std::memory_order_relaxed);
255 return (*this != m_queue.end()) &&
256 ((m_index >= currentRead && m_index < currentWrite) ||
257 (m_index >= currentRead && currentWrite < currentRead) ||
258 (currentWrite < currentRead && m_index < currentRead && m_index < currentWrite));
259 }
260
261 uint32_t get_index() { return m_index; } // NOLINT(build/unsigned)
262
263 private:
265 uint32_t m_index; // NOLINT(build/unsigned)
266 };
267
269 {
270 auto const currentRead = readIndex_.load(std::memory_order_relaxed);
271 if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
272 // queue is empty
273 return end();
274 }
275 return Iterator(*this, currentRead);
276 }
277
279 {
280 return Iterator(*this, std::numeric_limits<uint32_t>::max()); // NOLINT(build/unsigned)
281 }
282
283protected:
284 virtual void generate_opmon_data() override;
285
286 // Hidden original write implementation with signature difference. Only used for pre-allocation
287 template<class... Args>
288 bool write_(Args&&... recordArgs);
289
290 // Counter for failed writes, due to the fact the queue is full
291 std::atomic<int> overflow_ctr{ 0 };
292
293 // NUMA awareness and aligned allocator usage configuration
295 uint8_t numa_node_; // NOLINT (build/unsigned)
297 std::size_t alignment_size_;
299
300 // Pre-fill and page-fault internal thread control
301 std::string prefiller_name_{"lbpfn"};
302 std::mutex prefill_mutex_;
303 std::condition_variable prefill_cv_;
306
307 // Ptr logger for debugging
308 std::thread ptrlogger;
309
310 // Underlying buffer with padding:
311 // * hardware_destructive_interference_size is set to 128.
312 // * (Assuming cache line size of 64, so we use a cache line pair size of 128)
313 char pad0_[folly::hardware_destructive_interference_size]; // NOLINT(runtime/arrays)
314 uint32_t size_; // NOLINT(build/unsigned)
316 alignas(
317 folly::hardware_destructive_interference_size) std::atomic<unsigned int> readIndex_; // NOLINT(build/unsigned)
318 alignas(
319 folly::hardware_destructive_interference_size) std::atomic<unsigned int> writeIndex_; // NOLINT(build/unsigned)
320 char pad1_[folly::hardware_destructive_interference_size - sizeof(writeIndex_)]; // NOLINT(runtime/arrays)
321};
322
323} // namespace datahandlinglibs
324} // namespace dunedaq
325
326// Declarations
328
329#endif // DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_MODELS_ITERABLEQUEUEMODEL_HPP_
#define TLOG(...)
Definition macro.hpp:22
Including Qt Headers.
Definition module.cpp:16
FELIX Initialization std::string initerror FELIX queue timed std::string queuename Unexpected chunk size
friend bool operator!=(const Iterator &a, const Iterator &b)
friend bool operator==(const Iterator &a, const Iterator &b)
Iterator(IterableQueueModel< T > &queue, uint32_t index)
char pad0_[folly::hardware_destructive_interference_size]
void flush() override
Flush all elements from the latency buffer.
std::size_t occupancy() const override
Occupancy of LB.
void allocate_memory(std::size_t size, bool numa_aware, uint8_t numa_node=0, bool intrinsic_allocator=false, std::size_t alignment_size=0)
const T * back() override
Get pointer to the back of the LB.
const T * front() override
Get pointer to the front of the LB.
char pad1_[folly::hardware_destructive_interference_size - sizeof(writeIndex_)]
IterableQueueModel(std::size_t size, bool numa_aware=false, uint8_t numa_node=0, bool intrinsic_allocator=false, std::size_t alignment_size=0)
bool write(T &&record) override
Move referenced object into LB.
void conf(const appmodel::LatencyBuffer *cfg) override
Configure the LB.
IterableQueueModel(const IterableQueueModel &)=delete
IterableQueueModel & operator=(const IterableQueueModel &)=delete
void pop(std::size_t x)
Pop specified amount of elements from LB.
void scrap(const appfwk::DAQModule::CommandData_t &) override
Unconfigure the LB.
bool read(T &record) override
Move object from LB to referenced.