arduino-audio-tools
Loading...
Searching...
No Matches
ListLockFree.h
Go to the documentation of this file.
1#pragma once
2#ifdef USE_INITIALIZER_LIST
3# include "InitializerList.h"
4#endif
5#include <stddef.h>
6#include <atomic>
7#include <memory>
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};
31
32 Node() = default;
33 Node(const T& value) : data(value) {}
34 };
35
36 class Iterator {
37 public:
39
41 if (node != nullptr && owner != 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 // Advance onto the sentinel itself so that
48 // `it != owner->end()` (which compares by node
49 // pointer) actually becomes false - otherwise a
50 // for(it=begin(); it!=end(); ++it) loop never
51 // terminates once it reaches the last element.
52 node = &owner->last;
53 is_eof = true;
54 }
55 } else {
56 is_eof = true;
57 }
58 return *this;
59 }
60
61 inline Iterator operator++(int) {
62 Iterator tmp = *this;
63 ++(*this);
64 return tmp;
65 }
66
68 if (node != nullptr && owner != nullptr) {
69 Node* prior_node = node->prior.load(std::memory_order_acquire);
70 if (prior_node != nullptr && prior_node != &owner->first) {
71 node = prior_node;
72 is_eof = false;
73 } else {
74 // See operator++(): land on the sentinel itself
75 // so reverse iteration against rend() actually
76 // terminates instead of looping forever.
77 node = &owner->first;
78 is_eof = true;
79 }
80 } else {
81 is_eof = true;
82 }
83 return *this;
84 }
85
86 inline Iterator operator--(int) {
87 Iterator tmp = *this;
88 --(*this);
89 return tmp;
90 }
91
92 inline Iterator operator+(int offset) {
93 return getIteratorAtOffset(offset);
94 }
95
96 inline Iterator operator-(int offset) {
97 return getIteratorAtOffset(-offset);
98 }
99
100 inline bool operator==(const Iterator& it) const {
101 return node == it.node;
102 }
103
104 inline bool operator!=(const Iterator& it) const {
105 return node != it.node;
106 }
107
108 inline T& operator*() {
109 return node->data;
110 }
111
112 inline T* operator->() {
113 return &(node->data);
114 }
115
116 inline Node* get_node() {
117 return node;
118 }
119
120 inline operator bool() const {
121 return !is_eof;
122 }
123
124 void set_owner(ListLockFree* owner_ptr) {
125 owner = owner_ptr;
126 }
127
128 protected:
129 Node* node = nullptr;
130 bool is_eof = false;
131 ListLockFree* owner = nullptr;
132
134 Node* tmp = node;
135 if (owner != nullptr) {
136 if (offset > 0) {
137 for (int j = 0; j < offset && tmp != nullptr; j++) {
138 Node* next_node = tmp->next.load(std::memory_order_acquire);
139 if (next_node == nullptr || next_node == &owner->last) {
140 break;
141 }
142 tmp = next_node;
143 }
144 } else if (offset < 0) {
145 for (int j = 0; j < -offset && tmp != nullptr; j++) {
146 Node* prior_node = tmp->prior.load(std::memory_order_acquire);
147 if (prior_node == nullptr || prior_node == &owner->first) {
148 break;
149 }
150 tmp = prior_node;
151 }
152 }
153 }
154 Iterator it(tmp);
155 it.set_owner(owner);
156 return it;
157 }
158 };
159
161 ListLockFree(Allocator &allocator = DefaultAllocator) : p_allocator(&allocator) {
162 link();
163 }
164
167 link();
168 // Copy elements (this is not thread-safe for the source)
169 Node* current = ref.first.next.load(std::memory_order_acquire);
170 while (current != &ref.last) {
171 push_back(current->data);
172 current = current->next.load(std::memory_order_acquire);
173 }
174 }
175
177 template<size_t N>
178 ListLockFree(const T (&a)[N], Allocator &allocator = DefaultAllocator) : p_allocator(&allocator) {
179 link();
180 for(int i = 0; i < N; ++i) {
181 push_back(a[i]);
182 }
183 }
184
186 clear();
187 // Nothing can still be racing against a destructor call - drain
188 // the recycle list and actually free the nodes now.
189 Node* n = free_list.load(std::memory_order_relaxed);
190 while (n != nullptr) {
191 Node* next = n->next.load(std::memory_order_relaxed);
192 releaseNode(n);
193 n = next;
194 }
195 }
196
197#ifdef USE_INITIALIZER_LIST
198 ListLockFree(std::initializer_list<T> iniList, Allocator &allocator = DefaultAllocator) : p_allocator(&allocator) {
199 link();
200 for(const auto &obj : iniList) {
201 push_back(obj);
202 }
203 }
204#endif
205
211 if (this == &ref) return true;
212
213 Node* a_begin = first.next.load(std::memory_order_relaxed);
214 Node* a_end = last.prior.load(std::memory_order_relaxed);
215 Node* b_begin = ref.first.next.load(std::memory_order_relaxed);
216 Node* b_end = ref.last.prior.load(std::memory_order_relaxed);
217 bool a_empty = (a_begin == &last);
218 bool b_empty = (b_begin == &ref.last);
219
220 // Re-point this list's boundary at ref's chain.
221 first.next.store(b_empty ? &last : b_begin, std::memory_order_relaxed);
222 last.prior.store(b_empty ? &first : b_end, std::memory_order_relaxed);
223 if (!b_empty) {
224 b_begin->prior.store(&first, std::memory_order_relaxed);
225 b_end->next.store(&last, std::memory_order_relaxed);
226 }
227
228 // Re-point ref's boundary at this list's original chain.
229 ref.first.next.store(a_empty ? &ref.last : a_begin, std::memory_order_relaxed);
230 ref.last.prior.store(a_empty ? &ref.first : a_end, std::memory_order_relaxed);
231 if (!a_empty) {
232 a_begin->prior.store(&ref.first, std::memory_order_relaxed);
233 a_end->next.store(&ref.last, std::memory_order_relaxed);
234 }
235
236 size_t a_count = record_count.load(std::memory_order_relaxed);
237 size_t b_count = ref.record_count.load(std::memory_order_relaxed);
238 record_count.store(b_count, std::memory_order_relaxed);
239 ref.record_count.store(a_count, std::memory_order_relaxed);
240
241 return true;
242 }
243
244 bool push_back(const T& data) {
245 Node* node = createNode();
246 if (node == nullptr) return false;
247 node->data = data;
248
249 while (true) {
250 Node* old_last_prior = last.prior.load(std::memory_order_acquire);
251
252 // Try to link the new node
253 node->next.store(&last, std::memory_order_relaxed);
254 node->prior.store(old_last_prior, std::memory_order_relaxed);
255
256 // Atomically update the prior node's next pointer
257 Node* expected_next = &last;
258 if (old_last_prior->next.compare_exchange_weak(
259 expected_next, node, std::memory_order_release, std::memory_order_relaxed)) {
260
261 // Atomically update last's prior pointer
262 Node* expected_prior = old_last_prior;
263 if (last.prior.compare_exchange_weak(
264 expected_prior, node, std::memory_order_release, std::memory_order_relaxed)) {
265
266 record_count.fetch_add(1, std::memory_order_relaxed);
267 return true;
268 }
269
270 // Rollback the first change
271 old_last_prior->next.store(&last, std::memory_order_relaxed);
272 }
273 }
274 }
275
276 bool push_front(const T& data) {
277 Node* node = createNode();
278 if (node == nullptr) return false;
279 node->data = data;
280
281 while (true) {
282 Node* old_first_next = first.next.load(std::memory_order_acquire);
283
284 // Try to link the new node
285 node->prior.store(&first, std::memory_order_relaxed);
286 node->next.store(old_first_next, std::memory_order_relaxed);
287
288 // Atomically update the next node's prior pointer
289 Node* expected_prior = &first;
290 if (old_first_next->prior.compare_exchange_weak(
291 expected_prior, node, std::memory_order_release, std::memory_order_relaxed)) {
292
293 // Atomically update first's next pointer
294 Node* expected_next = old_first_next;
295 if (first.next.compare_exchange_weak(
296 expected_next, node, std::memory_order_release, std::memory_order_relaxed)) {
297
298 record_count.fetch_add(1, std::memory_order_relaxed);
299 return true;
300 }
301
302 // Rollback the first change
303 old_first_next->prior.store(&first, std::memory_order_relaxed);
304 }
305 }
306 }
307
308 bool insert(Iterator it, const T& data) {
309 Node* node = createNode();
310 if (node == nullptr) return false;
311 node->data = data;
312
313 Node* current_node = it.get_node();
314 if (current_node == nullptr) return false;
315
316 while (true) {
317 Node* prior = current_node->prior.load(std::memory_order_acquire);
318 if (prior == nullptr) return false;
319
320 // Set up new node links
321 node->prior.store(prior, std::memory_order_relaxed);
322 node->next.store(current_node, std::memory_order_relaxed);
323
324 // Try to atomically update the prior node's next pointer
325 Node* expected_next = current_node;
326 if (prior->next.compare_exchange_weak(
327 expected_next, node, std::memory_order_release, std::memory_order_relaxed)) {
328
329 // Try to atomically update current node's prior pointer
330 Node* expected_prior = prior;
331 if (current_node->prior.compare_exchange_weak(
332 expected_prior, node, std::memory_order_release, std::memory_order_relaxed)) {
333
334 record_count.fetch_add(1, std::memory_order_relaxed);
335 return true;
336 }
337
338 // Rollback the prior->next change
339 prior->next.store(current_node, std::memory_order_relaxed);
340 }
341 }
342 }
343
344 bool pop_front() {
345 T tmp;
346 return pop_front(tmp);
347 }
348
349 bool pop_back() {
350 T tmp;
351 return pop_back(tmp);
352 }
353
354 bool pop_front(T& data) {
355 while (true) {
356 Node* first_data = first.next.load(std::memory_order_acquire);
357 if (first_data == &last) return false; // Empty list
358
359 Node* next_node = first_data->next.load(std::memory_order_acquire);
360 data = first_data->data;
361
362 // Try to atomically update the links
363 if (first.next.compare_exchange_weak(
364 first_data, next_node, std::memory_order_release, std::memory_order_relaxed)) {
365
366 if (next_node->prior.compare_exchange_weak(
367 first_data, &first, std::memory_order_release, std::memory_order_relaxed)) {
368
369 deleteNode(first_data);
370 record_count.fetch_sub(1, std::memory_order_relaxed);
371 return true;
372 }
373
374 // Rollback
375 first.next.store(first_data, std::memory_order_relaxed);
376 }
377 }
378 }
379
380 bool pop_back(T& data) {
381 while (true) {
382 Node* last_data = last.prior.load(std::memory_order_acquire);
383 if (last_data == &first) return false; // Empty list
384
385 Node* prior_node = last_data->prior.load(std::memory_order_acquire);
386 data = last_data->data;
387
388 // Try to atomically update the links
389 if (last.prior.compare_exchange_weak(
390 last_data, prior_node, std::memory_order_release, std::memory_order_relaxed)) {
391
392 if (prior_node->next.compare_exchange_weak(
393 last_data, &last, std::memory_order_release, std::memory_order_relaxed)) {
394
395 deleteNode(last_data);
396 record_count.fetch_sub(1, std::memory_order_relaxed);
397 return true;
398 }
399
400 // Rollback
401 last.prior.store(last_data, std::memory_order_relaxed);
402 }
403 }
404 }
405
406 bool erase(Iterator it) {
407 Node* p_delete = it.get_node();
408 if (p_delete == nullptr || p_delete == &first || p_delete == &last) {
409 return false;
410 }
411
412 while (true) {
413 Node* prior_node = p_delete->prior.load(std::memory_order_acquire);
414 Node* next_node = p_delete->next.load(std::memory_order_acquire);
415
416 if (prior_node == nullptr || next_node == nullptr) return false;
417
418 // Try to atomically update the links
419 if (prior_node->next.compare_exchange_weak(
420 p_delete, next_node, std::memory_order_release, std::memory_order_relaxed)) {
421
422 if (next_node->prior.compare_exchange_weak(
423 p_delete, prior_node, std::memory_order_release, std::memory_order_relaxed)) {
424
425 deleteNode(p_delete);
426 record_count.fetch_sub(1, std::memory_order_relaxed);
427 return true;
428 }
429
430 // Rollback
431 prior_node->next.store(p_delete, std::memory_order_relaxed);
432 }
433 }
434 }
435
437 // For an empty list first.next == &last, so this matches end()
438 // exactly and the usual `for (it = begin(); it != end(); ++it)`
439 // idiom correctly skips the loop body instead of dereferencing
440 // a null node.
441 Node* first_data = first.next.load(std::memory_order_acquire);
442 Iterator it(first_data);
443 it.set_owner(this);
444 return it;
445 }
446
448 Iterator it(&last);
449 it.set_owner(this);
450 return it;
451 }
452
454 // See begin(): for an empty list last.prior == &first, matching
455 // rend() so reverse iteration over an empty list is a no-op.
456 Node* last_data = last.prior.load(std::memory_order_acquire);
457 Iterator it(last_data);
458 it.set_owner(this);
459 return it;
460 }
461
463 Iterator it(&first);
464 it.set_owner(this);
465 return it;
466 }
467
468 size_t size() {
469 return record_count.load(std::memory_order_relaxed);
470 }
471
472 bool empty() {
473 return size() == 0;
474 }
475
476 bool clear() {
477 while (pop_front()) {
478 // Keep removing elements
479 }
480 return true;
481 }
482
483 inline T& operator[](int index) {
484 // Note: This is not thread-safe and may give inconsistent results
485 // in highly concurrent scenarios
486 Node* n = first.next.load(std::memory_order_acquire);
487 for (int j = 0; j < index && n != &last; j++) {
488 n = n->next.load(std::memory_order_acquire);
489 if (n == nullptr) {
490 // Fall back to this list's own dummy sentinel storage
491 // rather than a function-local static, which would be
492 // shared (and racily written to) by every instance.
493 return last.data;
494 }
495 }
496 return n != &last ? n->data : last.data;
497 }
498
499 void setAllocator(Allocator& allocator) {
500 p_allocator = &allocator;
501 }
502
504 T& back() {
505 Node* last_data = last.prior.load(std::memory_order_acquire);
506 return last_data != &first ? last_data->data : last.data;
507 }
508
510 T& front() {
511 Node* first_data = first.next.load(std::memory_order_acquire);
512 return first_data != &last ? first_data->data : first.data;
513 }
514
515 protected:
516 Node first; // empty dummy first node
517 Node last; // empty dummy last node
518 std::atomic<size_t> record_count{0};
520 // Retired nodes are recycled here instead of being returned to the
521 // allocator - see deleteNode()/createNode() for why.
522 std::atomic<Node*> free_list{nullptr};
523
525 // Prefer a recycled node over a fresh allocation: this keeps
526 // unlinked-but-still-referenced nodes "type-stable" memory (see
527 // deleteNode()).
528 Node* head = free_list.load(std::memory_order_acquire);
529 while (head != nullptr) {
530 Node* next = head->next.load(std::memory_order_relaxed);
531 if (free_list.compare_exchange_weak(
532 head, next, std::memory_order_acquire,
533 std::memory_order_relaxed)) {
534 head->next.store(nullptr, std::memory_order_relaxed);
535 head->prior.store(nullptr, std::memory_order_relaxed);
536 return head;
537 }
538 }
539#if USE_ALLOCATOR
540 Node* node = (Node*) p_allocator->allocate(sizeof(Node));
541 if (node != nullptr) {
542 new (node) Node(); // Placement new to call constructor
543 }
544#else
545 Node* node = new Node();
546#endif
547 return node;
548 }
549
550 // Unlinking a node (pop/erase) races against any other thread that
551 // read a pointer to it *before* it was unlinked and is about to
552 // dereference/CAS through that stale pointer (see push_back()'s
553 // `old_last_prior->next.compare_exchange_weak(...)` and the
554 // equivalent spots in push_front()/erase()). Without hazard
555 // pointers/epochs we cannot know when it's safe to actually free
556 // the node, so instead of releasing it back to the allocator we
557 // recycle it onto free_list: the memory stays a live, correctly
558 // typed Node forever (as long as this list exists), so a racing
559 // thread's stale access reads/CASes against recycled-but-valid
560 // storage instead of freed/reused memory. A CAS through a stale
561 // pointer then simply fails (the expected value no longer matches)
562 // rather than corrupting unrelated heap memory. This does not
563 // eliminate the classic ABA case (the node fully recycled and
564 // re-linked before the stale CAS retries) - a complete fix needs
565 // hazard pointers or epoch-based reclamation - but it removes the
566 // undefined-behavior/use-after-free hazard of touching memory that
567 // may have been returned to the allocator and reused for something
568 // else entirely.
569 void deleteNode(Node* p_delete) {
570 if (p_delete == nullptr) return;
571 Node* old_head = free_list.load(std::memory_order_relaxed);
572 do {
573 p_delete->next.store(old_head, std::memory_order_relaxed);
574 } while (!free_list.compare_exchange_weak(
575 old_head, p_delete, std::memory_order_release,
576 std::memory_order_relaxed));
577 }
578
579 // Actually releases a node's memory - only safe once no other
580 // thread can possibly still be operating on the list (e.g. from
581 // the destructor).
582 void releaseNode(Node* p_delete) {
583#if USE_ALLOCATOR
584 if (p_delete != nullptr) {
585 p_delete->~Node(); // Explicit destructor call
586 p_allocator->free(p_delete);
587 }
588#else
589 delete p_delete;
590#endif
591 }
592
593 void link() {
594 first.next.store(&last, std::memory_order_relaxed);
595 last.prior.store(&first, std::memory_order_relaxed);
596 }
597
598 friend class Iterator;
599};
600
601}
Memory allocateator which uses malloc.
Definition Allocator.h:25
virtual void free(void *memory)
frees memory
Definition Allocator.h:84
virtual void * allocate(size_t size)
Allocates memory.
Definition Allocator.h:72
Definition ListLockFree.h:36
bool operator!=(const Iterator &it) const
Definition ListLockFree.h:104
ListLockFree * owner
Definition ListLockFree.h:131
Iterator operator-(int offset)
Definition ListLockFree.h:96
void set_owner(ListLockFree *owner_ptr)
Definition ListLockFree.h:124
T & operator*()
Definition ListLockFree.h:108
T * operator->()
Definition ListLockFree.h:112
Node * get_node()
Definition ListLockFree.h:116
bool is_eof
Definition ListLockFree.h:130
Iterator(Node *node)
Definition ListLockFree.h:38
Iterator operator--(int)
Definition ListLockFree.h:86
Iterator operator+(int offset)
Definition ListLockFree.h:92
bool operator==(const Iterator &it) const
Definition ListLockFree.h:100
Iterator operator--()
Definition ListLockFree.h:67
Node * node
Definition ListLockFree.h:129
Iterator operator++(int)
Definition ListLockFree.h:61
Iterator operator++()
Definition ListLockFree.h:40
Iterator getIteratorAtOffset(int offset)
Definition ListLockFree.h:133
Lock-free double linked list using atomic operations.
Definition ListLockFree.h:25
size_t size()
Definition ListLockFree.h:468
bool pop_front(T &data)
Definition ListLockFree.h:354
Iterator begin()
Definition ListLockFree.h:436
Node first
Definition ListLockFree.h:516
Allocator * p_allocator
Definition ListLockFree.h:519
ListLockFree(const ListLockFree &ref)
Copy constructor (not thread-safe for the source list)
Definition ListLockFree.h:166
bool push_front(const T &data)
Definition ListLockFree.h:276
bool insert(Iterator it, const T &data)
Definition ListLockFree.h:308
Node last
Definition ListLockFree.h:517
bool empty()
Definition ListLockFree.h:472
bool pop_front()
Definition ListLockFree.h:344
T & back()
Provides the last element.
Definition ListLockFree.h:504
bool pop_back(T &data)
Definition ListLockFree.h:380
bool swap(ListLockFree< T > &ref)
Definition ListLockFree.h:210
ListLockFree(Allocator &allocator=DefaultAllocator)
Default constructor.
Definition ListLockFree.h:161
Iterator rend()
Definition ListLockFree.h:462
~ListLockFree()
Definition ListLockFree.h:185
std::atomic< size_t > record_count
Definition ListLockFree.h:518
bool clear()
Definition ListLockFree.h:476
void deleteNode(Node *p_delete)
Definition ListLockFree.h:569
T & front()
Provides the first element.
Definition ListLockFree.h:510
Iterator rbegin()
Definition ListLockFree.h:453
Iterator end()
Definition ListLockFree.h:447
T & operator[](int index)
Definition ListLockFree.h:483
Node * createNode()
Definition ListLockFree.h:524
ListLockFree(const T(&a)[N], Allocator &allocator=DefaultAllocator)
Constructor using array.
Definition ListLockFree.h:178
bool pop_back()
Definition ListLockFree.h:349
void setAllocator(Allocator &allocator)
Definition ListLockFree.h:499
bool erase(Iterator it)
Definition ListLockFree.h:406
void releaseNode(Node *p_delete)
Definition ListLockFree.h:582
std::atomic< Node * > free_list
Definition ListLockFree.h:522
void link()
Definition ListLockFree.h:593
bool push_back(const T &data)
Definition ListLockFree.h:244
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
static TAllocatorExt DefaultAllocator
Definition Allocator.h:208
Definition ListLockFree.h:27
Node(const T &value)
Definition ListLockFree.h:33
T data
Definition ListLockFree.h:30
std::atomic< Node * > prior
Definition ListLockFree.h:29
std::atomic< Node * > next
Definition ListLockFree.h:28