arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Macros Modules Pages
ListLockFree.h
1#pragma once
2#ifdef USE_INITIALIZER_LIST
3# include "InitializerList.h"
4#endif
5#include <stddef.h>
6#include <atomic>
7#include <memory>
8#include "AudioTools/CoreAudio/AudioBasic/Collections/Allocator.h"
9
10namespace audio_tools {
11
24template <class T>
26 public:
27 struct Node {
28 std::atomic<Node*> next{nullptr};
29 std::atomic<Node*> prior{nullptr};
30 T data;
31
32 Node() = default;
33 Node(const T& value) : data(value) {}
34 };
35
36 class Iterator {
37 public:
38 Iterator(Node* node) : node(node) {}
39
40 inline Iterator operator++() {
41 if (node != nullptr) {
42 Node* next_node = node->next.load(std::memory_order_acquire);
43 if (next_node != nullptr && next_node != &owner->last) {
44 node = next_node;
45 is_eof = false;
46 } else {
47 is_eof = true;
48 }
49 }
50 return *this;
51 }
52
53 inline Iterator operator++(int) {
54 Iterator tmp = *this;
55 ++(*this);
56 return tmp;
57 }
58
59 inline Iterator operator--() {
60 if (node != nullptr) {
61 Node* prior_node = node->prior.load(std::memory_order_acquire);
62 if (prior_node != nullptr && prior_node != &owner->first) {
63 node = prior_node;
64 is_eof = false;
65 } else {
66 is_eof = true;
67 }
68 }
69 return *this;
70 }
71
72 inline Iterator operator--(int) {
73 Iterator tmp = *this;
74 --(*this);
75 return tmp;
76 }
77
78 inline Iterator operator+(int offset) {
79 return getIteratorAtOffset(offset);
80 }
81
82 inline Iterator operator-(int offset) {
83 return getIteratorAtOffset(-offset);
84 }
85
86 inline bool operator==(const Iterator& it) const {
87 return node == it.node;
88 }
89
90 inline bool operator!=(const Iterator& it) const {
91 return node != it.node;
92 }
93
94 inline T& operator*() {
95 return node->data;
96 }
97
98 inline T* operator->() {
99 return &(node->data);
100 }
101
102 inline Node* get_node() {
103 return node;
104 }
105
106 inline operator bool() const {
107 return !is_eof;
108 }
109
110 void set_owner(ListLockFree* owner_ptr) {
111 owner = owner_ptr;
112 }
113
114 protected:
115 Node* node = nullptr;
116 bool is_eof = false;
117 ListLockFree* owner = nullptr;
118
119 Iterator getIteratorAtOffset(int offset) {
120 Node* tmp = node;
121 if (offset > 0) {
122 for (int j = 0; j < offset && tmp != nullptr; j++) {
123 Node* next_node = tmp->next.load(std::memory_order_acquire);
124 if (next_node == nullptr || next_node == &owner->last) {
125 break;
126 }
127 tmp = next_node;
128 }
129 } else if (offset < 0) {
130 for (int j = 0; j < -offset && tmp != nullptr; j++) {
131 Node* prior_node = tmp->prior.load(std::memory_order_acquire);
132 if (prior_node == nullptr || prior_node == &owner->first) {
133 break;
134 }
135 tmp = prior_node;
136 }
137 }
138 Iterator it(tmp);
139 it.set_owner(owner);
140 return it;
141 }
142 };
143
145 ListLockFree(Allocator &allocator = DefaultAllocator) : p_allocator(&allocator) {
146 link();
147 }
148
150 ListLockFree(const ListLockFree& ref) : p_allocator(ref.p_allocator) {
151 link();
152 // Copy elements (this is not thread-safe for the source)
153 Node* current = ref.first.next.load(std::memory_order_acquire);
154 while (current != &ref.last) {
155 push_back(current->data);
156 current = current->next.load(std::memory_order_acquire);
157 }
158 }
159
161 template<size_t N>
162 ListLockFree(const T (&a)[N], Allocator &allocator = DefaultAllocator) : p_allocator(&allocator) {
163 link();
164 for(int i = 0; i < N; ++i) {
165 push_back(a[i]);
166 }
167 }
168
169 ~ListLockFree() {
170 clear();
171 }
172
173#ifdef USE_INITIALIZER_LIST
174 ListLockFree(std::initializer_list<T> iniList, Allocator &allocator = DefaultAllocator) : p_allocator(&allocator) {
175 link();
176 for(const auto &obj : iniList) {
177 push_back(obj);
178 }
179 }
180#endif
181
182 bool swap(ListLockFree<T>& ref) {
183 // For simplicity, this is not implemented as fully lock-free
184 // A full implementation would require complex atomic operations
185 return false;
186 }
187
188 bool push_back(const T& data) {
189 Node* node = createNode();
190 if (node == nullptr) return false;
191 node->data = data;
192
193 while (true) {
194 Node* old_last_prior = last.prior.load(std::memory_order_acquire);
195
196 // Try to link the new node
197 node->next.store(&last, std::memory_order_relaxed);
198 node->prior.store(old_last_prior, std::memory_order_relaxed);
199
200 // Atomically update the prior node's next pointer
201 Node* expected_next = &last;
202 if (old_last_prior->next.compare_exchange_weak(
203 expected_next, node, std::memory_order_release, std::memory_order_relaxed)) {
204
205 // Atomically update last's prior pointer
206 Node* expected_prior = old_last_prior;
207 if (last.prior.compare_exchange_weak(
208 expected_prior, node, std::memory_order_release, std::memory_order_relaxed)) {
209
210 record_count.fetch_add(1, std::memory_order_relaxed);
211 return true;
212 }
213
214 // Rollback the first change
215 old_last_prior->next.store(&last, std::memory_order_relaxed);
216 }
217 }
218 }
219
220 bool push_front(const T& data) {
221 Node* node = createNode();
222 if (node == nullptr) return false;
223 node->data = data;
224
225 while (true) {
226 Node* old_first_next = first.next.load(std::memory_order_acquire);
227
228 // Try to link the new node
229 node->prior.store(&first, std::memory_order_relaxed);
230 node->next.store(old_first_next, std::memory_order_relaxed);
231
232 // Atomically update the next node's prior pointer
233 Node* expected_prior = &first;
234 if (old_first_next->prior.compare_exchange_weak(
235 expected_prior, node, std::memory_order_release, std::memory_order_relaxed)) {
236
237 // Atomically update first's next pointer
238 Node* expected_next = old_first_next;
239 if (first.next.compare_exchange_weak(
240 expected_next, node, std::memory_order_release, std::memory_order_relaxed)) {
241
242 record_count.fetch_add(1, std::memory_order_relaxed);
243 return true;
244 }
245
246 // Rollback the first change
247 old_first_next->prior.store(&first, std::memory_order_relaxed);
248 }
249 }
250 }
251
252 bool insert(Iterator it, const T& data) {
253 Node* node = createNode();
254 if (node == nullptr) return false;
255 node->data = data;
256
257 Node* current_node = it.get_node();
258 if (current_node == nullptr) return false;
259
260 while (true) {
261 Node* prior = current_node->prior.load(std::memory_order_acquire);
262 if (prior == nullptr) return false;
263
264 // Set up new node links
265 node->prior.store(prior, std::memory_order_relaxed);
266 node->next.store(current_node, std::memory_order_relaxed);
267
268 // Try to atomically update the prior node's next pointer
269 Node* expected_next = current_node;
270 if (prior->next.compare_exchange_weak(
271 expected_next, node, std::memory_order_release, std::memory_order_relaxed)) {
272
273 // Try to atomically update current node's prior pointer
274 Node* expected_prior = prior;
275 if (current_node->prior.compare_exchange_weak(
276 expected_prior, node, std::memory_order_release, std::memory_order_relaxed)) {
277
278 record_count.fetch_add(1, std::memory_order_relaxed);
279 return true;
280 }
281
282 // Rollback the prior->next change
283 prior->next.store(current_node, std::memory_order_relaxed);
284 }
285 }
286 }
287
288 bool pop_front() {
289 T tmp;
290 return pop_front(tmp);
291 }
292
293 bool pop_back() {
294 T tmp;
295 return pop_back(tmp);
296 }
297
298 bool pop_front(T& data) {
299 while (true) {
300 Node* first_data = first.next.load(std::memory_order_acquire);
301 if (first_data == &last) return false; // Empty list
302
303 Node* next_node = first_data->next.load(std::memory_order_acquire);
304 data = first_data->data;
305
306 // Try to atomically update the links
307 if (first.next.compare_exchange_weak(
308 first_data, next_node, std::memory_order_release, std::memory_order_relaxed)) {
309
310 if (next_node->prior.compare_exchange_weak(
311 first_data, &first, std::memory_order_release, std::memory_order_relaxed)) {
312
313 deleteNode(first_data);
314 record_count.fetch_sub(1, std::memory_order_relaxed);
315 return true;
316 }
317
318 // Rollback
319 first.next.store(first_data, std::memory_order_relaxed);
320 }
321 }
322 }
323
324 bool pop_back(T& data) {
325 while (true) {
326 Node* last_data = last.prior.load(std::memory_order_acquire);
327 if (last_data == &first) return false; // Empty list
328
329 Node* prior_node = last_data->prior.load(std::memory_order_acquire);
330 data = last_data->data;
331
332 // Try to atomically update the links
333 if (last.prior.compare_exchange_weak(
334 last_data, prior_node, std::memory_order_release, std::memory_order_relaxed)) {
335
336 if (prior_node->next.compare_exchange_weak(
337 last_data, &last, std::memory_order_release, std::memory_order_relaxed)) {
338
339 deleteNode(last_data);
340 record_count.fetch_sub(1, std::memory_order_relaxed);
341 return true;
342 }
343
344 // Rollback
345 last.prior.store(last_data, std::memory_order_relaxed);
346 }
347 }
348 }
349
350 bool erase(Iterator it) {
351 Node* p_delete = it.get_node();
352 if (p_delete == nullptr || p_delete == &first || p_delete == &last) {
353 return false;
354 }
355
356 while (true) {
357 Node* prior_node = p_delete->prior.load(std::memory_order_acquire);
358 Node* next_node = p_delete->next.load(std::memory_order_acquire);
359
360 if (prior_node == nullptr || next_node == nullptr) return false;
361
362 // Try to atomically update the links
363 if (prior_node->next.compare_exchange_weak(
364 p_delete, next_node, std::memory_order_release, std::memory_order_relaxed)) {
365
366 if (next_node->prior.compare_exchange_weak(
367 p_delete, prior_node, std::memory_order_release, std::memory_order_relaxed)) {
368
369 deleteNode(p_delete);
370 record_count.fetch_sub(1, std::memory_order_relaxed);
371 return true;
372 }
373
374 // Rollback
375 prior_node->next.store(p_delete, std::memory_order_relaxed);
376 }
377 }
378 }
379
380 Iterator begin() {
381 Node* first_data = first.next.load(std::memory_order_acquire);
382 Iterator it(first_data == &last ? nullptr : first_data);
383 it.set_owner(this);
384 return it;
385 }
386
387 Iterator end() {
388 Iterator it(&last);
389 it.set_owner(this);
390 return it;
391 }
392
393 Iterator rbegin() {
394 Node* last_data = last.prior.load(std::memory_order_acquire);
395 Iterator it(last_data == &first ? nullptr : last_data);
396 it.set_owner(this);
397 return it;
398 }
399
400 Iterator rend() {
401 Iterator it(&first);
402 it.set_owner(this);
403 return it;
404 }
405
406 size_t size() {
407 return record_count.load(std::memory_order_relaxed);
408 }
409
410 bool empty() {
411 return size() == 0;
412 }
413
414 bool clear() {
415 while (pop_front()) {
416 // Keep removing elements
417 }
418 return true;
419 }
420
421 inline T& operator[](int index) {
422 // Note: This is not thread-safe and may give inconsistent results
423 // in highly concurrent scenarios
424 Node* n = first.next.load(std::memory_order_acquire);
425 for (int j = 0; j < index && n != &last; j++) {
426 n = n->next.load(std::memory_order_acquire);
427 if (n == nullptr) {
428 static T dummy{};
429 return dummy;
430 }
431 }
432 return n != &last ? n->data : last.data;
433 }
434
435 void setAllocator(Allocator& allocator) {
436 p_allocator = &allocator;
437 }
438
440 T& back() {
441 Node* last_data = last.prior.load(std::memory_order_acquire);
442 return last_data != &first ? last_data->data : last.data;
443 }
444
446 T& front() {
447 Node* first_data = first.next.load(std::memory_order_acquire);
448 return first_data != &last ? first_data->data : first.data;
449 }
450
451 protected:
452 Node first; // empty dummy first node
453 Node last; // empty dummy last node
454 std::atomic<size_t> record_count{0};
455 Allocator* p_allocator = &DefaultAllocator;
456
457 Node* createNode() {
458#if USE_ALLOCATOR
459 Node* node = (Node*) p_allocator->allocate(sizeof(Node));
460 if (node != nullptr) {
461 new (node) Node(); // Placement new to call constructor
462 }
463#else
464 Node* node = new Node();
465#endif
466 return node;
467 }
468
469 void deleteNode(Node* p_delete) {
470#if USE_ALLOCATOR
471 if (p_delete != nullptr) {
472 p_delete->~Node(); // Explicit destructor call
473 p_allocator->free(p_delete);
474 }
475#else
476 delete p_delete;
477#endif
478 }
479
480 void link() {
481 first.next.store(&last, std::memory_order_relaxed);
482 last.prior.store(&first, std::memory_order_relaxed);
483 }
484
485 friend class Iterator;
486};
487
488}
Memory allocateator which uses malloc.
Definition Allocator.h:23
Definition ListLockFree.h:36
Lock-free double linked list using atomic operations.
Definition ListLockFree.h:25
ListLockFree(const ListLockFree &ref)
Copy constructor (not thread-safe for the source list)
Definition ListLockFree.h:150
T & back()
Provides the last element.
Definition ListLockFree.h:440
ListLockFree(Allocator &allocator=DefaultAllocator)
Default constructor.
Definition ListLockFree.h:145
T & front()
Provides the first element.
Definition ListLockFree.h:446
ListLockFree(const T(&a)[N], Allocator &allocator=DefaultAllocator)
Constructor using array.
Definition ListLockFree.h:162
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
Definition ListLockFree.h:27