From 032902dea18cf65c3d8cfb069983e5641d720f8f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:11:25 +0000 Subject: [PATCH 01/14] fix(cmake): make LOCKFREE_CACHE_COHERENT a real option Previously CMake added the bare `LOCKFREE_CACHE_COHERENT` token to compile definitions whenever the variable was defined, which the compiler treated as `-DLOCKFREE_CACHE_COHERENT=1` regardless of the value the user set. `-DLOCKFREE_CACHE_COHERENT=false` thus silently kept cacheline padding enabled, leaving embedded targets wasting memory despite the README's instruction to disable it. Replace the variable with a proper `option()` defaulting to ON and forward it through `$` so any of CMake's truthy values enable padding and any falsy value (`OFF`, `false`, `0`, ...) actually disables it. --- CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1756b0d..d76eb52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,9 +11,12 @@ project(lockfree add_subdirectory(${PROJECT_NAME}) # Library configuration -if (DEFINED LOCKFREE_CACHE_COHERENT) - target_compile_definitions(${PROJECT_NAME} INTERFACE LOCKFREE_CACHE_COHERENT) -endif() +option(LOCKFREE_CACHE_COHERENT + "Pad data structures to cacheline boundaries to eliminate false sharing" ON) +target_compile_definitions(${PROJECT_NAME} +INTERFACE + LOCKFREE_CACHE_COHERENT=$ +) if (DEFINED LOCKFREE_CACHELINE_LENGTH) target_compile_definitions(${PROJECT_NAME} From 70f8d66d762239cb9878e65b29b753089246bbb4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:11:37 +0000 Subject: [PATCH 02/14] fix(test/spsc/priority_queue): correct value-priority encoding `cnt << 2 + prio` parses as `cnt << (2 + prio)` because + binds tighter than <<, so the priority bits never landed in the low two bits of the produced value. The downstream verification masked the low two bits to recover the priority and therefore always saw 0, making the priority ordering check a no-op. Use `(cnt << 2) | prio` to match the documented intent. --- tests/spsc/priority_queue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spsc/priority_queue.cpp b/tests/spsc/priority_queue.cpp index e3d74d4..207a6c1 100644 --- a/tests/spsc/priority_queue.cpp +++ b/tests/spsc/priority_queue.cpp @@ -97,7 +97,7 @@ TEST_CASE("spsc::PriorityQueue - Multithreaded read/write", uint64_t value = 0; uint8_t prio = 0; do { - value = cnt << 2 + prio; + value = (cnt << 2) | prio; bool push_success = queue.Push(value, prio); if (push_success) { written.push_back(value); From 773f3f74fc58ab213d696deaee873c96c7251595 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:11:55 +0000 Subject: [PATCH 03/14] fix(test/spsc/priority_queue): match producer/consumer counts The producer pushed TEST_MT_TRANSFER_CNT + 1 elements while the consumer's exit condition was driven by a counter that stopped at TEST_MT_TRANSFER_CNT, leaving one element abandoned in the queue. Unlike the spsc::Queue test (which terminates by observing the last value popped) the priority queue test counts pops, so the +1 is unnecessary. --- tests/spsc/priority_queue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spsc/priority_queue.cpp b/tests/spsc/priority_queue.cpp index 207a6c1..f6a6dec 100644 --- a/tests/spsc/priority_queue.cpp +++ b/tests/spsc/priority_queue.cpp @@ -104,7 +104,7 @@ TEST_CASE("spsc::PriorityQueue - Multithreaded read/write", prio = (prio + 1) % 4; // this could be also randomly generated cnt++; } - } while (cnt < TEST_MT_TRANSFER_CNT + 1); + } while (cnt < TEST_MT_TRANSFER_CNT); }); for (auto &t : threads) { From cd429666ade9031fac615e2c082cd949546af138 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:12:04 +0000 Subject: [PATCH 04/14] fix(test/spsc/bipartite_buf): use the right array in element-size division The release count for test_data2 was computed using sizeof(test_data[0]). It happened to produce the right number because both arrays are uint32_t, but the expression is wrong and a hazard for anyone copy/pasting from the tests. --- tests/spsc/bipartite_buf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spsc/bipartite_buf.cpp b/tests/spsc/bipartite_buf.cpp index 64bcae3..4fcc81f 100644 --- a/tests/spsc/bipartite_buf.cpp +++ b/tests/spsc/bipartite_buf.cpp @@ -61,7 +61,7 @@ TEST_CASE("spsc::BipartiteBuf - Write with overflow condition", bb.WriteAcquire(sizeof(test_data2) / sizeof(test_data2[0])); REQUIRE(write_location != nullptr); std::copy(std::begin(test_data2), std::end(test_data2), write_location); - bb.WriteRelease(sizeof(test_data2) / sizeof(test_data[0])); + bb.WriteRelease(sizeof(test_data2) / sizeof(test_data2[0])); read = bb.ReadAcquire(); REQUIRE(read.first != nullptr); From a0b5b06b0e75d82bab843cad9e516cec14b5e5e4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:12:15 +0000 Subject: [PATCH 05/14] fix(readme): correct CMake badge URL The badge URL had `.github/workflows/` duplicated, so the badge returned 404. GitHub Actions badges take only the workflow filename under `actions/workflows/`. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ccfc322..8c90381 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # lockfree -![CMake](https://github.com/DNedic/lockfree/actions/workflows/.github/workflows/cmake.yml/badge.svg) +![CMake](https://github.com/DNedic/lockfree/actions/workflows/cmake.yml/badge.svg) `lockfree` is a collection of lock-free data structures written in standard C++11 and suitable for all platforms - from deeply embedded to HPC. From 2c329c5152d5ffbe56d06487a21017893728a483 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:12:50 +0000 Subject: [PATCH 06/14] fix(doc/mpmc/priority_queue): remove single-thread caveats The Push/Pop comments were copy-pasted from the spsc::PriorityQueue header and told users to call them only from a single producer or consumer thread. The mpmc variant supports multiple producers and consumers, which is the entire reason it exists. --- lockfree/mpmc/priority_queue.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/lockfree/mpmc/priority_queue.hpp b/lockfree/mpmc/priority_queue.hpp index e4b573e..1791993 100644 --- a/lockfree/mpmc/priority_queue.hpp +++ b/lockfree/mpmc/priority_queue.hpp @@ -63,7 +63,6 @@ template class PriorityQueue { public: /** * @brief Adds an element with a specified priority into the queue. - * Should only be called from the producer thread. * @param[in] Element * @param[in] Element priority * @retval Operation success @@ -72,7 +71,6 @@ template class PriorityQueue { /** * @brief Removes an element with the highest priority from the queue. - * Should only be called from the consumer thread. * @param[out] Element * @retval Operation success */ @@ -81,7 +79,6 @@ template class PriorityQueue { #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) /** * @brief Removes an element with the highest priority from the queue. - * Should only be called from the consumer thread. * @retval Either the element or nothing if the queue is empty. */ std::optional Pop(); From 8d2bf46b41f4bec383ce2d43fb756fd62ffff9bf Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:13:14 +0000 Subject: [PATCH 07/14] fix(doc/spsc/ring_buf): correct @retval text for Read/Peek/Skip Read, Peek and Skip all advertised "Write success" as their return value, copy-pasted from Write. Make them describe what they actually return. --- lockfree/spsc/ring_buf.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lockfree/spsc/ring_buf.hpp b/lockfree/spsc/ring_buf.hpp index 3e16dea..b30e353 100755 --- a/lockfree/spsc/ring_buf.hpp +++ b/lockfree/spsc/ring_buf.hpp @@ -94,7 +94,7 @@ template class RingBuf { * Should only be called from the consumer thread. * @param[out] Pointer to the space to read the data to * @param[in] Number of elements to read - * @retval Write success + * @retval Read success */ bool Read(T *data, size_t cnt); @@ -102,7 +102,7 @@ template class RingBuf { * @brief Reads data from the ring buffer. * Should only be called from the consumer thread. * @param[out] Array to write the read to - * @retval Write success + * @retval Read success */ template bool Read(std::array &data); @@ -111,7 +111,7 @@ template class RingBuf { * @brief Reads data from the ring buffer. * Should only be called from the consumer thread. * @param[out] Span to read to - * @retval Write success + * @retval Read success */ bool Read(std::span data); #endif @@ -124,7 +124,7 @@ template class RingBuf { * it. Should only be called from the consumer thread. * @param[out] Pointer to the space to read the data to * @param[in] Number of elements to read - * @retval Write success + * @retval Peek success */ bool Peek(T *data, size_t cnt) const; @@ -135,7 +135,7 @@ template class RingBuf { * buffer after some operation using the data fails, or uses only some of * it. Should only be called from the consumer thread. * @param[out] Array to write the read to - * @retval Write success + * @retval Peek success */ template bool Peek(std::array &data) const; @@ -146,7 +146,7 @@ template class RingBuf { * buffer after some operation using the data fails, or uses only some of * it. Should only be called from the consumer thread. * @param[out] Span to read to - * @retval Write success + * @retval Peek success */ #if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) bool Peek(std::span data) const; @@ -159,7 +159,7 @@ template class RingBuf { * buffer after some operation using the data fails, or uses only some of * it. Should only be called from the consumer thread. * @param[in] Number of elements to skip - * @retval Write success + * @retval Skip success */ bool Skip(size_t cnt); From d5ff4d8b5782ee75b7c7a97a7704c53f460b8817 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:14:14 +0000 Subject: [PATCH 08/14] doc(lockfree): add parameter names to Doxygen @param tags Doxygen requires the parameter name as the first token of @param to associate the description with the right argument. The library's public headers consistently omitted the name, so generated docs ended up with detached descriptions. --- lockfree/mpmc/priority_queue.hpp | 6 +++--- lockfree/spsc/bipartite_buf.hpp | 12 ++++++------ lockfree/spsc/priority_queue.hpp | 6 +++--- lockfree/spsc/ring_buf.hpp | 26 +++++++++++++------------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lockfree/mpmc/priority_queue.hpp b/lockfree/mpmc/priority_queue.hpp index 1791993..282aa88 100644 --- a/lockfree/mpmc/priority_queue.hpp +++ b/lockfree/mpmc/priority_queue.hpp @@ -63,15 +63,15 @@ template class PriorityQueue { public: /** * @brief Adds an element with a specified priority into the queue. - * @param[in] Element - * @param[in] Element priority + * @param[in] element Element to push + * @param[in] priority Element priority * @retval Operation success */ bool Push(const T &element, size_t priority); /** * @brief Removes an element with the highest priority from the queue. - * @param[out] Element + * @param[out] element Element popped * @retval Operation success */ bool Pop(T &element); diff --git a/lockfree/spsc/bipartite_buf.hpp b/lockfree/spsc/bipartite_buf.hpp index d00b36a..a6cf471 100644 --- a/lockfree/spsc/bipartite_buf.hpp +++ b/lockfree/spsc/bipartite_buf.hpp @@ -65,7 +65,7 @@ template class BipartiteBuf { /** * @brief Acquires a linear region in the bipartite buffer for writing * Should only be called from the producer thread. - * @param[in] Free linear space in the buffer required + * @param[in] free_required Free linear space in the buffer required * @retval Pointer to the beginning of the linear space, nullptr if no space */ T *WriteAcquire(size_t free_required); @@ -74,7 +74,7 @@ template class BipartiteBuf { /** * @brief Acquires a linear region in the bipartite buffer for writing * Should only be called from the producer thread. - * @param[in] Free linear space in the buffer required + * @param[in] free_required Free linear space in the buffer required * @retval Span of the linear space */ std::span WriteAcquireSpan(size_t free_required); @@ -83,7 +83,7 @@ template class BipartiteBuf { /** * @brief Releases the bipartite buffer after a write * Should only be called from the producer thread. - * @param[in] Elements written to the linear space + * @param[in] written Elements written to the linear space * @retval None */ void WriteRelease(size_t written); @@ -92,7 +92,7 @@ template class BipartiteBuf { /** * @brief Releases the bipartite buffer after a write * Should only be called from the producer thread. - * @param[in] Span of the linear space + * @param[in] written Span of the linear space * @retval None */ void WriteRelease(std::span written); @@ -118,7 +118,7 @@ template class BipartiteBuf { /** * @brief Releases the bipartite buffer after a read * Should only be called from the consumer thread. - * @param[in] Elements read from the linear region + * @param[in] read Elements read from the linear region * @retval None */ void ReadRelease(size_t read); @@ -127,7 +127,7 @@ template class BipartiteBuf { /** * @brief Releases the bipartite buffer after a read * Should only be called from the consumer thread. - * @param[in] Span of the linear space + * @param[in] read Span of the linear space * @retval None */ void ReadRelease(std::span read); diff --git a/lockfree/spsc/priority_queue.hpp b/lockfree/spsc/priority_queue.hpp index 93c736a..9e5e230 100644 --- a/lockfree/spsc/priority_queue.hpp +++ b/lockfree/spsc/priority_queue.hpp @@ -65,8 +65,8 @@ template class PriorityQueue { /** * @brief Adds an element with a specified priority into the queue. * Should only be called from the producer thread. - * @param[in] Element - * @param[in] Element priority + * @param[in] element Element to push + * @param[in] priority Element priority * @retval Operation success */ bool Push(const T &element, size_t priority); @@ -74,7 +74,7 @@ template class PriorityQueue { /** * @brief Removes an element with the highest priority from the queue. * Should only be called from the consumer thread. - * @param[out] Element + * @param[out] element Element popped * @retval Operation success */ bool Pop(T &element); diff --git a/lockfree/spsc/ring_buf.hpp b/lockfree/spsc/ring_buf.hpp index b30e353..798a328 100755 --- a/lockfree/spsc/ring_buf.hpp +++ b/lockfree/spsc/ring_buf.hpp @@ -65,8 +65,8 @@ template class RingBuf { /** * @brief Writes data to the ring buffer. * Should only be called from the producer thread. - * @param[in] Pointer to the data to write - * @param[in] Number of elements to write + * @param[in] data Pointer to the data to write + * @param[in] cnt Number of elements to write * @retval Write success */ bool Write(const T *data, size_t cnt); @@ -74,7 +74,7 @@ template class RingBuf { /** * @brief Writes data to the ring buffer. * Should only be called from the producer thread. - * @param[in] Data array to write + * @param[in] data Data array to write * @retval Write success */ template bool Write(const std::array &data); @@ -83,7 +83,7 @@ template class RingBuf { /** * @brief Writes data to the ring buffer. * Should only be called from the producer thread. - * @param[in] Span of data to write + * @param[in] data Span of data to write * @retval Write success */ bool Write(std::span data); @@ -92,8 +92,8 @@ template class RingBuf { /** * @brief Reads data from the ring buffer. * Should only be called from the consumer thread. - * @param[out] Pointer to the space to read the data to - * @param[in] Number of elements to read + * @param[out] data Pointer to the space to read the data to + * @param[in] cnt Number of elements to read * @retval Read success */ bool Read(T *data, size_t cnt); @@ -101,7 +101,7 @@ template class RingBuf { /** * @brief Reads data from the ring buffer. * Should only be called from the consumer thread. - * @param[out] Array to write the read to + * @param[out] data Array to write the read to * @retval Read success */ template bool Read(std::array &data); @@ -110,7 +110,7 @@ template class RingBuf { /** * @brief Reads data from the ring buffer. * Should only be called from the consumer thread. - * @param[out] Span to read to + * @param[out] data Span to read to * @retval Read success */ bool Read(std::span data); @@ -122,8 +122,8 @@ template class RingBuf { * The combination is most useful when we want to keep the data in the * buffer after some operation using the data fails, or uses only some of * it. Should only be called from the consumer thread. - * @param[out] Pointer to the space to read the data to - * @param[in] Number of elements to read + * @param[out] data Pointer to the space to read the data to + * @param[in] cnt Number of elements to read * @retval Peek success */ bool Peek(T *data, size_t cnt) const; @@ -134,7 +134,7 @@ template class RingBuf { * The combination is most useful when we want to keep the data in the * buffer after some operation using the data fails, or uses only some of * it. Should only be called from the consumer thread. - * @param[out] Array to write the read to + * @param[out] data Array to write the read to * @retval Peek success */ template bool Peek(std::array &data) const; @@ -145,7 +145,7 @@ template class RingBuf { * The combination is most useful when we want to keep the data in the * buffer after some operation using the data fails, or uses only some of * it. Should only be called from the consumer thread. - * @param[out] Span to read to + * @param[out] data Span to read to * @retval Peek success */ #if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) @@ -158,7 +158,7 @@ template class RingBuf { * The combination is most useful when we want to keep the data in the * buffer after some operation using the data fails, or uses only some of * it. Should only be called from the consumer thread. - * @param[in] Number of elements to skip + * @param[in] cnt Number of elements to skip * @retval Skip success */ bool Skip(size_t cnt); From be1f11b3db1ccb53ea2efb2c5c4efcda5d1cc7d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:14:28 +0000 Subject: [PATCH 09/14] doc(spsc/bipartite_buf): fix unbalanced parentheses in examples Two of the example snippets had a missing closing paren after ADC_PollDmaComplete and a stray extra paren after read.empty(), making the examples uncompilable as written. --- docs/spsc/bipartite_buf.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/spsc/bipartite_buf.md b/docs/spsc/bipartite_buf.md index ec3cdfb..53e4cd2 100644 --- a/docs/spsc/bipartite_buf.md +++ b/docs/spsc/bipartite_buf.md @@ -35,7 +35,7 @@ if (!write_started) { write_started = true; } } else { - if (ADC_PollDmaComplete(&adc_dma_h) { + if (ADC_PollDmaComplete(&adc_dma_h)) { bb_adc.WriteRelease(data.size()); write_started = false; } @@ -47,7 +47,7 @@ There is also a `std::span` based API for those using C++20 and up: ```cpp auto read = bb_adc.ReadAcquireSpan(); -if (!read.empty())) { +if (!read.empty()) { auto span_used = DoStuffWithData(read); bb_adc.ReadRelease(span_used); } @@ -62,7 +62,7 @@ if (!write_started) { write_started = true; } } else { - if (ADC_PollDmaComplete(&adc_dma_h) { + if (ADC_PollDmaComplete(&adc_dma_h)) { bb_adc.WriteRelease(data.size()); write_started = false; } From 4259c9d0957b4b2d60aa750ec2ac2dba8156c3e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:14:37 +0000 Subject: [PATCH 10/14] doc(priority_queue): fix "chosing" typo --- docs/mpmc/priority_queue.md | 2 +- docs/spsc/priority_queue.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/mpmc/priority_queue.md b/docs/mpmc/priority_queue.md index f92e63d..7b432a4 100644 --- a/docs/mpmc/priority_queue.md +++ b/docs/mpmc/priority_queue.md @@ -41,4 +41,4 @@ if (read) { This implementation has `O(1)` time complexity for `Push` and `O(current_max_priority)` for `Pop` making it extremely fast. -On the other hand the memory usage is a function of `size * priority_count`, so adequately chosing the number of priorities is necessary. +On the other hand the memory usage is a function of `size * priority_count`, so adequately choosing the number of priorities is necessary. diff --git a/docs/spsc/priority_queue.md b/docs/spsc/priority_queue.md index 13a30fb..835fbdf 100644 --- a/docs/spsc/priority_queue.md +++ b/docs/spsc/priority_queue.md @@ -41,4 +41,4 @@ if (read) { This implementation has `O(1)` time complexity for `Push` and `O(current_max_priority)` for `Pop` making it extremely fast. -On the other hand the memory usage is a function of `size * priority_count`, so adequately chosing the number of priorities is necessary. +On the other hand the memory usage is a function of `size * priority_count`, so adequately choosing the number of priorities is necessary. From e957cfb9b9424778f92b061f34e1578739dddc76 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 19:14:45 +0000 Subject: [PATCH 11/14] doc(changelog): close unmatched code span in 2.0.10 entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01ea833..91d3168 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ - Ensured [PriorityQueue](docs/mpmc/priority_queue.md)s must have at least 2 priorities ## 2.0.10 -- Added a missing include in the [Ring Buffer](docs/spsc/ring_buf.md) causing errors for `memcpy use +- Added a missing include in the [Ring Buffer](docs/spsc/ring_buf.md) causing errors for `memcpy` use ## 2.0.9 - Fixed the initialization order in the [Bipartite Buffer](docs/spsc/bipartite_buf.md) constructor From eb52d615404b199340d21c850143e4fc365ea245 Mon Sep 17 00:00:00 2001 From: Djordje Nedic Date: Sun, 3 May 2026 20:59:47 +0200 Subject: [PATCH 12/14] test(spsc/priority_queue): drop multithreaded test The verification it performed couldn't actually check priority ordering under concurrent producer/consumer: Pop scans sub-queues from highest to lowest priority, so a higher-priority Push that lands in a sub-queue the scan has already passed legitimately leaves a higher-priority value behind while a lower-priority value is returned. With that strict check removed only value-integrity remained, which spsc::Queue's multithreaded test already covers via the per-priority sub-queues. --- tests/spsc/priority_queue.cpp | 77 ----------------------------------- 1 file changed, 77 deletions(-) diff --git a/tests/spsc/priority_queue.cpp b/tests/spsc/priority_queue.cpp index f6a6dec..fe1bb55 100644 --- a/tests/spsc/priority_queue.cpp +++ b/tests/spsc/priority_queue.cpp @@ -1,7 +1,3 @@ -#include -#include -#include - #include #include "lockfree.hpp" @@ -69,79 +65,6 @@ TEST_CASE("spsc::PriorityQueue - Write multiple with different priority and " REQUIRE(read == 1024); } -TEST_CASE("spsc::PriorityQueue - Multithreaded read/write", - "[pq_multithreaded]") { - lockfree::spsc::PriorityQueue queue; - std::vector threads; - std::vector written; - std::vector read; - - // consumer, it just pops values from the queue and stores them in the - // main thread vector - threads.emplace_back([&]() { - uint64_t value = 0; - uint64_t cnt = 0; - do { - bool pop_success = queue.Pop(value); - if (pop_success) { - read.push_back(value); - cnt++; - } - } while (cnt < TEST_MT_TRANSFER_CNT); - }); - - // producer, uses alternative priorities and pushes a counter shifted to - // accommodate the priority on its lower bits. - threads.emplace_back([&]() { - uint64_t cnt = 0; - uint64_t value = 0; - uint8_t prio = 0; - do { - value = (cnt << 2) | prio; - bool push_success = queue.Push(value, prio); - if (push_success) { - written.push_back(value); - prio = (prio + 1) % 4; // this could be also randomly generated - cnt++; - } - } while (cnt < TEST_MT_TRANSFER_CNT); - }); - - for (auto &t : threads) { - t.join(); - } - /* The following code checks that at all times no higher priority value was - present in the `written` vector. - - It needs to keep track which values were already read, it does that with - the help of the `consumed` Boolean vector. - */ - std::vector consumed(written.size(), false); - - for (size_t read_idx = 0; read_idx < read.size(); read_idx++) { - const uint64_t read_value = read[read_idx]; - const uint64_t read_priority = read_value & ((1 << 2) - 1); - - bool found_value = false; - for (size_t write_idx = 0; write_idx < written.size(); write_idx++) { - if (consumed[write_idx]) { // consumed values are skipped - continue; - } - // find when the value was written - const uint64_t written_value = written[write_idx]; - const uint64_t written_priority = written_value & ((1 << 2) - 1); - if (written[write_idx] == read_value) { - consumed[write_idx] = true; // this value is now accounted for - found_value = true; - break; - } else { // intermediate value, should be lower priority - REQUIRE(written_priority <= read_priority); - } - } - REQUIRE(found_value); - } -} - TEST_CASE("spsc::PriorityQueue - Optional API", "[pq_optional_api]") { lockfree::spsc::PriorityQueue queue; From 998603882a984bf3feec9d63d053554f6d2f6e0b Mon Sep 17 00:00:00 2001 From: Djordje Nedic Date: Sun, 3 May 2026 22:31:42 +0200 Subject: [PATCH 13/14] fix(config): make CMake the single source of truth for build options The umbrella `lockfree.hpp` defaulted `LOCKFREE_CACHE_COHERENT` and `LOCKFREE_CACHELINE_LENGTH` via `#ifndef`. That had two problems: the values weren't used when consumers pulled in an individual data structure header without going through `lockfree.hpp` first (silently disabling cacheline padding and breaking alignment), and the defaults duplicated logic that now lives in CMake. Move all defaulting into CMake: `LOCKFREE_CACHELINE_LENGTH` becomes a cache STRING defaulting to 64, and the two macros are always forwarded as compile definitions. Drop the umbrella defaults and add an `#error` to each data structure header so a build that forgets to define them fails loudly instead of silently producing the wrong layout. Non-CMake build systems must now define both macros themselves. --- CMakeLists.txt | 10 +++------- lockfree/lockfree.hpp | 10 ---------- lockfree/mpmc/queue.hpp | 5 +++++ lockfree/spsc/bipartite_buf.hpp | 5 +++++ lockfree/spsc/queue.hpp | 5 +++++ lockfree/spsc/ring_buf.hpp | 5 +++++ 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d76eb52..9d681a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,18 +13,14 @@ add_subdirectory(${PROJECT_NAME}) # Library configuration option(LOCKFREE_CACHE_COHERENT "Pad data structures to cacheline boundaries to eliminate false sharing" ON) +set(LOCKFREE_CACHELINE_LENGTH 64 CACHE STRING + "Cacheline length in bytes used for alignment when LOCKFREE_CACHE_COHERENT is on") target_compile_definitions(${PROJECT_NAME} INTERFACE LOCKFREE_CACHE_COHERENT=$ + LOCKFREE_CACHELINE_LENGTH=${LOCKFREE_CACHELINE_LENGTH} ) -if (DEFINED LOCKFREE_CACHELINE_LENGTH) - target_compile_definitions(${PROJECT_NAME} - INTERFACE - LOCKFREE_CACHELINE_LENGTH=${LOCKFREE_CACHELINE_LENGTH} - ) -endif() - # Only build tests if we're actually working on the library, # not when the library is being used in a project if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) diff --git a/lockfree/lockfree.hpp b/lockfree/lockfree.hpp index 8a1c6c9..f2ff1b9 100644 --- a/lockfree/lockfree.hpp +++ b/lockfree/lockfree.hpp @@ -39,16 +39,6 @@ #ifndef LOCKFREE_HPP #define LOCKFREE_HPP -/************************** DEFINE ****************************/ - -#ifndef LOCKFREE_CACHE_COHERENT -#define LOCKFREE_CACHE_COHERENT true -#endif - -#ifndef LOCKFREE_CACHELINE_LENGTH -#define LOCKFREE_CACHELINE_LENGTH 64U -#endif - /************************** INCLUDE ***************************/ #include "spsc/bipartite_buf.hpp" diff --git a/lockfree/mpmc/queue.hpp b/lockfree/mpmc/queue.hpp index 2fa7886..ac6cef9 100644 --- a/lockfree/mpmc/queue.hpp +++ b/lockfree/mpmc/queue.hpp @@ -40,6 +40,11 @@ #ifndef LOCKFREE_MPMC_QUEUE_HPP #define LOCKFREE_MPMC_QUEUE_HPP +#if !defined(LOCKFREE_CACHE_COHERENT) || !defined(LOCKFREE_CACHELINE_LENGTH) +#error \ + "lockfree requires LOCKFREE_CACHE_COHERENT and LOCKFREE_CACHELINE_LENGTH to be defined; use the CMake build or define them manually" +#endif + #include #include #include diff --git a/lockfree/spsc/bipartite_buf.hpp b/lockfree/spsc/bipartite_buf.hpp index a6cf471..3e2e535 100644 --- a/lockfree/spsc/bipartite_buf.hpp +++ b/lockfree/spsc/bipartite_buf.hpp @@ -41,6 +41,11 @@ #ifndef LOCKFREE_BIPARTITE_BUF_HPP #define LOCKFREE_BIPARTITE_BUF_HPP +#if !defined(LOCKFREE_CACHE_COHERENT) || !defined(LOCKFREE_CACHELINE_LENGTH) +#error \ + "lockfree requires LOCKFREE_CACHE_COHERENT and LOCKFREE_CACHELINE_LENGTH to be defined; use the CMake build or define them manually" +#endif + #include #include #include diff --git a/lockfree/spsc/queue.hpp b/lockfree/spsc/queue.hpp index d69a191..aca65fd 100755 --- a/lockfree/spsc/queue.hpp +++ b/lockfree/spsc/queue.hpp @@ -41,6 +41,11 @@ #ifndef LOCKFREE_QUEUE_HPP #define LOCKFREE_QUEUE_HPP +#if !defined(LOCKFREE_CACHE_COHERENT) || !defined(LOCKFREE_CACHELINE_LENGTH) +#error \ + "lockfree requires LOCKFREE_CACHE_COHERENT and LOCKFREE_CACHELINE_LENGTH to be defined; use the CMake build or define them manually" +#endif + #include #include #include diff --git a/lockfree/spsc/ring_buf.hpp b/lockfree/spsc/ring_buf.hpp index 798a328..473f1d3 100755 --- a/lockfree/spsc/ring_buf.hpp +++ b/lockfree/spsc/ring_buf.hpp @@ -41,6 +41,11 @@ #ifndef LOCKFREE_RING_BUF_HPP #define LOCKFREE_RING_BUF_HPP +#if !defined(LOCKFREE_CACHE_COHERENT) || !defined(LOCKFREE_CACHELINE_LENGTH) +#error \ + "lockfree requires LOCKFREE_CACHE_COHERENT and LOCKFREE_CACHELINE_LENGTH to be defined; use the CMake build or define them manually" +#endif + #include #include #include From ae6c4df124536218b0b1adfc21ab4921810a00a5 Mon Sep 17 00:00:00 2001 From: Djordje Nedic Date: Sun, 3 May 2026 23:10:27 +0200 Subject: [PATCH 14/14] Release 3.0.1 --- CHANGELOG.md | 9 +++++++++ CMakeLists.txt | 2 +- lockfree/lockfree.hpp | 2 +- lockfree/mpmc/priority_queue.hpp | 2 +- lockfree/mpmc/priority_queue_impl.hpp | 2 +- lockfree/mpmc/queue.hpp | 2 +- lockfree/mpmc/queue_impl.hpp | 2 +- lockfree/spsc/bipartite_buf.hpp | 2 +- lockfree/spsc/bipartite_buf_impl.hpp | 2 +- lockfree/spsc/priority_queue.hpp | 2 +- lockfree/spsc/priority_queue_impl.hpp | 2 +- lockfree/spsc/queue.hpp | 2 +- lockfree/spsc/queue_impl.hpp | 2 +- lockfree/spsc/ring_buf.hpp | 2 +- lockfree/spsc/ring_buf_impl.hpp | 2 +- 15 files changed, 23 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91d3168..daf8b91 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 3.0.1 +- Fixed `LOCKFREE_CACHE_COHERENT` being silently ignored: the variable's value wasn't forwarded to the compiler, so `-DLOCKFREE_CACHE_COHERENT=false` had no effect and cacheline padding was always enabled +- **Important**: `LOCKFREE_CACHE_COHERENT` and `LOCKFREE_CACHELINE_LENGTH` are now sourced exclusively from CMake; non-CMake builds must define both macros themselves and will fail to compile with an `#error` if either is missing +- Fixed broken example code in [Bipartite Buffer](docs/spsc/bipartite_buf.md) docs (unbalanced parentheses) +- Fixed incorrect `@retval` text for `Read`/`Peek`/`Skip` in [Ring Buffer](docs/spsc/ring_buf.md) (was copy-pasted from `Write`) +- Removed incorrect single-thread caveats from [mpmc::PriorityQueue](docs/mpmc/priority_queue.md) `Push`/`Pop` documentation +- Added missing parameter names to Doxygen `@param` tags across all data structure headers +- Various README and documentation fixes + ## 3.0.0 - **Breaking**: `PopOptional()` has been renamed to `Pop()` as an overload for the [Queue](docs/spsc/queue.md)s - **Breaking**: [mpmc::Queue](docs/mpmc/queue.md) now enforces a power-of-2 `size`. This is necessary to prevent deadlocks on index overflows. diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d681a1..9092edc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.16) project(lockfree - VERSION 3.0.0 + VERSION 3.0.1 LANGUAGES CXX ) diff --git a/lockfree/lockfree.hpp b/lockfree/lockfree.hpp index f2ff1b9..e0c296d 100644 --- a/lockfree/lockfree.hpp +++ b/lockfree/lockfree.hpp @@ -33,7 +33,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ #ifndef LOCKFREE_HPP diff --git a/lockfree/mpmc/priority_queue.hpp b/lockfree/mpmc/priority_queue.hpp index 282aa88..651e44f 100644 --- a/lockfree/mpmc/priority_queue.hpp +++ b/lockfree/mpmc/priority_queue.hpp @@ -33,7 +33,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ diff --git a/lockfree/mpmc/priority_queue_impl.hpp b/lockfree/mpmc/priority_queue_impl.hpp index 469b72b..7eb6416 100644 --- a/lockfree/mpmc/priority_queue_impl.hpp +++ b/lockfree/mpmc/priority_queue_impl.hpp @@ -33,7 +33,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ diff --git a/lockfree/mpmc/queue.hpp b/lockfree/mpmc/queue.hpp index ac6cef9..ca46994 100644 --- a/lockfree/mpmc/queue.hpp +++ b/lockfree/mpmc/queue.hpp @@ -33,7 +33,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ diff --git a/lockfree/mpmc/queue_impl.hpp b/lockfree/mpmc/queue_impl.hpp index d3f1bad..c7fd4e8 100644 --- a/lockfree/mpmc/queue_impl.hpp +++ b/lockfree/mpmc/queue_impl.hpp @@ -33,7 +33,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ namespace lockfree { diff --git a/lockfree/spsc/bipartite_buf.hpp b/lockfree/spsc/bipartite_buf.hpp index 3e2e535..4a66f76 100644 --- a/lockfree/spsc/bipartite_buf.hpp +++ b/lockfree/spsc/bipartite_buf.hpp @@ -34,7 +34,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ diff --git a/lockfree/spsc/bipartite_buf_impl.hpp b/lockfree/spsc/bipartite_buf_impl.hpp index 7555936..0d189ec 100644 --- a/lockfree/spsc/bipartite_buf_impl.hpp +++ b/lockfree/spsc/bipartite_buf_impl.hpp @@ -34,7 +34,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ diff --git a/lockfree/spsc/priority_queue.hpp b/lockfree/spsc/priority_queue.hpp index 9e5e230..71efe3d 100644 --- a/lockfree/spsc/priority_queue.hpp +++ b/lockfree/spsc/priority_queue.hpp @@ -34,7 +34,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ diff --git a/lockfree/spsc/priority_queue_impl.hpp b/lockfree/spsc/priority_queue_impl.hpp index 3e0d042..72b2fdb 100644 --- a/lockfree/spsc/priority_queue_impl.hpp +++ b/lockfree/spsc/priority_queue_impl.hpp @@ -34,7 +34,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ diff --git a/lockfree/spsc/queue.hpp b/lockfree/spsc/queue.hpp index aca65fd..6140dba 100755 --- a/lockfree/spsc/queue.hpp +++ b/lockfree/spsc/queue.hpp @@ -34,7 +34,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ diff --git a/lockfree/spsc/queue_impl.hpp b/lockfree/spsc/queue_impl.hpp index 2f99081..a36da23 100644 --- a/lockfree/spsc/queue_impl.hpp +++ b/lockfree/spsc/queue_impl.hpp @@ -34,7 +34,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ namespace lockfree { diff --git a/lockfree/spsc/ring_buf.hpp b/lockfree/spsc/ring_buf.hpp index 473f1d3..e79d6d5 100755 --- a/lockfree/spsc/ring_buf.hpp +++ b/lockfree/spsc/ring_buf.hpp @@ -34,7 +34,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ diff --git a/lockfree/spsc/ring_buf_impl.hpp b/lockfree/spsc/ring_buf_impl.hpp index 949ea61..74d74aa 100644 --- a/lockfree/spsc/ring_buf_impl.hpp +++ b/lockfree/spsc/ring_buf_impl.hpp @@ -34,7 +34,7 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ #include