diff --git a/CHANGELOG.md b/CHANGELOG.md index 01ea833..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. @@ -17,7 +26,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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1756b0d..9092edc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,23 +4,22 @@ cmake_minimum_required(VERSION 3.16) project(lockfree - VERSION 3.0.0 + VERSION 3.0.1 LANGUAGES CXX ) add_subdirectory(${PROJECT_NAME}) # Library configuration -if (DEFINED LOCKFREE_CACHE_COHERENT) - target_compile_definitions(${PROJECT_NAME} INTERFACE LOCKFREE_CACHE_COHERENT) -endif() - -if (DEFINED LOCKFREE_CACHELINE_LENGTH) - target_compile_definitions(${PROJECT_NAME} - INTERFACE - LOCKFREE_CACHELINE_LENGTH=${LOCKFREE_CACHELINE_LENGTH} - ) -endif() +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} +) # Only build tests if we're actually working on the library, # not when the library is being used in a project 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. 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/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; } 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. diff --git a/lockfree/lockfree.hpp b/lockfree/lockfree.hpp index 8a1c6c9..e0c296d 100644 --- a/lockfree/lockfree.hpp +++ b/lockfree/lockfree.hpp @@ -33,22 +33,12 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ #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/priority_queue.hpp b/lockfree/mpmc/priority_queue.hpp index e4b573e..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 ***************************/ @@ -63,17 +63,15 @@ 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 + * @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. - * Should only be called from the consumer thread. - * @param[out] Element + * @param[out] element Element popped * @retval Operation success */ bool Pop(T &element); @@ -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(); 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 2fa7886..ca46994 100644 --- a/lockfree/mpmc/queue.hpp +++ b/lockfree/mpmc/queue.hpp @@ -33,13 +33,18 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ #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/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 d00b36a..4a66f76 100644 --- a/lockfree/spsc/bipartite_buf.hpp +++ b/lockfree/spsc/bipartite_buf.hpp @@ -34,13 +34,18 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ #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 @@ -65,7 +70,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 +79,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 +88,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 +97,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 +123,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 +132,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/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 93c736a..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 ***************************/ @@ -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/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 d69a191..6140dba 100755 --- a/lockfree/spsc/queue.hpp +++ b/lockfree/spsc/queue.hpp @@ -34,13 +34,18 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ #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/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 3e16dea..e79d6d5 100755 --- a/lockfree/spsc/ring_buf.hpp +++ b/lockfree/spsc/ring_buf.hpp @@ -34,13 +34,18 @@ * This file is part of lockfree * * Author: Djordje Nedic - * Version: v3.0.0 + * Version: v3.0.1 **************************************************************/ /************************** INCLUDE ***************************/ #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 @@ -65,8 +70,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 +79,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 +88,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,17 +97,17 @@ 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 - * @retval Write success + * @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); /** * @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 + * @param[out] data Array to write the read to + * @retval Read success */ template bool Read(std::array &data); @@ -110,8 +115,8 @@ 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 + * @param[out] data Span to read to + * @retval Read success */ bool Read(std::span data); #endif @@ -122,9 +127,9 @@ 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 - * @retval Write success + * @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,8 +139,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] Array to write the read to - * @retval Write success + * @param[out] data Array to write the read to + * @retval Peek success */ template bool Peek(std::array &data) const; @@ -145,8 +150,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] Span to read to - * @retval Write success + * @param[out] data Span to read to + * @retval Peek success */ #if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) bool Peek(std::span data) const; @@ -158,8 +163,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[in] Number of elements to skip - * @retval Write success + * @param[in] cnt Number of elements to skip + * @retval Skip success */ bool Skip(size_t cnt); 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 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); diff --git a/tests/spsc/priority_queue.cpp b/tests/spsc/priority_queue.cpp index e3d74d4..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 + 1); - }); - - 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;