diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 269ac1f..29932f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,8 @@ jobs: - toolset: gcc-4.6 cxxstd64: "0x" cxxstd32: "0x" + # Workaround for missing std::this_thread::sleep_* functions + cxxflags: -D_GLIBCXX_USE_NANOSLEEP os: ubuntu-latest container: ubuntu:16.04 install: @@ -51,6 +53,8 @@ jobs: - toolset: gcc-4.7 cxxstd64: "11" cxxstd32: "11" + # Workaround for missing std::this_thread::sleep_* functions + cxxflags: -D_GLIBCXX_USE_NANOSLEEP os: ubuntu-latest container: ubuntu:16.04 install: diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 26f4eb4..726eceb 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -2,7 +2,7 @@ # # Copyright (c) 2011 Helge Bahmann # Copyright (c) 2012 Tim Blechmann -# Copyright (c) 2020 Andrey Semashev +# Copyright (c) 2020-2023 Andrey Semashev # # Distributed under the Boost Software License, Version 1.0. (See # accompanying file LICENSE_1_0.txt or copy at @@ -14,8 +14,6 @@ project boost/atomic/test : requirements . multi - /boost/chrono//boost_chrono - /boost/thread//boost_thread /boost/atomic//boost_atomic windows:BOOST_USE_WINDOWS_H gcc,windows:"-lkernel32" diff --git a/test/atomicity.cpp b/test/atomicity.cpp index 16b129d..3688ab4 100644 --- a/test/atomicity.cpp +++ b/test/atomicity.cpp @@ -24,22 +24,18 @@ // operations truly behave atomic if this test program does not // report an error. -#include +#include +#include #include -#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include "test_clock.hpp" /* helper class to let two instances of a function race against each other, with configurable timeout and early abort on detection of error */ @@ -49,29 +45,32 @@ public: /* concurrently run the function in two threads, until either timeout or one of the functions returns "false"; returns true if timeout was reached, or false if early abort and updates timeout accordingly */ - static bool execute(const boost::function & fn, boost::posix_time::time_duration & timeout) + static bool execute(std::function< bool (std::size_t) > const& fn, steady_clock::duration& timeout) { concurrent_runner runner(fn); runner.wait_finish(timeout); return !runner.failure(); } - concurrent_runner(const boost::function & fn) : + concurrent_runner(std::function< bool (std::size_t) > const& fn) : finished_(false), failure_(false) { - boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 0)).swap(first_thread_); - boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 1)).swap(second_thread_); + first_thread_ = std::thread([this, fn]() { thread_function(fn, 0); }); + second_thread_ = std::thread([this, fn]() { thread_function(fn, 1); }); } - void wait_finish(boost::posix_time::time_duration & timeout) + void wait_finish(steady_clock::duration& timeout) { - boost::system_time start = boost::get_system_time(); - boost::system_time end = start + timeout; + steady_clock::time_point start = steady_clock::now(); + steady_clock::time_point end = start + timeout; { - boost::unique_lock< boost::mutex > guard(m_); - while (boost::get_system_time() < end && !finished()) - c_.timed_wait(guard, end); + std::unique_lock< std::mutex > guard(m_); + while (!finished()) + { + if (c_.wait_until(guard, end) == std::cv_status::timeout) + break; + } } finished_.store(true, boost::memory_order_relaxed); @@ -79,7 +78,7 @@ public: first_thread_.join(); second_thread_.join(); - boost::posix_time::time_duration duration = boost::get_system_time() - start; + steady_clock::duration duration = steady_clock::now() - start; if (duration < timeout) timeout = duration; } @@ -95,13 +94,13 @@ public: } private: - void thread_function(boost::function function, std::size_t instance) + void thread_function(std::function< bool (std::size_t) > const& function, std::size_t instance) { while (!finished()) { if (!function(instance)) { - boost::lock_guard< boost::mutex > guard(m_); + std::lock_guard< std::mutex > guard(m_); failure_ = true; finished_.store(true, boost::memory_order_relaxed); c_.notify_all(); @@ -111,17 +110,17 @@ private: } private: - boost::mutex m_; - boost::condition_variable c_; + std::mutex m_; + std::condition_variable c_; boost::atomic finished_; bool failure_; - boost::thread first_thread_; - boost::thread second_thread_; + std::thread first_thread_; + std::thread second_thread_; }; -bool racy_add(volatile unsigned int & value, std::size_t instance) +bool racy_add(unsigned int volatile& value, std::size_t instance) { std::size_t shift = instance * 8; unsigned int mask = 0xff << shift; @@ -150,11 +149,11 @@ double estimate_avg_race_time(void) /* take 10 samples */ for (std::size_t n = 0; n < 10; ++n) { - boost::posix_time::time_duration timeout(0, 0, 10); + steady_clock::duration timeout = std::chrono::seconds(10); volatile unsigned int value(0); bool success = concurrent_runner::execute( - boost::bind(racy_add, boost::ref(value), boost::placeholders::_1), + [&value](std::size_t instance) { return racy_add(value, instance); }, timeout ); @@ -163,7 +162,7 @@ double estimate_avg_race_time(void) BOOST_ERROR("Failed to establish baseline time for reproducing race condition"); } - sum = sum + timeout.total_microseconds(); + sum += std::chrono::duration_cast< std::chrono::microseconds >(timeout).count(); } /* determine maximum likelihood estimate for average time between @@ -177,7 +176,7 @@ double estimate_avg_race_time(void) } template -bool test_arithmetic(boost::atomic & shared_value, std::size_t instance) +bool test_arithmetic(boost::atomic< value_type >& shared_value, std::size_t instance) { std::size_t shift = instance * 8; value_type mask = 0xff << shift; @@ -204,7 +203,7 @@ bool test_arithmetic(boost::atomic & shared_value, std::size_t insta } template -bool test_bitops(boost::atomic & shared_value, std::size_t instance) +bool test_bitops(boost::atomic< value_type >& shared_value, std::size_t instance) { std::size_t shift = instance * 8; value_type mask = 0xff << shift; @@ -247,17 +246,17 @@ int main(int, char *[]) double avg_race_time = estimate_avg_race_time(); /* 5.298 = 0.995 quantile of exponential distribution */ - const boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time)); + const steady_clock::duration timeout = std::chrono::microseconds(static_cast< std::chrono::microseconds::rep >(5.298 * avg_race_time)); { boost::atomic value(0); /* testing two different operations in this loop, therefore enlarge timeout */ - boost::posix_time::time_duration tmp(timeout * 2); + steady_clock::duration tmp(timeout * 2); bool success = concurrent_runner::execute( - boost::bind(test_arithmetic, boost::ref(value), boost::placeholders::_1), + [&value](std::size_t instance) { return test_arithmetic< unsigned int, 0 >(value, instance); }, tmp ); @@ -269,10 +268,10 @@ int main(int, char *[]) /* testing three different operations in this loop, therefore enlarge timeout */ - boost::posix_time::time_duration tmp(timeout * 3); + steady_clock::duration tmp(timeout * 3); bool success = concurrent_runner::execute( - boost::bind(test_bitops, boost::ref(value), boost::placeholders::_1), + [&value](std::size_t instance) { return test_bitops< unsigned int, 0 >(value, instance); }, tmp ); diff --git a/test/atomicity_ref.cpp b/test/atomicity_ref.cpp index d3dae7e..b2b83cd 100644 --- a/test/atomicity_ref.cpp +++ b/test/atomicity_ref.cpp @@ -32,19 +32,14 @@ #include #include -#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include "test_clock.hpp" /* helper class to let two instances of a function race against each other, with configurable timeout and early abort on detection of error */ @@ -54,30 +49,32 @@ public: /* concurrently run the function in two threads, until either timeout or one of the functions returns "false"; returns true if timeout was reached, or false if early abort and updates timeout accordingly */ - static bool execute(const boost::function & fn, boost::posix_time::time_duration & timeout) + static bool execute(std::function< bool (std::size_t) > const& fn, steady_clock::duration& timeout) { concurrent_runner runner(fn); runner.wait_finish(timeout); return !runner.failure(); } - - concurrent_runner(const boost::function & fn) : + concurrent_runner(std::function< bool (std::size_t) > const& fn) : finished_(false), failure_(false) { - boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 0)).swap(first_thread_); - boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 1)).swap(second_thread_); + first_thread_ = std::thread([this, fn]() { thread_function(fn, 0); }); + second_thread_ = std::thread([this, fn]() { thread_function(fn, 1); }); } - void wait_finish(boost::posix_time::time_duration & timeout) + void wait_finish(steady_clock::duration& timeout) { - boost::system_time start = boost::get_system_time(); - boost::system_time end = start + timeout; + steady_clock::time_point start = steady_clock::now(); + steady_clock::time_point end = start + timeout; { - boost::unique_lock< boost::mutex > guard(m_); - while (boost::get_system_time() < end && !finished()) - c_.timed_wait(guard, end); + std::unique_lock< std::mutex > guard(m_); + while (!finished()) + { + if (c_.wait_until(guard, end) == std::cv_status::timeout) + break; + } } finished_.store(true, boost::memory_order_relaxed); @@ -85,7 +82,7 @@ public: first_thread_.join(); second_thread_.join(); - boost::posix_time::time_duration duration = boost::get_system_time() - start; + steady_clock::duration duration = steady_clock::now() - start; if (duration < timeout) timeout = duration; } @@ -101,13 +98,13 @@ public: } private: - void thread_function(boost::function function, std::size_t instance) + void thread_function(std::function< bool (std::size_t) > const& function, std::size_t instance) { while (!finished()) { if (!function(instance)) { - boost::lock_guard< boost::mutex > guard(m_); + std::lock_guard< std::mutex > guard(m_); failure_ = true; finished_.store(true, boost::memory_order_relaxed); c_.notify_all(); @@ -117,17 +114,17 @@ private: } private: - boost::mutex m_; - boost::condition_variable c_; + std::mutex m_; + std::condition_variable c_; boost::atomic finished_; bool failure_; - boost::thread first_thread_; - boost::thread second_thread_; + std::thread first_thread_; + std::thread second_thread_; }; -bool racy_add(volatile unsigned int & value, std::size_t instance) +bool racy_add(unsigned int volatile& value, std::size_t instance) { std::size_t shift = instance * 8; unsigned int mask = 0xff << shift; @@ -154,13 +151,13 @@ double estimate_avg_race_time(void) double sum = 0.0; /* take 10 samples */ - for (std::size_t n = 0; n < 10; n++) + for (std::size_t n = 0; n < 10; ++n) { - boost::posix_time::time_duration timeout(0, 0, 10); + steady_clock::duration timeout = std::chrono::seconds(10); volatile unsigned int value(0); bool success = concurrent_runner::execute( - boost::bind(racy_add, boost::ref(value), boost::placeholders::_1), + [&value](std::size_t instance) { return racy_add(value, instance); }, timeout ); @@ -169,7 +166,7 @@ double estimate_avg_race_time(void) BOOST_ERROR("Failed to establish baseline time for reproducing race condition"); } - sum = sum + timeout.total_microseconds(); + sum += std::chrono::duration_cast< std::chrono::microseconds >(timeout).count(); } /* determine maximum likelihood estimate for average time between @@ -255,17 +252,17 @@ int main(int, char *[]) double avg_race_time = estimate_avg_race_time(); /* 5.298 = 0.995 quantile of exponential distribution */ - const boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time)); + const steady_clock::duration timeout = std::chrono::microseconds(static_cast< std::chrono::microseconds::rep >(5.298 * avg_race_time)); { unsigned int value = 0; /* testing two different operations in this loop, therefore enlarge timeout */ - boost::posix_time::time_duration tmp(timeout * 2); + steady_clock::duration tmp(timeout * 2); bool success = concurrent_runner::execute( - boost::bind(test_arithmetic, boost::ref(value), boost::placeholders::_1), + [&value](std::size_t instance) { return test_arithmetic< unsigned int, 0 >(value, instance); }, tmp ); @@ -277,10 +274,10 @@ int main(int, char *[]) /* testing three different operations in this loop, therefore enlarge timeout */ - boost::posix_time::time_duration tmp(timeout * 3); + steady_clock::duration tmp(timeout * 3); bool success = concurrent_runner::execute( - boost::bind(test_bitops, boost::ref(value), boost::placeholders::_1), + [&value](std::size_t instance) { return test_bitops< unsigned int, 0 >(value, instance); }, tmp ); diff --git a/test/ipc_wait_test_helpers.hpp b/test/ipc_wait_test_helpers.hpp index b2c2bf8..d1fd75c 100644 --- a/test/ipc_wait_test_helpers.hpp +++ b/test/ipc_wait_test_helpers.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Andrey Semashev +// Copyright (c) 2020-2023 Andrey Semashev // // Distributed under the Boost Software License, Version 1.0. // See accompanying file LICENSE_1_0.txt or copy at @@ -12,20 +12,20 @@ #include #include +#include +#include +#include +#include #include -#include #include -#include -#include -#include -#include #include #include #include -#include #include "atomic_wrapper.hpp" #include "lightweight_test_stream.hpp" #include "test_clock.hpp" +#include "test_thread.hpp" +#include "test_barrier.hpp" //! Since some of the tests below are allowed to fail, we retry up to this many times to pass the test BOOST_CONSTEXPR_OR_CONST unsigned int test_retry_count = 5u; @@ -66,7 +66,7 @@ private: T m_value1, m_value2, m_value3; - boost::barrier m_barrier; + test_barrier m_barrier; thread_state m_thread1_state; thread_state m_thread2_state; @@ -85,19 +85,19 @@ public: bool run() { - boost::thread thread1(¬ify_one_test::thread_func, this, &m_thread1_state); - boost::thread thread2(¬ify_one_test::thread_func, this, &m_thread2_state); + test_thread thread1([this]() { this->thread_func(&this->m_thread1_state); }); + test_thread thread2([this]() { this->thread_func(&this->m_thread2_state); }); - m_barrier.wait(); + m_barrier.arrive_and_wait(); test_clock::time_point start_time = test_clock::now(); - boost::this_thread::sleep_for(chrono::milliseconds(200)); + std::this_thread::sleep_for(chrono::milliseconds(200)); m_wrapper.a.store(m_value2, boost::memory_order_release); m_wrapper.a.notify_one(); - boost::this_thread::sleep_for(chrono::milliseconds(200)); + std::this_thread::sleep_for(chrono::milliseconds(200)); m_wrapper.a.store(m_value3, boost::memory_order_release); m_wrapper.a.notify_one(); @@ -154,7 +154,7 @@ public: private: void thread_func(thread_state* state) { - m_barrier.wait(); + m_barrier.arrive_and_wait(); state->m_received_value = m_wrapper.a.wait(m_value1); state->m_wakeup_time = test_clock::now(); @@ -167,7 +167,7 @@ inline void test_notify_one(T value1, T value2, T value3) for (unsigned int i = 0u; i < test_retry_count; ++i) { // Avoid creating IPC atomics on the stack as this breaks on Darwin - boost::scoped_ptr< notify_one_test< Wrapper, T > > test(new notify_one_test< Wrapper, T >(value1, value2, value3)); + std::unique_ptr< notify_one_test< Wrapper, T > > test(new notify_one_test< Wrapper, T >(value1, value2, value3)); if (test->run()) return; } @@ -201,7 +201,7 @@ private: T m_value1, m_value2; - boost::barrier m_barrier; + test_barrier m_barrier; thread_state m_thread1_state; thread_state m_thread2_state; @@ -219,14 +219,14 @@ public: bool run() { - boost::thread thread1(¬ify_all_test::thread_func, this, &m_thread1_state); - boost::thread thread2(¬ify_all_test::thread_func, this, &m_thread2_state); + test_thread thread1([this]() { this->thread_func(&this->m_thread1_state); }); + test_thread thread2([this]() { this->thread_func(&this->m_thread2_state); }); - m_barrier.wait(); + m_barrier.arrive_and_wait(); test_clock::time_point start_time = test_clock::now(); - boost::this_thread::sleep_for(chrono::milliseconds(200)); + std::this_thread::sleep_for(chrono::milliseconds(200)); m_wrapper.a.store(m_value2, boost::memory_order_release); m_wrapper.a.notify_all(); @@ -266,7 +266,7 @@ public: private: void thread_func(thread_state* state) { - m_barrier.wait(); + m_barrier.arrive_and_wait(); state->m_received_value = m_wrapper.a.wait(m_value1); state->m_wakeup_time = test_clock::now(); @@ -279,7 +279,7 @@ inline void test_notify_all(T value1, T value2) for (unsigned int i = 0u; i < test_retry_count; ++i) { // Avoid creating IPC atomics on the stack as this breaks on Darwin - boost::scoped_ptr< notify_all_test< Wrapper, T > > test(new notify_all_test< Wrapper, T >(value1, value2)); + std::unique_ptr< notify_all_test< Wrapper, T > > test(new notify_all_test< Wrapper, T >(value1, value2)); if (test->run()) return; } diff --git a/test/ordering.cpp b/test/ordering.cpp index a7416cd..650cfe2 100644 --- a/test/ordering.cpp +++ b/test/ordering.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2011 Helge Bahmann // Copyright (c) 2012 Tim Blechmann +// Copyright (c) 2023 Andrey Semashev // // Distributed under the Boost Software License, Version 1.0. // See accompanying file LICENSE_1_0.txt or copy at @@ -27,19 +28,18 @@ // fences work as expected if this test program does not // report an error. -#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include +#include "test_clock.hpp" +#include "test_barrier.hpp" // Two threads perform the following operations: // @@ -57,7 +57,7 @@ class total_store_order_test public: total_store_order_test(void); - void run(boost::posix_time::time_duration & timeout); + void run(steady_clock::duration& timeout); bool detected_conflict(void) const { return detected_conflict_; } private: @@ -74,7 +74,7 @@ private: boost::atomic b_; char pad2_[512]; - boost::barrier barrier_; + test_barrier barrier_; int vrfyb1_, vrfya2_; @@ -82,8 +82,8 @@ private: boost::atomic termination_consensus_; bool detected_conflict_; - boost::mutex m_; - boost::condition_variable c_; + std::mutex m_; + std::condition_variable c_; }; template @@ -96,18 +96,21 @@ total_store_order_test::total_store_order_test(void) : } template -void total_store_order_test::run(boost::posix_time::time_duration & timeout) +void total_store_order_test::run(steady_clock::duration& timeout) { - boost::system_time start = boost::get_system_time(); - boost::system_time end = start + timeout; + steady_clock::time_point start = steady_clock::now(); + steady_clock::time_point end = start + timeout; - boost::thread t1(boost::bind(&total_store_order_test::thread1fn, this)); - boost::thread t2(boost::bind(&total_store_order_test::thread2fn, this)); + std::thread t1([this]() { this->thread1fn(); }); + std::thread t2([this]() { this->thread2fn(); }); { - boost::unique_lock< boost::mutex > guard(m_); - while (boost::get_system_time() < end && !detected_conflict_) - c_.timed_wait(guard, end); + std::unique_lock< std::mutex > lock(m_); + while (!detected_conflict_) + { + if (c_.wait_until(lock, end) == std::cv_status::timeout) + break; + } } terminate_threads_.store(true, boost::memory_order_relaxed); @@ -115,7 +118,7 @@ void total_store_order_test::run(boost::posix_time::tim t2.join(); t1.join(); - boost::posix_time::time_duration duration = boost::get_system_time() - start; + steady_clock::duration duration = steady_clock::now() - start; if (duration < timeout) timeout = duration; } @@ -130,11 +133,11 @@ void total_store_order_test::thread1fn(void) a_.store(1, store_order); int b = b_.load(load_order); - barrier_.wait(); + barrier_.arrive_and_wait(); vrfyb1_ = b; - barrier_.wait(); + barrier_.arrive_and_wait(); check_conflict(); @@ -156,10 +159,10 @@ void total_store_order_test::thread1fn(void) termination_consensus_.fetch_xor(4, boost::memory_order_relaxed); - unsigned int delay = rand() % 10000; + unsigned int delay = std::rand() % 10000; a_.store(0, boost::memory_order_relaxed); - barrier_.wait(); + barrier_.arrive_and_wait(); while (delay--) backoff_dummy = delay; @@ -174,11 +177,11 @@ void total_store_order_test::thread2fn(void) b_.store(1, store_order); int a = a_.load(load_order); - barrier_.wait(); + barrier_.arrive_and_wait(); vrfya2_ = a; - barrier_.wait(); + barrier_.arrive_and_wait(); check_conflict(); @@ -200,10 +203,10 @@ void total_store_order_test::thread2fn(void) termination_consensus_.fetch_xor(4, boost::memory_order_relaxed); - unsigned int delay = rand() % 10000; + unsigned int delay = std::rand() % 10000; b_.store(0, boost::memory_order_relaxed); - barrier_.wait(); + barrier_.arrive_and_wait(); while (delay--) backoff_dummy = delay; @@ -215,7 +218,7 @@ void total_store_order_test::check_conflict(void) { if (vrfyb1_ == 0 && vrfya2_ == 0) { - boost::lock_guard< boost::mutex > guard(m_); + std::lock_guard< std::mutex > guard(m_); detected_conflict_ = true; terminate_threads_.store(true, boost::memory_order_relaxed); c_.notify_all(); @@ -229,7 +232,7 @@ void test_seq_cst(void) /* take 10 samples */ for (std::size_t n = 0; n < 10; n++) { - boost::posix_time::time_duration timeout(0, 0, 10); + steady_clock::duration timeout = std::chrono::seconds(10); total_store_order_test test; test.run(timeout); @@ -239,9 +242,10 @@ void test_seq_cst(void) return; } - std::cout << "seq_cst violation with order=relaxed after " << timeout.total_microseconds() << " us\n"; + std::chrono::microseconds timeout_us = std::chrono::duration_cast< std::chrono::microseconds >(timeout); + std::cout << "seq_cst violation with order=relaxed after " << timeout_us.count() << " us\n"; - sum = sum + timeout.total_microseconds(); + sum += timeout_us.count(); } /* determine maximum likelihood estimate for average time between @@ -252,9 +256,10 @@ void test_seq_cst(void) double avg_race_time_995 = avg_race_time_mle * 2 * 10 / 7.44; /* 5.298 = 0.995 quantile of exponential distribution */ - boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time_995)); + std::chrono::microseconds timeout_us(static_cast< std::chrono::microseconds::rep >(5.298 * avg_race_time_995)); + steady_clock::duration timeout = timeout_us; - std::cout << "run seq_cst for " << timeout.total_microseconds() << " us\n"; + std::cout << "run seq_cst for " << timeout_us.count() << " us\n"; total_store_order_test test; test.run(timeout); diff --git a/test/ordering_ref.cpp b/test/ordering_ref.cpp index 8a48277..5d06225 100644 --- a/test/ordering_ref.cpp +++ b/test/ordering_ref.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Andrey Semashev +// Copyright (c) 2020-2023 Andrey Semashev // // Distributed under the Boost Software License, Version 1.0. // See accompanying file LICENSE_1_0.txt or copy at @@ -34,16 +34,14 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include +#include "test_clock.hpp" +#include "test_barrier.hpp" // Two threads perform the following operations: // @@ -61,7 +59,7 @@ class total_store_order_test public: total_store_order_test(void); - void run(boost::posix_time::time_duration & timeout); + void run(steady_clock::duration& timeout); bool detected_conflict(void) const { return detected_conflict_; } private: @@ -80,7 +78,7 @@ private: boost::atomic_ref b_; char pad2_[512]; - boost::barrier barrier_; + test_barrier barrier_; int vrfyb1_, vrfya2_; @@ -88,8 +86,8 @@ private: boost::atomic termination_consensus_; bool detected_conflict_; - boost::mutex m_; - boost::condition_variable c_; + std::mutex m_; + std::condition_variable c_; }; template @@ -102,18 +100,21 @@ total_store_order_test::total_store_order_test(void) : } template -void total_store_order_test::run(boost::posix_time::time_duration & timeout) +void total_store_order_test::run(steady_clock::duration& timeout) { - boost::system_time start = boost::get_system_time(); - boost::system_time end = start + timeout; + steady_clock::time_point start = steady_clock::now(); + steady_clock::time_point end = start + timeout; - boost::thread t1(boost::bind(&total_store_order_test::thread1fn, this)); - boost::thread t2(boost::bind(&total_store_order_test::thread2fn, this)); + std::thread t1([this]() { this->thread1fn(); }); + std::thread t2([this]() { this->thread2fn(); }); { - boost::unique_lock< boost::mutex > guard(m_); - while (boost::get_system_time() < end && !detected_conflict_) - c_.timed_wait(guard, end); + std::unique_lock< std::mutex > lock(m_); + while (!detected_conflict_) + { + if (c_.wait_until(lock, end) == std::cv_status::timeout) + break; + } } terminate_threads_.store(true, boost::memory_order_relaxed); @@ -121,7 +122,7 @@ void total_store_order_test::run(boost::posix_time::tim t2.join(); t1.join(); - boost::posix_time::time_duration duration = boost::get_system_time() - start; + steady_clock::duration duration = steady_clock::now() - start; if (duration < timeout) timeout = duration; } @@ -136,11 +137,11 @@ void total_store_order_test::thread1fn(void) a_.store(1, store_order); int b = b_.load(load_order); - barrier_.wait(); + barrier_.arrive_and_wait(); vrfyb1_ = b; - barrier_.wait(); + barrier_.arrive_and_wait(); check_conflict(); @@ -162,10 +163,10 @@ void total_store_order_test::thread1fn(void) termination_consensus_.fetch_xor(4, boost::memory_order_relaxed); - unsigned int delay = rand() % 10000; + unsigned int delay = std::rand() % 10000; a_.store(0, boost::memory_order_relaxed); - barrier_.wait(); + barrier_.arrive_and_wait(); while (delay--) backoff_dummy = delay; @@ -180,11 +181,11 @@ void total_store_order_test::thread2fn(void) b_.store(1, store_order); int a = a_.load(load_order); - barrier_.wait(); + barrier_.arrive_and_wait(); vrfya2_ = a; - barrier_.wait(); + barrier_.arrive_and_wait(); check_conflict(); @@ -206,10 +207,10 @@ void total_store_order_test::thread2fn(void) termination_consensus_.fetch_xor(4, boost::memory_order_relaxed); - unsigned int delay = rand() % 10000; + unsigned int delay = std::rand() % 10000; b_.store(0, boost::memory_order_relaxed); - barrier_.wait(); + barrier_.arrive_and_wait(); while (delay--) backoff_dummy = delay; @@ -221,7 +222,7 @@ void total_store_order_test::check_conflict(void) { if (vrfyb1_ == 0 && vrfya2_ == 0) { - boost::lock_guard< boost::mutex > guard(m_); + std::lock_guard< std::mutex > guard(m_); detected_conflict_ = true; terminate_threads_.store(true, boost::memory_order_relaxed); c_.notify_all(); @@ -235,7 +236,7 @@ void test_seq_cst(void) /* take 10 samples */ for (std::size_t n = 0; n < 10; n++) { - boost::posix_time::time_duration timeout(0, 0, 10); + steady_clock::duration timeout = std::chrono::seconds(10); total_store_order_test test; test.run(timeout); @@ -245,9 +246,10 @@ void test_seq_cst(void) return; } - std::cout << "seq_cst violation with order=relaxed after " << timeout.total_microseconds() << " us\n"; + std::chrono::microseconds timeout_us = std::chrono::duration_cast< std::chrono::microseconds >(timeout); + std::cout << "seq_cst violation with order=relaxed after " << timeout_us.count() << " us\n"; - sum = sum + timeout.total_microseconds(); + sum += timeout_us.count(); } /* determine maximum likelihood estimate for average time between @@ -258,9 +260,10 @@ void test_seq_cst(void) double avg_race_time_995 = avg_race_time_mle * 2 * 10 / 7.44; /* 5.298 = 0.995 quantile of exponential distribution */ - boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time_995)); + std::chrono::microseconds timeout_us(static_cast< std::chrono::microseconds::rep >(5.298 * avg_race_time_995)); + steady_clock::duration timeout = timeout_us; - std::cout << "run seq_cst for " << timeout.total_microseconds() << " us\n"; + std::cout << "run seq_cst for " << timeout_us.count() << " us\n"; total_store_order_test test; test.run(timeout); diff --git a/test/test_barrier.hpp b/test/test_barrier.hpp new file mode 100644 index 0000000..fe47727 --- /dev/null +++ b/test/test_barrier.hpp @@ -0,0 +1,54 @@ +// Copyright (c) 2023 Andrey Semashev +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_ATOMIC_TEST_BARRIER_HPP_INCLUDED_ +#define BOOST_ATOMIC_TEST_BARRIER_HPP_INCLUDED_ + +#include +#include + +//! A simplified version of thread barrier from Boost.Thread and C++20 std::barrier +class test_barrier +{ +private: + std::mutex m_mutex; + std::condition_variable m_cond; + unsigned int m_generation; + unsigned int m_count; + const unsigned int m_initial_count; + +public: + explicit test_barrier(unsigned int initial_count) : + m_generation(0u), m_count(initial_count), m_initial_count(initial_count) + { + } + + test_barrier(test_barrier const&) = delete; + test_barrier& operator= (test_barrier const&) = delete; + + void arrive_and_wait() + { + std::unique_lock< std::mutex > lock(m_mutex); + + --m_count; + if (m_count == 0u) + { + ++m_generation; + m_count = m_initial_count; + m_cond.notify_all(); + return; + } + + const unsigned int generation = m_generation; + do + { + m_cond.wait(lock); + } + while (m_generation == generation); + } +}; + +#endif // BOOST_ATOMIC_TEST_BARRIER_HPP_INCLUDED_ diff --git a/test/test_clock.hpp b/test/test_clock.hpp index cf3f06d..5af1cee 100644 --- a/test/test_clock.hpp +++ b/test/test_clock.hpp @@ -12,11 +12,17 @@ #include #include #include -#include +#include #endif -#include +#include -namespace chrono = boost::chrono; +namespace chrono = std::chrono; + +#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 40700 +typedef chrono::monotonic_clock steady_clock; +#else +typedef chrono::steady_clock steady_clock; +#endif #if defined(BOOST_WINDOWS) @@ -29,7 +35,7 @@ struct test_clock #else typedef boost::winapi::DWORD_ rep; #endif - typedef boost::milli period; + typedef std::milli period; typedef chrono::duration< rep, period > duration; typedef chrono::time_point< test_clock, duration > time_point; @@ -46,10 +52,8 @@ struct test_clock } }; -#elif defined(BOOST_CHRONO_HAS_CLOCK_STEADY) -typedef chrono::steady_clock test_clock; #else -typedef chrono::system_clock test_clock; +typedef steady_clock test_clock; #endif #endif // BOOST_ATOMIC_TEST_TEST_CLOCK_HPP_INCLUDED_ diff --git a/test/test_thread.hpp b/test/test_thread.hpp new file mode 100644 index 0000000..f6d159f --- /dev/null +++ b/test/test_thread.hpp @@ -0,0 +1,84 @@ +// Copyright (c) 2023 Andrey Semashev +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_ATOMIC_TEST_THREAD_HPP_INCLUDED_ +#define BOOST_ATOMIC_TEST_THREAD_HPP_INCLUDED_ + +#include +#include +#include +#include +#include "test_clock.hpp" + +//! Test thread class with the ability to join the thread with a timeout +class test_thread +{ +private: + std::mutex m_mutex; + std::condition_variable m_cond; + bool m_finished; + std::thread m_thread; + +public: + template< typename Func > + explicit test_thread(Func&& func) : + m_finished(false), + m_thread([this, func]() + { + try + { + func(); + } + catch (...) + { + mark_finished(); + throw; + } + mark_finished(); + }) + { + } + + test_thread(test_thread const&) = delete; + test_thread& operator= (test_thread const&) = delete; + + void join() + { + m_thread.join(); + } + + template< typename Rep, typename Period > + bool try_join_for(std::chrono::duration< Rep, Period > dur) + { + return try_join_until(steady_clock::now() + dur); + } + + template< typename Clock, typename Duration > + bool try_join_until(std::chrono::time_point< Clock, Duration > timeout) + { + { + std::unique_lock< std::mutex > lock(m_mutex); + while (!m_finished) + { + if (m_cond.wait_until(lock, timeout) == std::cv_status::timeout) + return false; + } + } + + join(); + return true; + } + +private: + void mark_finished() + { + std::lock_guard< std::mutex > lock(m_mutex); + m_finished = true; + m_cond.notify_all(); + } +}; + +#endif // BOOST_ATOMIC_TEST_THREAD_HPP_INCLUDED_ diff --git a/test/wait_fuzz.cpp b/test/wait_fuzz.cpp index f944d39..64842fe 100644 --- a/test/wait_fuzz.cpp +++ b/test/wait_fuzz.cpp @@ -13,23 +13,21 @@ #include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include - -namespace chrono = boost::chrono; +#include "test_clock.hpp" +#include "test_barrier.hpp" boost::atomic< unsigned int > g_atomic(0u); BOOST_CONSTEXPR_OR_CONST unsigned int loop_count = 4096u; -void thread_func(boost::barrier* barrier) +void thread_func(test_barrier* barrier) { - barrier->wait(); + barrier->arrive_and_wait(); unsigned int old_count = 0u; while (true) @@ -44,17 +42,17 @@ void thread_func(boost::barrier* barrier) int main() { - const unsigned int thread_count = boost::thread::hardware_concurrency() + 4u; - boost::barrier barrier(thread_count + 1u); - boost::scoped_array< boost::thread > threads(new boost::thread[thread_count]); + const unsigned int thread_count = std::thread::hardware_concurrency() + 4u; + test_barrier barrier(thread_count + 1u); + std::unique_ptr< std::thread[] > threads(new std::thread[thread_count]); for (unsigned int i = 0u; i < thread_count; ++i) - boost::thread(boost::bind(&thread_func, &barrier)).swap(threads[i]); + threads[i] = std::thread([&barrier]() { thread_func(&barrier); }); - barrier.wait(); + barrier.arrive_and_wait(); // Let the threads block on the atomic counter - boost::this_thread::sleep_for(chrono::milliseconds(100)); + std::this_thread::sleep_for(chrono::milliseconds(100)); while (true) { diff --git a/test/wait_test_helpers.hpp b/test/wait_test_helpers.hpp index 9f5d23f..73b38b7 100644 --- a/test/wait_test_helpers.hpp +++ b/test/wait_test_helpers.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Andrey Semashev +// Copyright (c) 2020-2023 Andrey Semashev // // Distributed under the Boost Software License, Version 1.0. // See accompanying file LICENSE_1_0.txt or copy at @@ -12,16 +12,17 @@ #include #include +#include +#include +#include +#include #include -#include #include -#include -#include -#include -#include #include "atomic_wrapper.hpp" #include "lightweight_test_stream.hpp" #include "test_clock.hpp" +#include "test_thread.hpp" +#include "test_barrier.hpp" //! Since some of the tests below are allowed to fail, we retry up to this many times to pass the test BOOST_CONSTEXPR_OR_CONST unsigned int test_retry_count = 5u; @@ -62,7 +63,7 @@ private: T m_value1, m_value2, m_value3; - boost::barrier m_barrier; + test_barrier m_barrier; thread_state m_thread1_state; thread_state m_thread2_state; @@ -81,19 +82,19 @@ public: bool run() { - boost::thread thread1(¬ify_one_test::thread_func, this, &m_thread1_state); - boost::thread thread2(¬ify_one_test::thread_func, this, &m_thread2_state); + test_thread thread1([this]() { this->thread_func(&this->m_thread1_state); }); + test_thread thread2([this]() { this->thread_func(&this->m_thread2_state); }); - m_barrier.wait(); + m_barrier.arrive_and_wait(); test_clock::time_point start_time = test_clock::now(); - boost::this_thread::sleep_for(chrono::milliseconds(200)); + std::this_thread::sleep_for(chrono::milliseconds(200)); m_wrapper.a.store(m_value2, boost::memory_order_release); m_wrapper.a.notify_one(); - boost::this_thread::sleep_for(chrono::milliseconds(200)); + std::this_thread::sleep_for(chrono::milliseconds(200)); m_wrapper.a.store(m_value3, boost::memory_order_release); m_wrapper.a.notify_one(); @@ -141,7 +142,7 @@ public: private: void thread_func(thread_state* state) { - m_barrier.wait(); + m_barrier.arrive_and_wait(); state->m_received_value = m_wrapper.a.wait(m_value1); state->m_wakeup_time = test_clock::now(); @@ -187,7 +188,7 @@ private: T m_value1, m_value2; - boost::barrier m_barrier; + test_barrier m_barrier; thread_state m_thread1_state; thread_state m_thread2_state; @@ -205,14 +206,14 @@ public: bool run() { - boost::thread thread1(¬ify_all_test::thread_func, this, &m_thread1_state); - boost::thread thread2(¬ify_all_test::thread_func, this, &m_thread2_state); + test_thread thread1([this]() { this->thread_func(&this->m_thread1_state); }); + test_thread thread2([this]() { this->thread_func(&this->m_thread2_state); }); - m_barrier.wait(); + m_barrier.arrive_and_wait(); test_clock::time_point start_time = test_clock::now(); - boost::this_thread::sleep_for(chrono::milliseconds(200)); + std::this_thread::sleep_for(chrono::milliseconds(200)); m_wrapper.a.store(m_value2, boost::memory_order_release); m_wrapper.a.notify_all(); @@ -249,7 +250,7 @@ public: private: void thread_func(thread_state* state) { - m_barrier.wait(); + m_barrier.arrive_and_wait(); state->m_received_value = m_wrapper.a.wait(m_value1); state->m_wakeup_time = test_clock::now();