From f2c1e65166f03aff1fa32d40a755bdc037992b00 Mon Sep 17 00:00:00 2001 From: Tim Blechmann Date: Sun, 12 Apr 2026 12:44:08 +0800 Subject: [PATCH 1/4] freelist: forward arguments and remove obsolete construct signatures --- include/boost/lockfree/detail/freelist.hpp | 27 +++------------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/include/boost/lockfree/detail/freelist.hpp b/include/boost/lockfree/detail/freelist.hpp index 7db87db..1b49c3f 100644 --- a/include/boost/lockfree/detail/freelist.hpp +++ b/include/boost/lockfree/detail/freelist.hpp @@ -85,15 +85,6 @@ class alignas( cacheline_bytes ) freelist_stack : Alloc return node; } - template < bool ThreadSafe, bool Bounded, typename ArgumentType > - T* construct( const ArgumentType& arg ) - { - T* node = allocate< ThreadSafe, Bounded >(); - if ( node ) - new ( node ) T( arg ); - return node; - } - template < bool ThreadSafe, bool Bounded, typename ArgumentType > T* construct( ArgumentType&& arg ) { @@ -108,7 +99,7 @@ class alignas( cacheline_bytes ) freelist_stack : Alloc { T* node = allocate< ThreadSafe, Bounded >(); if ( node ) - new ( node ) T( arg1, arg2 ); + new ( node ) T( std::forward< ArgumentType1 >( arg1 ), std::forward< ArgumentType2 >( arg2 ) ); return node; } @@ -454,18 +445,6 @@ class fixed_size_freelist : NodeStorage return node; } - template < bool ThreadSafe, bool Bounded, typename ArgumentType > - T* construct( const ArgumentType& arg ) - { - index_t node_index = allocate< ThreadSafe >(); - if ( node_index == null_handle() ) - return NULL; - - T* node = NodeStorage::nodes() + node_index; - new ( node ) T( arg ); - return node; - } - template < bool ThreadSafe, bool Bounded, typename ArgumentType > T* construct( ArgumentType&& arg ) { @@ -479,14 +458,14 @@ class fixed_size_freelist : NodeStorage } template < bool ThreadSafe, bool Bounded, typename ArgumentType1, typename ArgumentType2 > - T* construct( const ArgumentType1& arg1, const ArgumentType2& arg2 ) + T* construct( ArgumentType1&& arg1, ArgumentType2&& arg2 ) { index_t node_index = allocate< ThreadSafe >(); if ( node_index == null_handle() ) return NULL; T* node = NodeStorage::nodes() + node_index; - new ( node ) T( arg1, arg2 ); + new ( node ) T( std::forward< ArgumentType1 >( arg1 ), std::forward< ArgumentType2 >( arg2 ) ); return node; } From 9adf0c7b1c78bd4f163b27e020589316183bb2a6 Mon Sep 17 00:00:00 2001 From: Tim Blechmann Date: Sun, 12 Apr 2026 12:45:05 +0800 Subject: [PATCH 2/4] queue: improve unsynchronized_push --- include/boost/lockfree/queue.hpp | 35 +++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/include/boost/lockfree/queue.hpp b/include/boost/lockfree/queue.hpp index 91bd660..8c1e64f 100644 --- a/include/boost/lockfree/queue.hpp +++ b/include/boost/lockfree/queue.hpp @@ -393,27 +393,48 @@ class queue * \note Not Thread-safe. If internal memory pool is exhausted and the memory pool is not fixed-sized, a new node * will be allocated from the OS. This may not be lock-free. \throws if memory allocator throws * */ + bool unsynchronized_push( const T& t ) + { + return unsynchronized_push_impl( t ); + } + + /// \copydoc boost::lockfree::queue::unsynchronized_push(const T& t) bool unsynchronized_push( T&& t ) { - node* n = pool.template construct< false, false >( std::forward< T >( t ), pool.null_handle() ); + return unsynchronized_push_impl( std::forward< T >( t ) ); + } + +private: +#ifndef BOOST_DOXYGEN_INVOKED + template < typename U > + bool unsynchronized_push_impl( U&& t ) + { + node* n = pool.template construct< false, false >( std::forward< U >( t ), pool.null_handle() ); if ( n == NULL ) return false; + handle_type node_handle = pool.get_handle( n ); + for ( ;; ) { - tagged_node_handle tail = tail_.load( memory_order_relaxed ); - tagged_node_handle next = tail->next.load( memory_order_relaxed ); - node* next_ptr = next.get_ptr(); + tagged_node_handle tail = tail_.load( memory_order_relaxed ); + node* tail_node = pool.get_pointer( tail ); + tagged_node_handle next = tail_node->next.load( memory_order_relaxed ); + node* next_ptr = pool.get_pointer( next ); if ( next_ptr == 0 ) { - tail->next.store( tagged_node_handle( n, next.get_next_tag() ), memory_order_relaxed ); - tail_.store( tagged_node_handle( n, tail.get_next_tag() ), memory_order_relaxed ); + tail_node->next.store( tagged_node_handle( node_handle, next.get_next_tag() ), memory_order_relaxed ); + tail_.store( tagged_node_handle( node_handle, tail.get_next_tag() ), memory_order_relaxed ); return true; } else - tail_.store( tagged_node_handle( next_ptr, tail.get_next_tag() ), memory_order_relaxed ); + tail_.store( tagged_node_handle( pool.get_handle( next_ptr ), tail.get_next_tag() ), + memory_order_relaxed ); } } +#endif +public: + /** Pops object from queue. * * \post if pop operation is successful, object will be copied to ret. From 26a011d7311cace8bed6c9f971e58dbb874a4c2a Mon Sep 17 00:00:00 2001 From: Tim Blechmann Date: Sun, 12 Apr 2026 12:45:36 +0800 Subject: [PATCH 3/4] stack/queue: improve obtaining of node pointers --- include/boost/lockfree/queue.hpp | 4 ++-- include/boost/lockfree/stack.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/lockfree/queue.hpp b/include/boost/lockfree/queue.hpp index 8c1e64f..9f8c33d 100644 --- a/include/boost/lockfree/queue.hpp +++ b/include/boost/lockfree/queue.hpp @@ -354,11 +354,11 @@ class queue bool do_push_node( node* n ) { - handle_type node_handle = pool.get_handle( n ); - if ( n == NULL ) return false; + handle_type node_handle = pool.get_handle( n ); + for ( ;; ) { tagged_node_handle tail = tail_.load( memory_order_acquire ); node* tail_node = pool.get_pointer( tail ); diff --git a/include/boost/lockfree/stack.hpp b/include/boost/lockfree/stack.hpp index 4489e05..a1eb9b3 100644 --- a/include/boost/lockfree/stack.hpp +++ b/include/boost/lockfree/stack.hpp @@ -619,7 +619,7 @@ class stack tagged_node_handle old_tos = tos.load( detail::memory_order_relaxed ); node* old_tos_pointer = pool.get_pointer( old_tos ); - if ( !pool.get_pointer( old_tos ) ) + if ( !old_tos_pointer ) return false; node* new_tos_ptr = pool.get_pointer( old_tos_pointer->next ); From aa29263f2d61f86dd9bee33370ffafa0cb0538ef Mon Sep 17 00:00:00 2001 From: Tim Blechmann Date: Sun, 12 Apr 2026 12:46:50 +0800 Subject: [PATCH 4/4] test: improve test coverage --- test/queue_test.cpp | 146 +++++++++++++++++++++ test/queue_unbounded_stress_test.cpp | 18 +++ test/spsc_queue_test.cpp | 113 +++++++++++++++++ test/spsc_value_test.cpp | 96 ++++++++++++++ test/stack_test.cpp | 159 +++++++++++++++++++++++ test/stack_unbounded_stress_test.cpp | 27 ++++ test/test_common.hpp | 182 +++++++++++++++++++++++++++ 7 files changed, 741 insertions(+) diff --git a/test/queue_test.cpp b/test/queue_test.cpp index 16ac1bc..ec06e4b 100644 --- a/test/queue_test.cpp +++ b/test/queue_test.cpp @@ -241,4 +241,150 @@ BOOST_AUTO_TEST_CASE( queue_uses_optional ) BOOST_TEST_REQUIRE( pop_to_optional ); } +BOOST_AUTO_TEST_CASE( queue_uses_optional_capacity ) +{ + boost::lockfree::queue< int, boost::lockfree::capacity< 64 > > stk; + + bool pop_to_nullopt = stk.pop( boost::lockfree::uses_optional ) == std::nullopt; + BOOST_TEST_REQUIRE( pop_to_nullopt ); + + stk.push( 53 ); + bool pop_to_optional = stk.pop( boost::lockfree::uses_optional ) == 53; + BOOST_TEST_REQUIRE( pop_to_optional ); +} + #endif + +BOOST_AUTO_TEST_CASE( fixed_size_queue_test_exhausted ) +{ + queue< int, capacity< 2 > > f; + + BOOST_TEST_REQUIRE( f.push( 1 ) ); + BOOST_TEST_REQUIRE( f.push( 2 ) ); + BOOST_TEST_REQUIRE( !f.push( 3 ) ); + + int out; + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == 1 ); + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == 2 ); + BOOST_TEST_REQUIRE( !f.pop( out ) ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( bounded_queue_test_exhausted ) +{ + queue< int > f( 2 ); + + BOOST_TEST_REQUIRE( f.bounded_push( 1 ) ); + BOOST_TEST_REQUIRE( f.bounded_push( 2 ) ); + BOOST_TEST_REQUIRE( !f.bounded_push( 3 ) ); + + int out; + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == 1 ); + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == 2 ); + BOOST_TEST_REQUIRE( !f.pop( out ) ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( queue_unsynchronized_push_const_ref ) +{ + queue< int > f( 64 ); + + BOOST_TEST_REQUIRE( f.empty() ); + + const int a = 42; + const int b = 43; + + f.unsynchronized_push( a ); + f.unsynchronized_push( b ); + + int i1( 0 ), i2( 0 ); + BOOST_TEST_REQUIRE( f.unsynchronized_pop( i1 ) ); + BOOST_TEST_REQUIRE( i1 == 42 ); + BOOST_TEST_REQUIRE( f.unsynchronized_pop( i2 ) ); + BOOST_TEST_REQUIRE( i2 == 43 ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( queue_consume_one_capacity_test ) +{ + queue< int, capacity< 64 > > f; + + BOOST_TEST_REQUIRE( f.empty() ); + + f.push( 10 ); + f.push( 20 ); + + bool success1 = f.consume_one( []( int i ) { + BOOST_TEST_REQUIRE( i == 10 ); + } ); + + bool success2 = f.consume_one( []( int i ) { + BOOST_TEST_REQUIRE( i == 20 ); + } ); + + BOOST_TEST_REQUIRE( success1 ); + BOOST_TEST_REQUIRE( success2 ); + BOOST_TEST_REQUIRE( !f.consume_one( []( int ) {} ) ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( queue_consume_all_capacity_test ) +{ + queue< int, capacity< 64 > > f; + + BOOST_TEST_REQUIRE( f.empty() ); + + f.push( 1 ); + f.push( 2 ); + f.push( 3 ); + + size_t consumed = f.consume_all( []( int ) {} ); + + BOOST_TEST_REQUIRE( consumed == 3u ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( queue_empty_pop_test ) +{ + queue< int > f( 64 ); + + int out = 0xDEAD; + BOOST_TEST_REQUIRE( !f.pop( out ) ); + BOOST_TEST_REQUIRE( !f.unsynchronized_pop( out ) ); + BOOST_TEST_REQUIRE( !f.consume_one( []( int ) {} ) ); + BOOST_TEST_REQUIRE( f.consume_all( []( int ) {} ) == 0u ); +} + +BOOST_AUTO_TEST_CASE( queue_push_pop_many ) +{ + queue< int > f( 64 ); + + for ( int i = 0; i < 100; ++i ) + BOOST_TEST_REQUIRE( f.push( i ) ); + + for ( int i = 0; i < 100; ++i ) { + int out; + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == i ); + } + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( queue_push_pop_many_capacity ) +{ + queue< int, capacity< 128 > > f; + + for ( int i = 0; i < 100; ++i ) + BOOST_TEST_REQUIRE( f.push( i ) ); + + for ( int i = 0; i < 100; ++i ) { + int out; + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == i ); + } + BOOST_TEST_REQUIRE( f.empty() ); +} diff --git a/test/queue_unbounded_stress_test.cpp b/test/queue_unbounded_stress_test.cpp index cd59300..3be6bfd 100644 --- a/test/queue_unbounded_stress_test.cpp +++ b/test/queue_unbounded_stress_test.cpp @@ -23,3 +23,21 @@ BOOST_AUTO_TEST_CASE( queue_test_unbounded ) boost::lockfree::queue< long > q( 128 ); tester->run( q ); } + +BOOST_AUTO_TEST_CASE( queue_test_unbounded_asymmetric_many_writers ) +{ + typedef queue_stress_tester< false > tester_type; + std::unique_ptr< tester_type > tester( new tester_type( 1, 4 ) ); + + boost::lockfree::queue< long > q( 128 ); + tester->run( q ); +} + +BOOST_AUTO_TEST_CASE( queue_test_unbounded_asymmetric_many_readers ) +{ + typedef queue_stress_tester< false > tester_type; + std::unique_ptr< tester_type > tester( new tester_type( 4, 1 ) ); + + boost::lockfree::queue< long > q( 128 ); + tester->run( q ); +} diff --git a/test/spsc_queue_test.cpp b/test/spsc_queue_test.cpp index d7949d5..b3d79ca 100644 --- a/test/spsc_queue_test.cpp +++ b/test/spsc_queue_test.cpp @@ -395,4 +395,117 @@ BOOST_AUTO_TEST_CASE( queue_uses_optional ) BOOST_TEST_REQUIRE( pop_to_optional ); } +BOOST_AUTO_TEST_CASE( spsc_queue_uses_optional_capacity ) +{ + boost::lockfree::spsc_queue< int, boost::lockfree::capacity< 64 > > stk; + + bool pop_to_nullopt = stk.pop( boost::lockfree::uses_optional ) == std::nullopt; + BOOST_TEST_REQUIRE( pop_to_nullopt ); + + stk.push( 53 ); + bool pop_to_optional = stk.pop( boost::lockfree::uses_optional ) == 53; + BOOST_TEST_REQUIRE( pop_to_optional ); +} + #endif + +BOOST_AUTO_TEST_CASE( spsc_queue_empty_operations_test ) +{ + spsc_queue< int > f( 64 ); + + int out = 0xDEAD; + BOOST_TEST_REQUIRE( !f.pop( out ) ); + BOOST_TEST_REQUIRE( !f.pop() ); + BOOST_TEST_REQUIRE( !f.consume_one( []( int ) {} ) ); + BOOST_TEST_REQUIRE( f.consume_all( []( int ) {} ) == 0u ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( spsc_queue_push_pop_many_runtime_sized ) +{ + spsc_queue< int > f( 128 ); + + for ( int i = 0; i < 100; ++i ) + BOOST_TEST_REQUIRE( f.push( i ) ); + + for ( int i = 0; i < 100; ++i ) { + int out; + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == i ); + } + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( spsc_queue_push_pop_many_compile_time_sized ) +{ + spsc_queue< int, capacity< 128 > > f; + + for ( int i = 0; i < 100; ++i ) + BOOST_TEST_REQUIRE( f.push( i ) ); + + for ( int i = 0; i < 100; ++i ) { + int out; + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == i ); + } + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( spsc_queue_wraparound_test ) +{ + // Use a small queue to force wraparound + spsc_queue< int, capacity< 4 > > f; + + // Fill and drain multiple times to test wraparound + for ( int round = 0; round < 10; ++round ) { + BOOST_TEST_REQUIRE( f.push( round * 3 + 0 ) ); + BOOST_TEST_REQUIRE( f.push( round * 3 + 1 ) ); + BOOST_TEST_REQUIRE( f.push( round * 3 + 2 ) ); + + int out; + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == round * 3 + 0 ); + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == round * 3 + 1 ); + BOOST_TEST_REQUIRE( f.pop( out ) ); + BOOST_TEST_REQUIRE( out == round * 3 + 2 ); + BOOST_TEST_REQUIRE( f.empty() ); + } +} + +BOOST_AUTO_TEST_CASE( spsc_queue_span_pop_test ) +{ + spsc_queue< int, capacity< 64 > > f; + + int data[ 4 ] = { 10, 20, 30, 40 }; + BOOST_TEST_REQUIRE( f.push( boost::span< const int >( data ) ) == size_t( 4 ) ); + + int out[ 4 ]; + BOOST_TEST_REQUIRE( f.pop( out, 4 ) == size_t( 4 ) ); + BOOST_TEST_REQUIRE( out[ 0 ] == 10 ); + BOOST_TEST_REQUIRE( out[ 1 ] == 20 ); + BOOST_TEST_REQUIRE( out[ 2 ] == 30 ); + BOOST_TEST_REQUIRE( out[ 3 ] == 40 ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( spsc_queue_consume_all_test_compile_time ) +{ + spsc_queue< int, capacity< 64 > > f; + + f.push( 1 ); + f.push( 2 ); + f.push( 3 ); + + std::vector< int > consumed; + size_t count = f.consume_all( [&]( int i ) { + consumed.push_back( i ); + } ); + + BOOST_TEST_REQUIRE( count == 3u ); + BOOST_TEST_REQUIRE( consumed.size() == 3u ); + BOOST_TEST_REQUIRE( consumed[ 0 ] == 1 ); + BOOST_TEST_REQUIRE( consumed[ 1 ] == 2 ); + BOOST_TEST_REQUIRE( consumed[ 2 ] == 3 ); + BOOST_TEST_REQUIRE( f.empty() ); +} diff --git a/test/spsc_value_test.cpp b/test/spsc_value_test.cpp index c0e4828..053bdce 100644 --- a/test/spsc_value_test.cpp +++ b/test/spsc_value_test.cpp @@ -119,3 +119,99 @@ BOOST_AUTO_TEST_CASE( spsc_value_test_move_only_type ) std::unique_ptr< uint64_t > out; BOOST_TEST_REQUIRE( v.read( out ) == false ); } + +#if !defined( BOOST_NO_CXX17_HDR_OPTIONAL ) + +BOOST_AUTO_TEST_CASE( spsc_value_uses_optional ) +{ + boost::lockfree::spsc_value< uint64_t > v; + + bool is_nullopt = v.read( boost::lockfree::uses_optional ) == std::nullopt; + BOOST_TEST_REQUIRE( is_nullopt ); + + v.write( 42 ); + bool is_42 = v.read( boost::lockfree::uses_optional ) == 42; + BOOST_TEST_REQUIRE( is_42 ); + + // no pending value + bool is_nullopt2 = v.read( boost::lockfree::uses_optional ) == std::nullopt; + BOOST_TEST_REQUIRE( is_nullopt2 ); +} + +BOOST_AUTO_TEST_CASE( spsc_value_uses_optional_allow_multiple_reads ) +{ + boost::lockfree::spsc_value< uint64_t, boost::lockfree::allow_multiple_reads< true > > v { 99 }; + + bool is_99 = v.read( boost::lockfree::uses_optional ) == 99; + BOOST_TEST_REQUIRE( is_99 ); + + // Can read again + bool is_99_2 = v.read( boost::lockfree::uses_optional ) == 99; + BOOST_TEST_REQUIRE( is_99_2 ); + + v.write( 100 ); + bool is_100 = v.read( boost::lockfree::uses_optional ) == 100; + BOOST_TEST_REQUIRE( is_100 ); +} + +#endif + +BOOST_AUTO_TEST_CASE( spsc_value_consume_test ) +{ + boost::lockfree::spsc_value< uint64_t > v; + + BOOST_TEST_REQUIRE( v.consume( []( uint64_t ) {} ) == false ); + + v.write( 123 ); + bool consumed = v.consume( []( uint64_t val ) { + BOOST_TEST_REQUIRE( val == 123 ); + } ); + BOOST_TEST_REQUIRE( consumed ); + + // no more value available + BOOST_TEST_REQUIRE( v.consume( []( uint64_t ) {} ) == false ); +} + +BOOST_AUTO_TEST_CASE( spsc_value_consume_allow_multiple_reads ) +{ + boost::lockfree::spsc_value< uint64_t, boost::lockfree::allow_multiple_reads< true > > v { 77 }; + + bool consumed = v.consume( []( uint64_t val ) { + BOOST_TEST_REQUIRE( val == 77 ); + } ); + BOOST_TEST_REQUIRE( consumed ); + + // can consume again with allow_multiple_reads + consumed = v.consume( []( uint64_t val ) { + BOOST_TEST_REQUIRE( val == 77 ); + } ); + BOOST_TEST_REQUIRE( consumed ); +} + +BOOST_AUTO_TEST_CASE( spsc_value_rapid_overwrite ) +{ + boost::lockfree::spsc_value< uint64_t > v; + + // Write multiple values without reading + for ( uint64_t i = 0; i < 100; ++i ) + v.write( i ); + + // Should read the most recently available value + uint64_t out {}; + BOOST_TEST_REQUIRE( v.read( out ) == true ); + // The value may be any of the written values, but typically the last or second-to-last + // due to the triple-buffer. Just verify we get *something* valid. + BOOST_TEST_REQUIRE( out < 100 ); +} + +BOOST_AUTO_TEST_CASE( spsc_value_initialized_with_value ) +{ + boost::lockfree::spsc_value< uint64_t > v { 42 }; + + uint64_t out {}; + BOOST_TEST_REQUIRE( v.read( out ) == true ); + BOOST_TEST_REQUIRE( out == 42 ); + + // second read should fail (not allow_multiple_reads) + BOOST_TEST_REQUIRE( v.read( out ) == false ); +} diff --git a/test/stack_test.cpp b/test/stack_test.cpp index ee3c1e7..cb32058 100644 --- a/test/stack_test.cpp +++ b/test/stack_test.cpp @@ -7,6 +7,9 @@ #include +#include +#include + #define BOOST_TEST_MAIN #ifdef BOOST_LOCKFREE_INCLUDE_TESTS # include @@ -308,4 +311,160 @@ BOOST_AUTO_TEST_CASE( queue_uses_optional ) BOOST_TEST_REQUIRE( pop_to_optional ); } +BOOST_AUTO_TEST_CASE( stack_uses_optional_capacity ) +{ + boost::lockfree::stack< int, boost::lockfree::capacity< 64 > > stk; + + bool pop_to_nullopt = stk.pop( boost::lockfree::uses_optional ) == std::nullopt; + BOOST_TEST_REQUIRE( pop_to_nullopt ); + + stk.push( 53 ); + bool pop_to_optional = stk.pop( boost::lockfree::uses_optional ) == 53; + BOOST_TEST_REQUIRE( pop_to_optional ); +} + #endif + +BOOST_AUTO_TEST_CASE( stack_consume_all_atomic_order_test ) +{ + boost::lockfree::stack< int > f( 64 ); + + BOOST_TEST_REQUIRE( f.empty() ); + + f.push( 1 ); + f.push( 2 ); + f.push( 3 ); + + // consume_all_atomic pops all atomically and then processes in stack order (LIFO: 3, 2, 1) + std::vector< int > consumed_order; + size_t consumed = f.consume_all_atomic( [&]( int i ) { + consumed_order.push_back( i ); + } ); + + BOOST_TEST_REQUIRE( consumed == 3u ); + BOOST_TEST_REQUIRE( consumed_order.size() == 3u ); + BOOST_TEST_REQUIRE( consumed_order[ 0 ] == 3 ); + BOOST_TEST_REQUIRE( consumed_order[ 1 ] == 2 ); + BOOST_TEST_REQUIRE( consumed_order[ 2 ] == 1 ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( stack_consume_all_atomic_reversed_order_test ) +{ + boost::lockfree::stack< int > f( 64 ); + + BOOST_TEST_REQUIRE( f.empty() ); + + f.push( 1 ); + f.push( 2 ); + f.push( 3 ); + + // consume_all_atomic_reversed processes in FIFO order (1, 2, 3) + std::vector< int > consumed_order; + size_t consumed = f.consume_all_atomic_reversed( [&]( int i ) { + consumed_order.push_back( i ); + } ); + + BOOST_TEST_REQUIRE( consumed == 3u ); + BOOST_TEST_REQUIRE( consumed_order.size() == 3u ); + BOOST_TEST_REQUIRE( consumed_order[ 0 ] == 1 ); + BOOST_TEST_REQUIRE( consumed_order[ 1 ] == 2 ); + BOOST_TEST_REQUIRE( consumed_order[ 2 ] == 3 ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( stack_consume_all_atomic_empty_test ) +{ + boost::lockfree::stack< int > f( 64 ); + + BOOST_TEST_REQUIRE( f.consume_all_atomic( []( int ) {} ) == 0u ); + BOOST_TEST_REQUIRE( f.consume_all_atomic_reversed( []( int ) {} ) == 0u ); +} + +BOOST_AUTO_TEST_CASE( stack_empty_operations_test ) +{ + boost::lockfree::stack< int > f( 64 ); + + int out = 0xDEAD; + BOOST_TEST_REQUIRE( !f.pop( out ) ); + BOOST_TEST_REQUIRE( !f.unsynchronized_pop( out ) ); + BOOST_TEST_REQUIRE( !f.consume_one( []( int ) {} ) ); + BOOST_TEST_REQUIRE( f.consume_all( []( int ) {} ) == 0u ); + BOOST_TEST_REQUIRE( f.empty() ); +} + +BOOST_AUTO_TEST_CASE( stack_push_pop_many ) +{ + boost::lockfree::stack< long > stk( 128 ); + + for ( long i = 0; i < 100; ++i ) + BOOST_TEST_REQUIRE( stk.push( i ) ); + + for ( long i = 99; i >= 0; --i ) { + long out; + BOOST_TEST_REQUIRE( stk.pop( out ) ); + BOOST_TEST_REQUIRE( out == i ); + } + BOOST_TEST_REQUIRE( stk.empty() ); +} + +BOOST_AUTO_TEST_CASE( stack_push_pop_many_capacity ) +{ + boost::lockfree::stack< long, boost::lockfree::capacity< 128 > > stk; + + for ( long i = 0; i < 100; ++i ) + BOOST_TEST_REQUIRE( stk.push( i ) ); + + for ( long i = 99; i >= 0; --i ) { + long out; + BOOST_TEST_REQUIRE( stk.pop( out ) ); + BOOST_TEST_REQUIRE( out == i ); + } + BOOST_TEST_REQUIRE( stk.empty() ); +} + +BOOST_AUTO_TEST_CASE( stack_move_unsynchronized ) +{ + boost::lockfree::stack< std::unique_ptr< int > > stk( 128 ); + + stk.unsynchronized_push( std::make_unique< int >( 42 ) ); + + std::unique_ptr< int > out; + BOOST_TEST_REQUIRE( stk.unsynchronized_pop( out ) ); + BOOST_TEST_REQUIRE( *out == 42 ); + BOOST_TEST_REQUIRE( stk.empty() ); +} + +BOOST_AUTO_TEST_CASE( stack_bounded_push_range ) +{ + boost::lockfree::stack< long > stk( 128 ); + + long data[ 3 ] = { 10, 20, 30 }; + BOOST_TEST_REQUIRE( stk.bounded_push( data, data + 3 ) == data + 3 ); + + long out; + BOOST_TEST_REQUIRE( stk.pop( out ) ); + BOOST_TEST_REQUIRE( out == 30 ); + BOOST_TEST_REQUIRE( stk.pop( out ) ); + BOOST_TEST_REQUIRE( out == 20 ); + BOOST_TEST_REQUIRE( stk.pop( out ) ); + BOOST_TEST_REQUIRE( out == 10 ); + BOOST_TEST_REQUIRE( stk.empty() ); +} + +BOOST_AUTO_TEST_CASE( stack_bounded_push_span ) +{ + boost::lockfree::stack< long > stk( 128 ); + + long data[ 3 ] = { 10, 20, 30 }; + BOOST_TEST_REQUIRE( stk.bounded_push( boost::span< const long >( data ) ) == size_t( 3 ) ); + + long out; + BOOST_TEST_REQUIRE( stk.pop( out ) ); + BOOST_TEST_REQUIRE( out == 30 ); + BOOST_TEST_REQUIRE( stk.pop( out ) ); + BOOST_TEST_REQUIRE( out == 20 ); + BOOST_TEST_REQUIRE( stk.pop( out ) ); + BOOST_TEST_REQUIRE( out == 10 ); + BOOST_TEST_REQUIRE( stk.empty() ); +} diff --git a/test/stack_unbounded_stress_test.cpp b/test/stack_unbounded_stress_test.cpp index 05e9c15..57fb868 100644 --- a/test/stack_unbounded_stress_test.cpp +++ b/test/stack_unbounded_stress_test.cpp @@ -24,3 +24,30 @@ BOOST_AUTO_TEST_CASE( stack_test_unbounded ) boost::lockfree::stack< long > q( 128 ); tester->run( q ); } + +BOOST_AUTO_TEST_CASE( stack_test_unbounded_asymmetric ) +{ + typedef queue_stress_tester< false > tester_type; + std::unique_ptr< tester_type > tester( new tester_type( 1, 4 ) ); + + boost::lockfree::stack< long > q( 128 ); + tester->run( q ); +} + +BOOST_AUTO_TEST_CASE( stack_test_consume_all_atomic ) +{ + typedef stack_consume_all_atomic_stress_tester< false > tester_type; + std::unique_ptr< tester_type > tester( new tester_type( 2, 4 ) ); + + boost::lockfree::stack< long > q( 128 ); + tester->run_atomic( q ); +} + +BOOST_AUTO_TEST_CASE( stack_test_consume_all_atomic_reversed ) +{ + typedef stack_consume_all_atomic_stress_tester< false > tester_type; + std::unique_ptr< tester_type > tester( new tester_type( 2, 4 ) ); + + boost::lockfree::stack< long > q( 128 ); + tester->run_atomic_reversed( q ); +} diff --git a/test/test_common.hpp b/test/test_common.hpp index 40577f8..c449eaa 100644 --- a/test/test_common.hpp +++ b/test/test_common.hpp @@ -155,3 +155,185 @@ struct queue_stress_tester } // namespace impl using impl::queue_stress_tester; + +namespace impl { + +template < bool Bounded = false > +struct stack_consume_all_atomic_stress_tester +{ + static const unsigned int buckets = 1 << 13; +#ifndef BOOST_LOCKFREE_STRESS_TEST + static const long node_count = 5000; +#else + static const long node_count = 5000000; +#endif + const int reader_threads; + const int writer_threads; + + std::atomic< int > writers_finished; + + static_hashed_set< long, buckets > data; + static_hashed_set< long, buckets > dequeued; + + std::atomic< int > push_count, pop_count; + + stack_consume_all_atomic_stress_tester( int reader, int writer ) : + reader_threads( reader ), + writer_threads( writer ), + push_count( 0 ), + pop_count( 0 ) + {} + + template < typename stack_type > + void add_items( stack_type& stk ) + { + for ( long i = 0; i != node_count; ++i ) { + long id = generate_id< long >(); + + bool inserted = data.insert( id ); + assert( inserted ); + (void)inserted; + + if ( Bounded ) + while ( stk.bounded_push( id ) == false ) { +#ifdef __VXWORKS__ + std::this_thread::yield(); +#endif + } + else + while ( stk.push( id ) == false ) { +#ifdef __VXWORKS__ + std::this_thread::yield(); +#endif + } + ++push_count; + } + writers_finished += 1; + } + + template < typename stack_type > + void get_items_atomic( stack_type& stk ) + { + for ( ;; ) { + size_t consumed = stk.consume_all_atomic( [ & ]( long id ) { + bool erased = data.erase( id ); + bool inserted = dequeued.insert( id ); + (void)erased; + (void)inserted; + assert( erased ); + assert( inserted ); + ++pop_count; + } ); + + if ( consumed == 0 && writers_finished.load() == writer_threads ) + break; + +#ifdef __VXWORKS__ + std::this_thread::yield(); +#endif + } + + // drain remaining + stk.consume_all_atomic( [ & ]( long id ) { + bool erased = data.erase( id ); + bool inserted = dequeued.insert( id ); + (void)erased; + (void)inserted; + assert( erased ); + assert( inserted ); + ++pop_count; + } ); + } + + template < typename stack_type > + void get_items_atomic_reversed( stack_type& stk ) + { + for ( ;; ) { + size_t consumed = stk.consume_all_atomic_reversed( [ & ]( long id ) { + bool erased = data.erase( id ); + bool inserted = dequeued.insert( id ); + (void)erased; + (void)inserted; + assert( erased ); + assert( inserted ); + ++pop_count; + } ); + + if ( consumed == 0 && writers_finished.load() == writer_threads ) + break; + +#ifdef __VXWORKS__ + std::this_thread::yield(); +#endif + } + + // drain remaining + stk.consume_all_atomic_reversed( [ & ]( long id ) { + bool erased = data.erase( id ); + bool inserted = dequeued.insert( id ); + (void)erased; + (void)inserted; + assert( erased ); + assert( inserted ); + ++pop_count; + } ); + } + + template < typename stack_type, typename ReaderFunc > + void run_impl( stack_type& stk, ReaderFunc reader_func ) + { + BOOST_WARN( stk.is_lock_free() ); + writers_finished.store( 0 ); + + boost::thread_group writer; + boost::thread_group reader; + + BOOST_TEST_REQUIRE( stk.empty() ); + + for ( int i = 0; i != reader_threads; ++i ) + reader.create_thread( [ &, reader_func ] { + reader_func( stk ); + } ); + + for ( int i = 0; i != writer_threads; ++i ) + writer.create_thread( [ & ] { + add_items( stk ); + } ); + + std::cout << "threads created" << std::endl; + + writer.join_all(); + + std::cout << "writer threads joined, waiting for readers" << std::endl; + + reader.join_all(); + + std::cout << "reader threads joined" << std::endl; + + BOOST_TEST_REQUIRE( data.count_nodes() == (size_t)0 ); + BOOST_TEST_REQUIRE( stk.empty() ); + + BOOST_TEST_REQUIRE( push_count == pop_count ); + BOOST_TEST_REQUIRE( push_count == writer_threads * node_count ); + } + + template < typename stack_type > + void run_atomic( stack_type& stk ) + { + run_impl( stk, [ this ]( stack_type& s ) { + get_items_atomic( s ); + } ); + } + + template < typename stack_type > + void run_atomic_reversed( stack_type& stk ) + { + run_impl( stk, [ this ]( stack_type& s ) { + get_items_atomic_reversed( s ); + } ); + } +}; + +} // namespace impl + +using impl::stack_consume_all_atomic_stress_tester;