early-access version 3088

This commit is contained in:
pineappleEA
2022-11-05 15:35:56 +01:00
parent 4e4fc25ce3
commit b601909c6d
35519 changed files with 5996896 additions and 860 deletions

View File

@@ -0,0 +1,51 @@
# Boost.Atomic Library test Jamfile
#
# Copyright (c) 2011 Helge Bahmann
# Copyright (c) 2012 Tim Blechmann
# Copyright (c) 2020 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)
import testing ;
project boost/atomic/test
: requirements
<include>.
<threading>multi
<library>/boost/chrono//boost_chrono
<library>/boost/thread//boost_thread
<library>/boost/atomic//boost_atomic
<target-os>windows:<define>BOOST_USE_WINDOWS_H
<toolset>gcc,<target-os>windows:<linkflags>"-lkernel32"
# Variadic macros and empty macro arguments are used by Boost.Preprocessor even in C++03 mode, which makes gcc and clang complain
<toolset>gcc:<cxxflags>"-Wno-variadic-macros"
<toolset>clang:<cxxflags>"-Wno-c99-extensions"
;
test-suite atomic
: [ run atomic_api.cpp ]
[ run atomic_ref_api.cpp ]
[ run atomic_api.cpp : : : <define>BOOST_ATOMIC_FORCE_FALLBACK : fallback_atomic_api ]
[ run atomic_ref_api.cpp : : : <define>BOOST_ATOMIC_FORCE_FALLBACK : fallback_atomic_ref_api ]
[ run wait_api.cpp ]
[ run wait_ref_api.cpp ]
[ run wait_api.cpp : : : <define>BOOST_ATOMIC_FORCE_FALLBACK : fallback_wait_api ]
[ run wait_ref_api.cpp : : : <define>BOOST_ATOMIC_FORCE_FALLBACK : fallback_wait_ref_api ]
[ run wait_fuzz.cpp ]
[ run wait_fuzz.cpp : : : <define>BOOST_ATOMIC_FORCE_FALLBACK : fallback_wait_fuzz ]
[ run ipc_atomic_api.cpp ]
[ run ipc_atomic_ref_api.cpp ]
[ run ipc_wait_api.cpp ]
[ run ipc_wait_ref_api.cpp ]
[ run atomicity.cpp ]
[ run atomicity_ref.cpp ]
[ run ordering.cpp ]
[ run ordering_ref.cpp ]
[ run lockfree.cpp ]
[ compile-fail cf_arith_void_ptr.cpp ]
[ compile-fail cf_arith_func_ptr.cpp ]
[ compile-fail cf_arith_mem_ptr.cpp ]
[ compile c_implicit_ctor.cpp ]
;

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2020 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_TESTS_ALIGNED_OBJECT_HPP_INCLUDED_
#define BOOST_ATOMIC_TESTS_ALIGNED_OBJECT_HPP_INCLUDED_
#include <cstddef>
#include <new>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
//! A wrapper that creates an object that has at least the specified alignment
template< typename T, std::size_t Alignment >
class aligned_object
{
private:
T* m_p;
unsigned char m_storage[Alignment + sizeof(T)];
public:
aligned_object()
{
m_p = new (get_aligned_storage()) T;
}
explicit aligned_object(T const& value)
{
m_p = new (get_aligned_storage()) T(value);
}
~aligned_object() BOOST_NOEXCEPT
{
m_p->~T();
}
T& get() const BOOST_NOEXCEPT
{
return *m_p;
}
BOOST_DELETED_FUNCTION(aligned_object(aligned_object const&))
BOOST_DELETED_FUNCTION(aligned_object& operator= (aligned_object const&))
private:
unsigned char* get_aligned_storage()
{
#if defined(BOOST_HAS_INTPTR_T)
typedef boost::uintptr_t uintptr_type;
#else
typedef std::size_t uintptr_type;
#endif
unsigned char* p = m_storage;
uintptr_type misalignment = ((uintptr_type)p) & (Alignment - 1u);
if (misalignment > 0)
p += Alignment - misalignment;
return p;
}
};
#endif // BOOST_ATOMIC_TESTS_ALIGNED_OBJECT_HPP_INCLUDED_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2011 Helge Bahmann
// Copyright (c) 2020 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)
#include <boost/atomic.hpp>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include "atomic_wrapper.hpp"
#include "api_test_helpers.hpp"
int main(int, char *[])
{
test_flag_api< boost::atomic_flag >();
test_integral_api< atomic_wrapper, char >();
test_integral_api< atomic_wrapper, signed char >();
test_integral_api< atomic_wrapper, unsigned char >();
test_integral_api< atomic_wrapper, boost::uint8_t >();
test_integral_api< atomic_wrapper, boost::int8_t >();
test_integral_api< atomic_wrapper, short >();
test_integral_api< atomic_wrapper, unsigned short >();
test_integral_api< atomic_wrapper, boost::uint16_t >();
test_integral_api< atomic_wrapper, boost::int16_t >();
test_integral_api< atomic_wrapper, int >();
test_integral_api< atomic_wrapper, unsigned int >();
test_integral_api< atomic_wrapper, boost::uint32_t >();
test_integral_api< atomic_wrapper, boost::int32_t >();
test_integral_api< atomic_wrapper, long >();
test_integral_api< atomic_wrapper, unsigned long >();
test_integral_api< atomic_wrapper, boost::uint64_t >();
test_integral_api< atomic_wrapper, boost::int64_t >();
test_integral_api< atomic_wrapper, long long >();
test_integral_api< atomic_wrapper, unsigned long long >();
#if defined(BOOST_HAS_INT128) && !defined(BOOST_ATOMIC_TESTS_NO_INT128)
test_integral_api< atomic_wrapper, boost::int128_type >();
test_integral_api< atomic_wrapper, boost::uint128_type >();
#endif
test_constexpr_ctor< bool >();
test_constexpr_ctor< char >();
test_constexpr_ctor< short >();
test_constexpr_ctor< int >();
test_constexpr_ctor< long >();
test_constexpr_ctor< long long >();
test_constexpr_ctor< test_enum >();
#if !defined(BOOST_ATOMIC_DETAIL_NO_CXX11_CONSTEXPR_BITWISE_CAST)
// As of gcc 11, clang 12 and MSVC 19.27, compilers don't support __builtin_bit_cast from pointers in constant expressions.
// test_constexpr_ctor< int* >();
test_constexpr_ctor< test_struct< int > >();
#if !defined(BOOST_ATOMIC_NO_FLOATING_POINT)
test_constexpr_ctor< float >();
test_constexpr_ctor< double >();
// We don't test long double as it may include padding bits, which will make the constructor non-constexpr
#endif
#endif
#if !defined(BOOST_ATOMIC_NO_FLOATING_POINT)
test_floating_point_api< atomic_wrapper, float >();
test_floating_point_api< atomic_wrapper, double >();
test_floating_point_api< atomic_wrapper, long double >();
#if defined(BOOST_HAS_FLOAT128) && !defined(BOOST_ATOMIC_TESTS_NO_FLOAT128)
test_floating_point_api< atomic_wrapper, boost::float128_type >();
#endif
#endif
test_pointer_api< atomic_wrapper, int >();
test_enum_api< atomic_wrapper >();
test_struct_api< atomic_wrapper, test_struct< boost::uint8_t > >();
test_struct_api< atomic_wrapper, test_struct< boost::uint16_t > >();
test_struct_api< atomic_wrapper, test_struct< boost::uint32_t > >();
test_struct_api< atomic_wrapper, test_struct< boost::uint64_t > >();
#if defined(BOOST_HAS_INT128)
test_struct_api< atomic_wrapper, test_struct< boost::uint128_type > >();
#endif
// https://svn.boost.org/trac/boost/ticket/10994
test_struct_x2_api< atomic_wrapper, test_struct_x2< boost::uint64_t > >();
// https://svn.boost.org/trac/boost/ticket/9985
test_struct_api< atomic_wrapper, test_struct< double > >();
test_large_struct_api< atomic_wrapper >();
// Test that boost::atomic<T> only requires T to be trivially copyable.
// Other non-trivial constructors are allowed.
test_struct_with_ctor_api< atomic_wrapper >();
#if !defined(BOOST_ATOMIC_NO_CLEAR_PADDING)
test_struct_with_padding_api< atomic_wrapper >();
#endif
// Test that fences at least compile
boost::atomic_thread_fence(boost::memory_order_seq_cst);
boost::atomic_signal_fence(boost::memory_order_seq_cst);
return boost::report_errors();
}

View File

@@ -0,0 +1,96 @@
// Copyright (c) 2020-2021 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)
#include <boost/atomic/atomic_ref.hpp>
#include <boost/memory_order.hpp>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include "aligned_object.hpp"
#include "atomic_wrapper.hpp"
#include "api_test_helpers.hpp"
int main(int, char *[])
{
test_integral_api< atomic_ref_wrapper, char >();
test_integral_api< atomic_ref_wrapper, signed char >();
test_integral_api< atomic_ref_wrapper, unsigned char >();
test_integral_api< atomic_ref_wrapper, boost::uint8_t >();
test_integral_api< atomic_ref_wrapper, boost::int8_t >();
test_integral_api< atomic_ref_wrapper, short >();
test_integral_api< atomic_ref_wrapper, unsigned short >();
test_integral_api< atomic_ref_wrapper, boost::uint16_t >();
test_integral_api< atomic_ref_wrapper, boost::int16_t >();
test_integral_api< atomic_ref_wrapper, int >();
test_integral_api< atomic_ref_wrapper, unsigned int >();
test_integral_api< atomic_ref_wrapper, boost::uint32_t >();
test_integral_api< atomic_ref_wrapper, boost::int32_t >();
test_integral_api< atomic_ref_wrapper, long >();
test_integral_api< atomic_ref_wrapper, unsigned long >();
test_integral_api< atomic_ref_wrapper, boost::uint64_t >();
test_integral_api< atomic_ref_wrapper, boost::int64_t >();
test_integral_api< atomic_ref_wrapper, long long >();
test_integral_api< atomic_ref_wrapper, unsigned long long >();
#if defined(BOOST_HAS_INT128) && !defined(BOOST_ATOMIC_TESTS_NO_INT128)
test_integral_api< atomic_ref_wrapper, boost::int128_type >();
test_integral_api< atomic_ref_wrapper, boost::uint128_type >();
#endif
#if !defined(BOOST_ATOMIC_NO_FLOATING_POINT)
test_floating_point_api< atomic_ref_wrapper, float >();
test_floating_point_api< atomic_ref_wrapper, double >();
test_floating_point_api< atomic_ref_wrapper, long double >();
#if defined(BOOST_HAS_FLOAT128) && !defined(BOOST_ATOMIC_TESTS_NO_FLOAT128)
test_floating_point_api< atomic_ref_wrapper, boost::float128_type >();
#endif
#endif
test_pointer_api< atomic_ref_wrapper, int >();
test_enum_api< atomic_ref_wrapper >();
test_struct_api< atomic_ref_wrapper, test_struct< boost::uint8_t > >();
test_struct_api< atomic_ref_wrapper, test_struct< boost::uint16_t > >();
test_struct_api< atomic_ref_wrapper, test_struct< boost::uint32_t > >();
test_struct_api< atomic_ref_wrapper, test_struct< boost::uint64_t > >();
#if defined(BOOST_HAS_INT128)
test_struct_api< atomic_ref_wrapper, test_struct< boost::uint128_type > >();
#endif
// https://svn.boost.org/trac/boost/ticket/10994
test_struct_x2_api< atomic_ref_wrapper, test_struct_x2< boost::uint64_t > >();
// https://svn.boost.org/trac/boost/ticket/9985
test_struct_api< atomic_ref_wrapper, test_struct< double > >();
test_large_struct_api< atomic_ref_wrapper >();
// Test that boost::atomic_ref<T> only requires T to be trivially copyable.
// Other non-trivial constructors are allowed.
test_struct_with_ctor_api< atomic_ref_wrapper >();
#if !defined(BOOST_ATOMIC_NO_CLEAR_PADDING)
test_struct_with_padding_api< atomic_ref_wrapper >();
#endif
#if !defined(BOOST_ATOMIC_DETAIL_NO_CXX17_DEDUCTION_GUIDES)
if (boost::atomic_ref< int >::is_always_lock_free)
{
aligned_object< int, boost::atomic_ref< int >::required_alignment > object(0);
boost::atomic_ref r(object.get());
r.store(1, boost::memory_order_relaxed);
}
#endif
if (boost::atomic_ref< int >::is_always_lock_free)
{
aligned_object< int, boost::atomic_ref< int >::required_alignment > object(0);
boost::atomic_ref< int > r = boost::make_atomic_ref(object.get());
r.store(1, boost::memory_order_relaxed);
}
return boost::report_errors();
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2020 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_ATOMIC_WRAPPER_HPP_INCLUDED_
#define BOOST_ATOMIC_TEST_ATOMIC_WRAPPER_HPP_INCLUDED_
#include <boost/atomic/atomic.hpp>
#include <boost/atomic/atomic_ref.hpp>
#include <boost/atomic/ipc_atomic.hpp>
#include <boost/atomic/ipc_atomic_ref.hpp>
#include <boost/config.hpp>
#include "aligned_object.hpp"
//! Wrapper type for atomic template
template< typename T >
struct atomic_wrapper
{
typedef boost::atomic< T > atomic_type;
typedef atomic_type& atomic_reference_type;
atomic_type a;
BOOST_DEFAULTED_FUNCTION(atomic_wrapper(), {})
explicit atomic_wrapper(T const& value) : a(value) {}
};
//! Wrapper type for atomic_ref template
template< typename T >
struct atomic_ref_wrapper
{
typedef boost::atomic_ref< T > atomic_type;
typedef atomic_type const& atomic_reference_type;
aligned_object< T, atomic_type::required_alignment > object;
const atomic_type a;
atomic_ref_wrapper() : a(object.get()) {}
explicit atomic_ref_wrapper(T const& value) : object(value), a(object.get()) {}
};
//! Wrapper type for ipc_atomic template
template< typename T >
struct ipc_atomic_wrapper
{
typedef boost::ipc_atomic< T > atomic_type;
typedef atomic_type& atomic_reference_type;
atomic_type a;
BOOST_DEFAULTED_FUNCTION(ipc_atomic_wrapper(), {})
explicit ipc_atomic_wrapper(T const& value) : a(value) {}
};
//! Wrapper type for atomic_ref template
template< typename T >
struct ipc_atomic_ref_wrapper
{
typedef boost::ipc_atomic_ref< T > atomic_type;
typedef atomic_type const& atomic_reference_type;
aligned_object< T, atomic_type::required_alignment > object;
const atomic_type a;
ipc_atomic_ref_wrapper() : a(object.get()) {}
explicit ipc_atomic_ref_wrapper(T const& value) : object(value), a(object.get()) {}
};
#endif // BOOST_ATOMIC_TEST_ATOMIC_WRAPPER_HPP_INCLUDED_

View File

@@ -0,0 +1,283 @@
// Copyright (c) 2011 Helge Bahmann
//
// 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)
// Attempt to determine whether the operations on atomic variables
// do in fact behave atomically: Let multiple threads race modifying
// a shared atomic variable and verify that it behaves as expected.
//
// We assume that "observable race condition" events are exponentially
// distributed, with unknown "average time between observable races"
// (which is just the reciprocal of exp distribution parameter lambda).
// Use a non-atomic implementation that intentionally exhibits a
// (hopefully tight) race to compute the maximum-likelihood estimate
// for this time. From this, compute an estimate that covers the
// unknown value with 0.995 confidence (using chi square quantile).
//
// Use this estimate to pick a timeout for the race tests of the
// atomic implementations such that under the assumed distribution
// we get 0.995 probability to detect a race (if there is one).
//
// Overall this yields 0.995 * 0.995 > 0.99 confidence that the
// operations truly behave atomic if this test program does not
// report an error.
#include <boost/atomic.hpp>
#include <cstddef>
#include <algorithm>
#include <boost/config.hpp>
#include <boost/ref.hpp>
#include <boost/function.hpp>
#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/thread_time.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/lock_types.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/core/lightweight_test.hpp>
/* helper class to let two instances of a function race against each
other, with configurable timeout and early abort on detection of error */
class concurrent_runner
{
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<bool(std::size_t)> & fn, boost::posix_time::time_duration & timeout)
{
concurrent_runner runner(fn);
runner.wait_finish(timeout);
return !runner.failure();
}
concurrent_runner(const boost::function<bool(std::size_t)> & 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_);
}
void wait_finish(boost::posix_time::time_duration & timeout)
{
boost::system_time start = boost::get_system_time();
boost::system_time end = start + timeout;
{
boost::unique_lock< boost::mutex > guard(m_);
while (boost::get_system_time() < end && !finished())
c_.timed_wait(guard, end);
}
finished_.store(true, boost::memory_order_relaxed);
first_thread_.join();
second_thread_.join();
boost::posix_time::time_duration duration = boost::get_system_time() - start;
if (duration < timeout)
timeout = duration;
}
bool finished(void) const BOOST_NOEXCEPT_OR_NOTHROW
{
return finished_.load(boost::memory_order_relaxed);
}
bool failure(void) const BOOST_NOEXCEPT_OR_NOTHROW
{
return failure_;
}
private:
void thread_function(boost::function<bool(std::size_t)> function, std::size_t instance)
{
while (!finished())
{
if (!function(instance))
{
boost::lock_guard< boost::mutex > guard(m_);
failure_ = true;
finished_.store(true, boost::memory_order_relaxed);
c_.notify_all();
break;
}
}
}
private:
boost::mutex m_;
boost::condition_variable c_;
boost::atomic<bool> finished_;
bool failure_;
boost::thread first_thread_;
boost::thread second_thread_;
};
bool racy_add(volatile unsigned int & value, std::size_t instance)
{
std::size_t shift = instance * 8;
unsigned int mask = 0xff << shift;
for (std::size_t n = 0; n < 255; ++n)
{
unsigned int tmp = value;
value = tmp + (1 << shift);
if ((tmp & mask) != (n << shift))
return false;
}
unsigned int tmp = value;
value = tmp & ~mask;
if ((tmp & mask) != mask)
return false;
return true;
}
/* compute estimate for average time between races being observable, in usecs */
double estimate_avg_race_time(void)
{
double sum = 0.0;
/* take 10 samples */
for (std::size_t n = 0; n < 10; ++n)
{
boost::posix_time::time_duration timeout(0, 0, 10);
volatile unsigned int value(0);
bool success = concurrent_runner::execute(
boost::bind(racy_add, boost::ref(value), boost::placeholders::_1),
timeout
);
if (success)
{
BOOST_ERROR("Failed to establish baseline time for reproducing race condition");
}
sum = sum + timeout.total_microseconds();
}
/* determine maximum likelihood estimate for average time between
race observations */
double avg_race_time_mle = (sum / 10);
/* pick 0.995 confidence (7.44 = chi square 0.995 confidence) */
double avg_race_time_995 = avg_race_time_mle * 2 * 10 / 7.44;
return avg_race_time_995;
}
template<typename value_type, std::size_t shift_>
bool test_arithmetic(boost::atomic<value_type> & shared_value, std::size_t instance)
{
std::size_t shift = instance * 8;
value_type mask = 0xff << shift;
value_type increment = 1 << shift;
value_type expected = 0;
for (std::size_t n = 0; n < 255; ++n)
{
value_type tmp = shared_value.fetch_add(increment, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift) )
return false;
++expected;
}
for (std::size_t n = 0; n < 255; ++n)
{
value_type tmp = shared_value.fetch_sub(increment, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift) )
return false;
--expected;
}
return true;
}
template<typename value_type, std::size_t shift_>
bool test_bitops(boost::atomic<value_type> & shared_value, std::size_t instance)
{
std::size_t shift = instance * 8;
value_type mask = 0xff << shift;
value_type expected = 0;
for (std::size_t k = 0; k < 8; ++k)
{
value_type mod = 1u << k;
value_type tmp = shared_value.fetch_or(mod << shift, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift))
return false;
expected = expected | mod;
}
for (std::size_t k = 0; k < 8; ++k)
{
value_type tmp = shared_value.fetch_and(~(1u << (shift + k)), boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift))
return false;
expected = expected & ~(1u << k);
}
for (std::size_t k = 0; k < 8; ++k)
{
value_type mod = 255u ^ (1u << k);
value_type tmp = shared_value.fetch_xor(mod << shift, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift))
return false;
expected = expected ^ mod;
}
value_type tmp = shared_value.fetch_and(~mask, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift) )
return false;
return true;
}
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));
{
boost::atomic<unsigned int> value(0);
/* testing two different operations in this loop, therefore
enlarge timeout */
boost::posix_time::time_duration tmp(timeout * 2);
bool success = concurrent_runner::execute(
boost::bind(test_arithmetic<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
tmp
);
BOOST_TEST(success); // concurrent arithmetic error
}
{
boost::atomic<unsigned int> value(0);
/* testing three different operations in this loop, therefore
enlarge timeout */
boost::posix_time::time_duration tmp(timeout * 3);
bool success = concurrent_runner::execute(
boost::bind(test_bitops<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
tmp
);
BOOST_TEST(success); // concurrent bit operations error
}
return boost::report_errors();
}

View File

@@ -0,0 +1,291 @@
// Copyright (c) 2020 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)
// This test is based on atomicity.cpp by Helge Bahmann. The test
// Was modified to use atomic_ref template instead of atomic.
// Attempt to determine whether the operations on atomic variables
// do in fact behave atomically: Let multiple threads race modifying
// a shared atomic variable and verify that it behaves as expected.
//
// We assume that "observable race condition" events are exponentially
// distributed, with unknown "average time between observable races"
// (which is just the reciprocal of exp distribution parameter lambda).
// Use a non-atomic implementation that intentionally exhibits a
// (hopefully tight) race to compute the maximum-likelihood estimate
// for this time. From this, compute an estimate that covers the
// unknown value with 0.995 confidence (using chi square quantile).
//
// Use this estimate to pick a timeout for the race tests of the
// atomic implementations such that under the assumed distribution
// we get 0.995 probability to detect a race (if there is one).
//
// Overall this yields 0.995 * 0.995 > 0.99 confidence that the
// operations truly behave atomic if this test program does not
// report an error.
#include <boost/memory_order.hpp>
#include <boost/atomic/atomic.hpp>
#include <boost/atomic/atomic_ref.hpp>
#include <cstddef>
#include <algorithm>
#include <boost/config.hpp>
#include <boost/ref.hpp>
#include <boost/function.hpp>
#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/thread_time.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/lock_types.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/core/lightweight_test.hpp>
/* helper class to let two instances of a function race against each
other, with configurable timeout and early abort on detection of error */
class concurrent_runner
{
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<bool(std::size_t)> & fn, boost::posix_time::time_duration & timeout)
{
concurrent_runner runner(fn);
runner.wait_finish(timeout);
return !runner.failure();
}
concurrent_runner(const boost::function<bool(std::size_t)> & 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_);
}
void wait_finish(boost::posix_time::time_duration & timeout)
{
boost::system_time start = boost::get_system_time();
boost::system_time end = start + timeout;
{
boost::unique_lock< boost::mutex > guard(m_);
while (boost::get_system_time() < end && !finished())
c_.timed_wait(guard, end);
}
finished_.store(true, boost::memory_order_relaxed);
first_thread_.join();
second_thread_.join();
boost::posix_time::time_duration duration = boost::get_system_time() - start;
if (duration < timeout)
timeout = duration;
}
bool finished(void) const BOOST_NOEXCEPT_OR_NOTHROW
{
return finished_.load(boost::memory_order_relaxed);
}
bool failure(void) const BOOST_NOEXCEPT_OR_NOTHROW
{
return failure_;
}
private:
void thread_function(boost::function<bool(std::size_t)> function, std::size_t instance)
{
while (!finished())
{
if (!function(instance))
{
boost::lock_guard< boost::mutex > guard(m_);
failure_ = true;
finished_.store(true, boost::memory_order_relaxed);
c_.notify_all();
break;
}
}
}
private:
boost::mutex m_;
boost::condition_variable c_;
boost::atomic<bool> finished_;
bool failure_;
boost::thread first_thread_;
boost::thread second_thread_;
};
bool racy_add(volatile unsigned int & value, std::size_t instance)
{
std::size_t shift = instance * 8;
unsigned int mask = 0xff << shift;
for (std::size_t n = 0; n < 255; ++n)
{
unsigned int tmp = value;
value = tmp + (1 << shift);
if ((tmp & mask) != (n << shift))
return false;
}
unsigned int tmp = value;
value = tmp & ~mask;
if ((tmp & mask) != mask)
return false;
return true;
}
/* compute estimate for average time between races being observable, in usecs */
double estimate_avg_race_time(void)
{
double sum = 0.0;
/* take 10 samples */
for (std::size_t n = 0; n < 10; n++)
{
boost::posix_time::time_duration timeout(0, 0, 10);
volatile unsigned int value(0);
bool success = concurrent_runner::execute(
boost::bind(racy_add, boost::ref(value), boost::placeholders::_1),
timeout
);
if (success)
{
BOOST_ERROR("Failed to establish baseline time for reproducing race condition");
}
sum = sum + timeout.total_microseconds();
}
/* determine maximum likelihood estimate for average time between
race observations */
double avg_race_time_mle = (sum / 10);
/* pick 0.995 confidence (7.44 = chi square 0.995 confidence) */
double avg_race_time_995 = avg_race_time_mle * 2 * 10 / 7.44;
return avg_race_time_995;
}
template<typename value_type, std::size_t shift_>
bool test_arithmetic(value_type& shared_value, std::size_t instance)
{
std::size_t shift = instance * 8;
value_type mask = 0xff << shift;
value_type increment = 1 << shift;
value_type expected = 0;
boost::atomic_ref<value_type> shared_value_ref(shared_value);
for (std::size_t n = 0; n < 255; ++n)
{
value_type tmp = shared_value_ref.fetch_add(increment, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift) )
return false;
++expected;
}
for (std::size_t n = 0; n < 255; ++n)
{
value_type tmp = shared_value_ref.fetch_sub(increment, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift) )
return false;
--expected;
}
return true;
}
template<typename value_type, std::size_t shift_>
bool test_bitops(value_type& shared_value, std::size_t instance)
{
std::size_t shift = instance * 8;
value_type mask = 0xff << shift;
value_type expected = 0;
boost::atomic_ref<value_type> shared_value_ref(shared_value);
for (std::size_t k = 0; k < 8; ++k)
{
value_type mod = 1u << k;
value_type tmp = shared_value_ref.fetch_or(mod << shift, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift))
return false;
expected = expected | mod;
}
for (std::size_t k = 0; k < 8; ++k)
{
value_type tmp = shared_value_ref.fetch_and(~(1u << (shift + k)), boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift))
return false;
expected = expected & ~(1u << k);
}
for (std::size_t k = 0; k < 8; ++k)
{
value_type mod = 255u ^ (1u << k);
value_type tmp = shared_value_ref.fetch_xor(mod << shift, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift))
return false;
expected = expected ^ mod;
}
value_type tmp = shared_value_ref.fetch_and(~mask, boost::memory_order_relaxed);
if ( (tmp & mask) != (expected << shift) )
return false;
return true;
}
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));
{
unsigned int value = 0;
/* testing two different operations in this loop, therefore
enlarge timeout */
boost::posix_time::time_duration tmp(timeout * 2);
bool success = concurrent_runner::execute(
boost::bind(test_arithmetic<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
tmp
);
BOOST_TEST(success); // concurrent arithmetic error
}
{
unsigned int value = 0;
/* testing three different operations in this loop, therefore
enlarge timeout */
boost::posix_time::time_duration tmp(timeout * 3);
bool success = concurrent_runner::execute(
boost::bind(test_bitops<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
tmp
);
BOOST_TEST(success); // concurrent bit operations error
}
return boost::report_errors();
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2018 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)
// The test verifies that atomic<T> has an implicit conversion constructor from T.
// This can only be tested in C++17 because it has mandated copy elision. Previous C++ versions
// also require atomic<> to have a copy or move constructor, which it does not.
#if __cplusplus >= 201703L
#include <boost/atomic.hpp>
#include <boost/static_assert.hpp>
#include <boost/config.hpp>
#include <type_traits>
int main(int, char *[])
{
static_assert(std::is_convertible< int, boost::atomic< int > >::value, "boost::atomic<T> does not have an implicit constructor from T");
boost::atomic< short > a = 10;
(void)a;
return 0;
}
#else // __cplusplus >= 201703L
int main(int, char *[])
{
return 0;
}
#endif // __cplusplus >= 201703L

View File

@@ -0,0 +1,17 @@
// Copyright (c) 2017 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)
#include <boost/atomic.hpp>
int main(int, char *[])
{
// The test verifies that atomic<> does not provide arithmetic operations on function pointers
typedef void (*func_ptr)(int);
boost::atomic< func_ptr > a;
a.fetch_add(1);
return 1;
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2017 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)
#include <boost/atomic.hpp>
struct foo
{
int n;
};
int main(int, char *[])
{
// The test verifies that atomic<> does not provide arithmetic operations on member pointers
typedef int (foo::*mem_ptr);
boost::atomic< mem_ptr > a;
a.fetch_add(1);
return 1;
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) 2017 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)
#include <boost/atomic.hpp>
int main(int, char *[])
{
// The test verifies that atomic<> does not provide arithmetic operations on void pointers
boost::atomic< void* > a;
a.fetch_add(1);
return 1;
}

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2020 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)
#include <boost/atomic/ipc_atomic.hpp>
#include <boost/atomic/ipc_atomic_flag.hpp>
#include <boost/atomic/capabilities.hpp>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include "atomic_wrapper.hpp"
#include "api_test_helpers.hpp"
int main(int, char *[])
{
#if BOOST_ATOMIC_FLAG_LOCK_FREE == 2
test_flag_api< boost::ipc_atomic_flag >();
#endif
test_lock_free_integral_api< ipc_atomic_wrapper, boost::uint8_t >();
test_lock_free_integral_api< ipc_atomic_wrapper, boost::int8_t >();
test_lock_free_integral_api< ipc_atomic_wrapper, boost::uint16_t >();
test_lock_free_integral_api< ipc_atomic_wrapper, boost::int16_t >();
test_lock_free_integral_api< ipc_atomic_wrapper, boost::uint32_t >();
test_lock_free_integral_api< ipc_atomic_wrapper, boost::int32_t >();
test_lock_free_integral_api< ipc_atomic_wrapper, boost::uint64_t >();
test_lock_free_integral_api< ipc_atomic_wrapper, boost::int64_t >();
#if defined(BOOST_HAS_INT128) && !defined(BOOST_ATOMIC_TESTS_NO_INT128)
test_lock_free_integral_api< ipc_atomic_wrapper, boost::int128_type >();
test_lock_free_integral_api< ipc_atomic_wrapper, boost::uint128_type >();
#endif
#if !defined(BOOST_ATOMIC_NO_FLOATING_POINT)
test_lock_free_floating_point_api< ipc_atomic_wrapper, float >();
test_lock_free_floating_point_api< ipc_atomic_wrapper, double >();
test_lock_free_floating_point_api< ipc_atomic_wrapper, long double >();
#if defined(BOOST_HAS_FLOAT128) && !defined(BOOST_ATOMIC_TESTS_NO_FLOAT128)
test_lock_free_floating_point_api< ipc_atomic_wrapper, boost::float128_type >();
#endif
#endif
test_lock_free_pointer_api< ipc_atomic_wrapper, int >();
test_lock_free_enum_api< ipc_atomic_wrapper >();
return boost::report_errors();
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2020-2021 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)
#include <boost/atomic/ipc_atomic_ref.hpp>
#include <boost/memory_order.hpp>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include "aligned_object.hpp"
#include "atomic_wrapper.hpp"
#include "api_test_helpers.hpp"
int main(int, char *[])
{
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::uint8_t >();
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::int8_t >();
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::uint16_t >();
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::int16_t >();
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::uint32_t >();
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::int32_t >();
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::uint64_t >();
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::int64_t >();
#if defined(BOOST_HAS_INT128) && !defined(BOOST_ATOMIC_TESTS_NO_INT128)
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::int128_type >();
test_lock_free_integral_api< ipc_atomic_ref_wrapper, boost::uint128_type >();
#endif
#if !defined(BOOST_ATOMIC_NO_FLOATING_POINT)
test_lock_free_floating_point_api< ipc_atomic_ref_wrapper, float >();
test_lock_free_floating_point_api< ipc_atomic_ref_wrapper, double >();
test_lock_free_floating_point_api< ipc_atomic_ref_wrapper, long double >();
#if defined(BOOST_HAS_FLOAT128) && !defined(BOOST_ATOMIC_TESTS_NO_FLOAT128)
test_lock_free_floating_point_api< ipc_atomic_ref_wrapper, boost::float128_type >();
#endif
#endif
test_lock_free_pointer_api< ipc_atomic_ref_wrapper, int >();
test_lock_free_enum_api< ipc_atomic_ref_wrapper >();
#if !defined(BOOST_ATOMIC_DETAIL_NO_CXX17_DEDUCTION_GUIDES)
if (boost::ipc_atomic_ref< int >::is_always_lock_free)
{
aligned_object< int, boost::ipc_atomic_ref< int >::required_alignment > object(0);
boost::ipc_atomic_ref r(object.get());
r.store(1, boost::memory_order_relaxed);
}
#endif
if (boost::ipc_atomic_ref< int >::is_always_lock_free)
{
aligned_object< int, boost::ipc_atomic_ref< int >::required_alignment > object(0);
boost::ipc_atomic_ref< int > r = boost::make_ipc_atomic_ref(object.get());
r.store(1, boost::memory_order_relaxed);
}
return boost::report_errors();
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) 2020 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)
#include <boost/atomic/ipc_atomic.hpp>
#include <boost/atomic/ipc_atomic_flag.hpp>
#include <boost/atomic/capabilities.hpp>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include "ipc_wait_test_helpers.hpp"
int main(int, char *[])
{
test_flag_wait_notify_api();
test_wait_notify_api< ipc_atomic_wrapper, boost::uint8_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT8_IPC_WAIT_NOTIFY);
test_wait_notify_api< ipc_atomic_wrapper, boost::uint16_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT16_IPC_WAIT_NOTIFY);
test_wait_notify_api< ipc_atomic_wrapper, boost::uint32_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT32_IPC_WAIT_NOTIFY);
test_wait_notify_api< ipc_atomic_wrapper, boost::uint64_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT64_IPC_WAIT_NOTIFY);
#if defined(BOOST_HAS_INT128) && !defined(BOOST_ATOMIC_TESTS_NO_INT128)
test_wait_notify_api< ipc_atomic_wrapper, boost::uint128_type >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT128_IPC_WAIT_NOTIFY);
#endif
return boost::report_errors();
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2020 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)
#include <boost/atomic/ipc_atomic_ref.hpp>
#include <boost/atomic/capabilities.hpp>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include "ipc_wait_test_helpers.hpp"
int main(int, char *[])
{
test_wait_notify_api< ipc_atomic_ref_wrapper, boost::uint8_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT8_IPC_WAIT_NOTIFY);
test_wait_notify_api< ipc_atomic_ref_wrapper, boost::uint16_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT16_IPC_WAIT_NOTIFY);
test_wait_notify_api< ipc_atomic_ref_wrapper, boost::uint32_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT32_IPC_WAIT_NOTIFY);
test_wait_notify_api< ipc_atomic_ref_wrapper, boost::uint64_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT64_IPC_WAIT_NOTIFY);
#if defined(BOOST_HAS_INT128) && !defined(BOOST_ATOMIC_TESTS_NO_INT128)
test_wait_notify_api< ipc_atomic_ref_wrapper, boost::uint128_type >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT128_IPC_WAIT_NOTIFY);
#endif
return boost::report_errors();
}

View File

@@ -0,0 +1,336 @@
// Copyright (c) 2020 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_IPC_WAIT_TEST_HELPERS_HPP_INCLUDED_
#define BOOST_ATOMIC_TEST_IPC_WAIT_TEST_HELPERS_HPP_INCLUDED_
#include <boost/memory_order.hpp>
#include <boost/atomic/ipc_atomic_flag.hpp>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <boost/config.hpp>
#include <boost/chrono/chrono.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>
#include <boost/atomic/capabilities.hpp>
#include <boost/atomic/ipc_atomic_flag.hpp>
#include <boost/type_traits/integral_constant.hpp>
#include <boost/smart_ptr/scoped_ptr.hpp>
#include "atomic_wrapper.hpp"
#include "lightweight_test_stream.hpp"
#include "test_clock.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;
//! The test verifies that the wait operation returns immediately if the passed value does not match the atomic value
template< template< typename > class Wrapper, typename T >
inline void test_wait_value_mismatch(T value1, T value2)
{
Wrapper< T > m_wrapper(value1);
T received_value = m_wrapper.a.wait(value2);
BOOST_TEST(received_value == value1);
}
/*!
* The test verifies that notify_one releases one blocked thread and that the released thread receives the modified atomic value.
*
* Technically, this test is allowed to fail since wait() is allowed to return spuriously. However, normally this should not happen.
*/
template< template< typename > class Wrapper, typename T >
class notify_one_test
{
private:
struct thread_state
{
T m_received_value;
test_clock::time_point m_wakeup_time;
explicit thread_state(T value) : m_received_value(value)
{
}
};
private:
Wrapper< T > m_wrapper;
char m_padding[1024];
T m_value1, m_value2, m_value3;
boost::barrier m_barrier;
thread_state m_thread1_state;
thread_state m_thread2_state;
public:
explicit notify_one_test(T value1, T value2, T value3) :
m_wrapper(value1),
m_value1(value1),
m_value2(value2),
m_value3(value3),
m_barrier(3),
m_thread1_state(value1),
m_thread2_state(value1)
{
}
bool run()
{
boost::thread thread1(&notify_one_test::thread_func, this, &m_thread1_state);
boost::thread thread2(&notify_one_test::thread_func, this, &m_thread2_state);
m_barrier.wait();
test_clock::time_point start_time = test_clock::now();
boost::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));
m_wrapper.a.store(m_value3, boost::memory_order_release);
m_wrapper.a.notify_one();
if (!thread1.try_join_for(chrono::seconds(3)))
{
BOOST_ERROR("Thread 1 failed to join");
std::abort();
}
if (!thread2.try_join_for(chrono::seconds(3)))
{
BOOST_ERROR("Thread 2 failed to join");
std::abort();
}
thread_state* first_state = &m_thread1_state;
thread_state* second_state = &m_thread2_state;
if (second_state->m_wakeup_time < first_state->m_wakeup_time)
std::swap(first_state, second_state);
if (m_wrapper.a.has_native_wait_notify())
{
if ((first_state->m_wakeup_time - start_time) < chrono::milliseconds(200))
{
std::cout << "notify_one_test: first thread woke up too soon: " << chrono::duration_cast< chrono::milliseconds >(first_state->m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
if ((first_state->m_wakeup_time - start_time) >= chrono::milliseconds(400))
{
std::cout << "notify_one_test: first thread woke up too late: " << chrono::duration_cast< chrono::milliseconds >(first_state->m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
if ((second_state->m_wakeup_time - start_time) < chrono::milliseconds(400))
{
std::cout << "notify_one_test: second thread woke up too soon: " << chrono::duration_cast< chrono::milliseconds >(second_state->m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
BOOST_TEST_EQ(first_state->m_received_value, m_value2);
BOOST_TEST_EQ(second_state->m_received_value, m_value3);
}
else
{
// With the emulated wait/notify the threads are most likely to return prior to notify
BOOST_TEST(first_state->m_received_value == m_value2 || first_state->m_received_value == m_value3);
BOOST_TEST(second_state->m_received_value == m_value2 || second_state->m_received_value == m_value3);
}
return true;
}
private:
void thread_func(thread_state* state)
{
m_barrier.wait();
state->m_received_value = m_wrapper.a.wait(m_value1);
state->m_wakeup_time = test_clock::now();
}
};
template< template< typename > class Wrapper, typename T >
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));
if (test->run())
return;
}
BOOST_ERROR("notify_one_test could not complete because blocked thread wake up too soon");
}
/*!
* The test verifies that notify_all releases all blocked threads and that the released threads receive the modified atomic value.
*
* Technically, this test is allowed to fail since wait() is allowed to return spuriously. However, normally this should not happen.
*/
template< template< typename > class Wrapper, typename T >
class notify_all_test
{
private:
struct thread_state
{
T m_received_value;
test_clock::time_point m_wakeup_time;
explicit thread_state(T value) : m_received_value(value)
{
}
};
private:
Wrapper< T > m_wrapper;
char m_padding[1024];
T m_value1, m_value2;
boost::barrier m_barrier;
thread_state m_thread1_state;
thread_state m_thread2_state;
public:
explicit notify_all_test(T value1, T value2) :
m_wrapper(value1),
m_value1(value1),
m_value2(value2),
m_barrier(3),
m_thread1_state(value1),
m_thread2_state(value1)
{
}
bool run()
{
boost::thread thread1(&notify_all_test::thread_func, this, &m_thread1_state);
boost::thread thread2(&notify_all_test::thread_func, this, &m_thread2_state);
m_barrier.wait();
test_clock::time_point start_time = test_clock::now();
boost::this_thread::sleep_for(chrono::milliseconds(200));
m_wrapper.a.store(m_value2, boost::memory_order_release);
m_wrapper.a.notify_all();
if (!thread1.try_join_for(chrono::seconds(3)))
{
BOOST_ERROR("Thread 1 failed to join");
std::abort();
}
if (!thread2.try_join_for(chrono::seconds(3)))
{
BOOST_ERROR("Thread 2 failed to join");
std::abort();
}
if (m_wrapper.a.has_native_wait_notify())
{
if ((m_thread1_state.m_wakeup_time - start_time) < chrono::milliseconds(200))
{
std::cout << "notify_all_test: first thread woke up too soon: " << chrono::duration_cast< chrono::milliseconds >(m_thread1_state.m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
if ((m_thread2_state.m_wakeup_time - start_time) < chrono::milliseconds(200))
{
std::cout << "notify_all_test: second thread woke up too soon: " << chrono::duration_cast< chrono::milliseconds >(m_thread2_state.m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
}
BOOST_TEST_EQ(m_thread1_state.m_received_value, m_value2);
BOOST_TEST_EQ(m_thread2_state.m_received_value, m_value2);
return true;
}
private:
void thread_func(thread_state* state)
{
m_barrier.wait();
state->m_received_value = m_wrapper.a.wait(m_value1);
state->m_wakeup_time = test_clock::now();
}
};
template< template< typename > class Wrapper, typename T >
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));
if (test->run())
return;
}
BOOST_ERROR("notify_all_test could not complete because blocked thread wake up too soon");
}
//! Invokes all wait/notify tests
template< template< typename > class Wrapper, typename T >
void test_wait_notify_api_impl(T value1, T value2, T value3, boost::true_type)
{
test_wait_value_mismatch< Wrapper >(value1, value2);
test_notify_one< Wrapper >(value1, value2, value3);
test_notify_all< Wrapper >(value1, value2);
}
template< template< typename > class Wrapper, typename T >
inline void test_wait_notify_api_impl(T, T, T, boost::false_type)
{
}
//! Invokes all wait/notify tests, if the atomic type is lock-free
template< template< typename > class Wrapper, typename T >
inline void test_wait_notify_api(T value1, T value2, T value3)
{
test_wait_notify_api_impl< Wrapper >(value1, value2, value3, boost::integral_constant< bool, Wrapper< T >::atomic_type::is_always_lock_free >());
}
//! Invokes all wait/notify tests, if the atomic type is lock-free
template< template< typename > class Wrapper, typename T >
inline void test_wait_notify_api(T value1, T value2, T value3, int has_native_wait_notify_macro)
{
BOOST_TEST_EQ(Wrapper< T >::atomic_type::always_has_native_wait_notify, (has_native_wait_notify_macro == 2));
test_wait_notify_api< Wrapper >(value1, value2, value3);
}
inline void test_flag_wait_notify_api()
{
#if BOOST_ATOMIC_FLAG_LOCK_FREE == 2
#ifndef BOOST_ATOMIC_NO_ATOMIC_FLAG_INIT
boost::ipc_atomic_flag f = BOOST_ATOMIC_FLAG_INIT;
#else
boost::ipc_atomic_flag f;
#endif
bool received_value = f.wait(true);
BOOST_TEST(!received_value);
f.notify_one();
f.notify_all();
#endif // BOOST_ATOMIC_FLAG_LOCK_FREE == 2
}
#endif // BOOST_ATOMIC_TEST_IPC_WAIT_TEST_HELPERS_HPP_INCLUDED_

View File

@@ -0,0 +1,98 @@
// Copyright (c) 2020 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_LIGHTWEIGHT_TEST_STREAM_HPP_INCLUDED_
#define BOOST_ATOMIC_TEST_LIGHTWEIGHT_TEST_STREAM_HPP_INCLUDED_
#include <iostream>
#include <boost/config.hpp>
struct test_stream_type
{
typedef std::ios_base& (*ios_base_manip)(std::ios_base&);
typedef std::basic_ios< char, std::char_traits< char > >& (*basic_ios_manip)(std::basic_ios< char, std::char_traits< char > >&);
typedef std::ostream& (*stream_manip)(std::ostream&);
template< typename T >
test_stream_type const& operator<< (T const& value) const
{
std::cerr << value;
return *this;
}
test_stream_type const& operator<< (ios_base_manip manip) const
{
std::cerr << manip;
return *this;
}
test_stream_type const& operator<< (basic_ios_manip manip) const
{
std::cerr << manip;
return *this;
}
test_stream_type const& operator<< (stream_manip manip) const
{
std::cerr << manip;
return *this;
}
// Make sure characters are printed as numbers if tests fail
test_stream_type const& operator<< (char value) const
{
std::cerr << static_cast< int >(value);
return *this;
}
test_stream_type const& operator<< (signed char value) const
{
std::cerr << static_cast< int >(value);
return *this;
}
test_stream_type const& operator<< (unsigned char value) const
{
std::cerr << static_cast< unsigned int >(value);
return *this;
}
test_stream_type const& operator<< (short value) const
{
std::cerr << static_cast< int >(value);
return *this;
}
test_stream_type const& operator<< (unsigned short value) const
{
std::cerr << static_cast< unsigned int >(value);
return *this;
}
#if defined(BOOST_HAS_INT128)
// Some GCC versions don't provide output operators for __int128
test_stream_type const& operator<< (boost::int128_type const& v) const
{
std::cerr << static_cast< long long >(v);
return *this;
}
test_stream_type const& operator<< (boost::uint128_type const& v) const
{
std::cerr << static_cast< unsigned long long >(v);
return *this;
}
#endif // defined(BOOST_HAS_INT128)
#if defined(BOOST_HAS_FLOAT128)
// libstdc++ does not provide output operators for __float128
test_stream_type const& operator<< (boost::float128_type const& v) const
{
std::cerr << static_cast< double >(v);
return *this;
}
#endif // defined(BOOST_HAS_FLOAT128)
};
const test_stream_type test_stream = {};
#define BOOST_LIGHTWEIGHT_TEST_OSTREAM test_stream
#include <boost/core/lightweight_test.hpp>
#endif // BOOST_ATOMIC_TEST_LIGHTWEIGHT_TEST_STREAM_HPP_INCLUDED_

View File

@@ -0,0 +1,251 @@
// Copyright (c) 2011 Helge Bahmann
// Copyright (c) 2020 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)
// Verify that definition of the "LOCK_FREE" macros and the
// "is_lock_free" members is consistent and matches expectations.
// Also, if any operation is lock-free, then the platform
// implementation must provide overridden fence implementations.
#include <boost/atomic.hpp>
#include <iostream>
#include <boost/config.hpp>
#include <boost/core/lightweight_test.hpp>
#include "aligned_object.hpp"
static const char* const lock_free_level[] =
{
"never",
"sometimes",
"always"
};
template< typename T >
void verify_lock_free(const char* type_name, int lock_free_macro_val, int lock_free_expect)
{
BOOST_TEST(lock_free_macro_val >= 0 && lock_free_macro_val <= 2);
BOOST_TEST_EQ(lock_free_macro_val, lock_free_expect);
boost::atomic<T> value;
if (lock_free_macro_val == 0)
BOOST_TEST(!value.is_lock_free());
if (lock_free_macro_val == 2)
BOOST_TEST(value.is_lock_free());
BOOST_TEST_EQ(boost::atomic<T>::is_always_lock_free, (lock_free_expect == 2));
std::cout << "atomic<" << type_name << "> is " << lock_free_level[lock_free_macro_val] << " lock free\n";
// atomic<T> may use larger storage than sizeof(T) to achieve lock-free property. In this case atomic_ref<T> may not be lock-free.
if (sizeof(boost::atomic<T>) == sizeof(T))
{
aligned_object<T, boost::atomic_ref<T>::required_alignment> object;
boost::atomic_ref<T> ref(object.get());
BOOST_TEST_EQ(ref.is_lock_free(), value.is_lock_free());
BOOST_TEST_EQ(boost::atomic_ref<T>::is_always_lock_free, boost::atomic<T>::is_always_lock_free);
}
}
#if (defined(__GNUC__) || defined(__SUNPRO_CC)) && defined(__i386__)
#define EXPECT_CHAR_LOCK_FREE 2
#define EXPECT_SHORT_LOCK_FREE 2
#define EXPECT_INT_LOCK_FREE 2
#define EXPECT_LONG_LOCK_FREE 2
#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) ||\
defined(__i586__) || defined(__i686__) || defined(__SSE__)
#define EXPECT_LLONG_LOCK_FREE 2
#else
#define EXPECT_LLONG_LOCK_FREE 0
#endif
#define EXPECT_INT128_LOCK_FREE 0
#define EXPECT_POINTER_LOCK_FREE 2
#define EXPECT_BOOL_LOCK_FREE 2
#elif (defined(__GNUC__) || defined(__SUNPRO_CC)) && defined(__x86_64__)
#define EXPECT_CHAR_LOCK_FREE 2
#define EXPECT_SHORT_LOCK_FREE 2
#define EXPECT_INT_LOCK_FREE 2
#define EXPECT_LONG_LOCK_FREE 2
#define EXPECT_LLONG_LOCK_FREE 2
#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)
#define EXPECT_INT128_LOCK_FREE 2
#else
#define EXPECT_INT128_LOCK_FREE 0
#endif
#define EXPECT_POINTER_LOCK_FREE 2
#define EXPECT_BOOL_LOCK_FREE 2
#elif defined(__GNUC__) && (defined(__POWERPC__) || defined(__PPC__))
#define EXPECT_CHAR_LOCK_FREE 2
#define EXPECT_CHAR16_T_LOCK_FREE 2
#define EXPECT_CHAR32_T_LOCK_FREE 2
#define EXPECT_WCHAR_T_LOCK_FREE 2
#define EXPECT_SHORT_LOCK_FREE 2
#define EXPECT_INT_LOCK_FREE 2
#define EXPECT_LONG_LOCK_FREE 2
#if defined(__powerpc64__)
#define EXPECT_LLONG_LOCK_FREE 2
#else
#define EXPECT_LLONG_LOCK_FREE 0
#endif
#define EXPECT_INT128_LOCK_FREE 0
#define EXPECT_POINTER_LOCK_FREE 2
#define EXPECT_BOOL_LOCK_FREE 2
#elif defined(__GNUC__) && defined(__alpha__)
#define EXPECT_CHAR_LOCK_FREE 2
#define EXPECT_CHAR16_T_LOCK_FREE 2
#define EXPECT_CHAR32_T_LOCK_FREE 2
#define EXPECT_WCHAR_T_LOCK_FREE 2
#define EXPECT_SHORT_LOCK_FREE 2
#define EXPECT_INT_LOCK_FREE 2
#define EXPECT_LONG_LOCK_FREE 2
#define EXPECT_LLONG_LOCK_FREE 2
#define EXPECT_INT128_LOCK_FREE 0
#define EXPECT_POINTER_LOCK_FREE 2
#define EXPECT_BOOL_LOCK_FREE 2
#elif defined(__GNUC__) && defined(__aarch64__)
#define EXPECT_CHAR_LOCK_FREE 2
#define EXPECT_SHORT_LOCK_FREE 2
#define EXPECT_INT_LOCK_FREE 2
#define EXPECT_LONG_LOCK_FREE 2
#define EXPECT_LLONG_LOCK_FREE 2
#define EXPECT_INT128_LOCK_FREE 2
#define EXPECT_POINTER_LOCK_FREE 2
#define EXPECT_BOOL_LOCK_FREE 2
#elif defined(__GNUC__) && defined(__arm__) &&\
(\
(defined(__ARM_ARCH) && __ARM_ARCH >= 6) ||\
defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) ||\
defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) ||\
defined(__ARM_ARCH_6ZK__) ||\
defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) ||\
defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) ||\
defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7S__) ||\
defined(__ARM_ARCH_8A__)\
)
#define EXPECT_CHAR_LOCK_FREE 2
#define EXPECT_SHORT_LOCK_FREE 2
#define EXPECT_INT_LOCK_FREE 2
#define EXPECT_LONG_LOCK_FREE 2
#if !(defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6Z__)\
|| ((defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6ZK__)) && defined(__thumb__)) || defined(__ARM_ARCH_7M__))
#define EXPECT_LLONG_LOCK_FREE 2
#else
#define EXPECT_LLONG_LOCK_FREE 0
#endif
#define EXPECT_INT128_LOCK_FREE 0
#define EXPECT_POINTER_LOCK_FREE 2
#define EXPECT_BOOL_LOCK_FREE 2
#elif defined(__linux__) && defined(__arm__)
#define EXPECT_CHAR_LOCK_FREE 2
#define EXPECT_SHORT_LOCK_FREE 2
#define EXPECT_INT_LOCK_FREE 2
#define EXPECT_LONG_LOCK_FREE 2
#define EXPECT_LLONG_LOCK_FREE 0
#define EXPECT_INT128_LOCK_FREE 0
#define EXPECT_POINTER_LOCK_FREE 2
#define EXPECT_BOOL_LOCK_FREE 2
#elif (defined(__GNUC__) || defined(__SUNPRO_CC)) && (defined(__sparcv8plus) || defined(__sparc_v9__))
#define EXPECT_CHAR_LOCK_FREE 2
#define EXPECT_SHORT_LOCK_FREE 2
#define EXPECT_INT_LOCK_FREE 2
#define EXPECT_LONG_LOCK_FREE 2
#define EXPECT_LLONG_LOCK_FREE 2
#define EXPECT_INT128_LOCK_FREE 0
#define EXPECT_POINTER_LOCK_FREE 2
#define EXPECT_BOOL_LOCK_FREE 2
#elif defined(BOOST_USE_WINDOWS_H) || defined(_WIN32_CE) || defined(BOOST_MSVC) || defined(BOOST_INTEL_WIN) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
#define EXPECT_CHAR_LOCK_FREE 2
#define EXPECT_SHORT_LOCK_FREE 2
#define EXPECT_INT_LOCK_FREE 2
#define EXPECT_LONG_LOCK_FREE 2
#if defined(_WIN64) || defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) || defined(_M_AMD64) || defined(_M_IA64) || (_MSC_VER >= 1700 && (defined(_M_ARM) || defined(_M_ARM64)))
#define EXPECT_LLONG_LOCK_FREE 2
#else
#define EXPECT_LLONG_LOCK_FREE 0
#endif
#define EXPECT_INT128_LOCK_FREE 0
#define EXPECT_POINTER_LOCK_FREE 2
#define EXPECT_BOOL_LOCK_FREE 2
#else
#define EXPECT_CHAR_LOCK_FREE 0
#define EXPECT_SHORT_LOCK_FREE 0
#define EXPECT_INT_LOCK_FREE 0
#define EXPECT_LONG_LOCK_FREE 0
#define EXPECT_LLONG_LOCK_FREE 0
#define EXPECT_INT128_LOCK_FREE 0
#define EXPECT_POINTER_LOCK_FREE 0
#define EXPECT_BOOL_LOCK_FREE 0
#endif
int main(int, char *[])
{
verify_lock_free<char>("char", BOOST_ATOMIC_CHAR_LOCK_FREE, EXPECT_CHAR_LOCK_FREE);
verify_lock_free<short>("short", BOOST_ATOMIC_SHORT_LOCK_FREE, EXPECT_SHORT_LOCK_FREE);
verify_lock_free<int>("int", BOOST_ATOMIC_INT_LOCK_FREE, EXPECT_INT_LOCK_FREE);
verify_lock_free<long>("long", BOOST_ATOMIC_LONG_LOCK_FREE, EXPECT_LONG_LOCK_FREE);
#ifdef BOOST_HAS_LONG_LONG
verify_lock_free<long long>("long long", BOOST_ATOMIC_LLONG_LOCK_FREE, EXPECT_LLONG_LOCK_FREE);
#endif
#ifdef BOOST_HAS_INT128
verify_lock_free<boost::int128_type>("int128", BOOST_ATOMIC_INT128_LOCK_FREE, EXPECT_INT128_LOCK_FREE);
#endif
verify_lock_free<void *>("void *", BOOST_ATOMIC_POINTER_LOCK_FREE, EXPECT_SHORT_LOCK_FREE);
verify_lock_free<bool>("bool", BOOST_ATOMIC_BOOL_LOCK_FREE, EXPECT_BOOL_LOCK_FREE);
#ifndef BOOST_ATOMIC_NO_FLOATING_POINT
verify_lock_free<float>("float", BOOST_ATOMIC_FLOAT_LOCK_FREE,
sizeof(float) == 1 ? EXPECT_CHAR_LOCK_FREE : (sizeof(float) == 2 ? EXPECT_SHORT_LOCK_FREE :
(sizeof(float) <= 4 ? EXPECT_INT_LOCK_FREE : (sizeof(float) <= 8 ? EXPECT_LLONG_LOCK_FREE : (sizeof(float) <= 16 ? EXPECT_INT128_LOCK_FREE : 0)))));
verify_lock_free<double>("double", BOOST_ATOMIC_DOUBLE_LOCK_FREE,
sizeof(double) == 1 ? EXPECT_CHAR_LOCK_FREE : (sizeof(double) == 2 ? EXPECT_SHORT_LOCK_FREE :
(sizeof(double) <= 4 ? EXPECT_INT_LOCK_FREE : (sizeof(double) <= 8 ? EXPECT_LLONG_LOCK_FREE : (sizeof(double) <= 16 ? EXPECT_INT128_LOCK_FREE : 0)))));
verify_lock_free<long double>("long double", BOOST_ATOMIC_LONG_DOUBLE_LOCK_FREE,
sizeof(long double) == 1 ? EXPECT_CHAR_LOCK_FREE : (sizeof(long double) == 2 ? EXPECT_SHORT_LOCK_FREE :
(sizeof(long double) <= 4 ? EXPECT_INT_LOCK_FREE : (sizeof(long double) <= 8 ? EXPECT_LLONG_LOCK_FREE : (sizeof(long double) <= 16 ? EXPECT_INT128_LOCK_FREE : 0)))));
#if defined(BOOST_HAS_INT128) && defined(BOOST_HAS_FLOAT128)
verify_lock_free<boost::float128_type>("float128", BOOST_ATOMIC_INT128_LOCK_FREE, EXPECT_INT128_LOCK_FREE);
#endif
#endif // BOOST_ATOMIC_NO_FLOATING_POINT
bool any_lock_free =
BOOST_ATOMIC_CHAR_LOCK_FREE > 0 ||
BOOST_ATOMIC_SHORT_LOCK_FREE > 0 ||
BOOST_ATOMIC_INT_LOCK_FREE > 0 ||
BOOST_ATOMIC_LONG_LOCK_FREE > 0 ||
BOOST_ATOMIC_LLONG_LOCK_FREE > 0 ||
BOOST_ATOMIC_BOOL_LOCK_FREE > 0;
BOOST_TEST(!any_lock_free || BOOST_ATOMIC_THREAD_FENCE > 0);
return boost::report_errors();
}

View File

@@ -0,0 +1,270 @@
// Copyright (c) 2011 Helge Bahmann
// Copyright (c) 2012 Tim Blechmann
//
// 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)
// Attempt to determine whether the memory ordering/ fence operations
// work as expected:
// Let two threads race accessing multiple shared variables and
// verify that "observable" order of operations matches with the
// ordering constraints specified.
//
// We assume that "memory ordering violation" events are exponentially
// distributed, with unknown "average time between violations"
// (which is just the reciprocal of exp distribution parameter lambda).
// Use a "relaxed ordering" implementation that intentionally exhibits
// a (hopefully observable) such violation to compute the maximum-likelihood
// estimate for this time. From this, compute an estimate that covers the
// unknown value with 0.995 confidence (using chi square quantile).
//
// Use this estimate to pick a timeout for the race tests of the
// atomic implementations such that under the assumed distribution
// we get 0.995 probability to detect a race (if there is one).
//
// Overall this yields 0.995 * 0.995 > 0.99 confidence that the
// fences work as expected if this test program does not
// report an error.
#include <boost/atomic.hpp>
#include <cstddef>
#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/thread_time.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/lock_types.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/barrier.hpp>
#include <boost/core/lightweight_test.hpp>
// Two threads perform the following operations:
//
// thread # 1 thread # 2
// store(a, 1) store(b, 1)
// x = read(b) y = read(a)
//
// Under relaxed memory ordering, the case (x, y) == (0, 0) is
// possible. Under sequential consistency, this case is impossible.
//
// This "problem" is reproducible on all platforms, even x86.
template<boost::memory_order store_order, boost::memory_order load_order>
class total_store_order_test
{
public:
total_store_order_test(void);
void run(boost::posix_time::time_duration & timeout);
bool detected_conflict(void) const { return detected_conflict_; }
private:
void thread1fn(void);
void thread2fn(void);
void check_conflict(void);
private:
boost::atomic<int> a_;
/* insert a bit of padding to push the two variables into
different cache lines and increase the likelihood of detecting
a conflict */
char pad1_[512];
boost::atomic<int> b_;
char pad2_[512];
boost::barrier barrier_;
int vrfyb1_, vrfya2_;
boost::atomic<bool> terminate_threads_;
boost::atomic<int> termination_consensus_;
bool detected_conflict_;
boost::mutex m_;
boost::condition_variable c_;
};
template<boost::memory_order store_order, boost::memory_order load_order>
total_store_order_test<store_order, load_order>::total_store_order_test(void) :
a_(0), b_(0), barrier_(2),
vrfyb1_(0), vrfya2_(0),
terminate_threads_(false), termination_consensus_(0),
detected_conflict_(false)
{
}
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::run(boost::posix_time::time_duration & timeout)
{
boost::system_time start = boost::get_system_time();
boost::system_time 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));
{
boost::unique_lock< boost::mutex > guard(m_);
while (boost::get_system_time() < end && !detected_conflict_)
c_.timed_wait(guard, end);
}
terminate_threads_.store(true, boost::memory_order_relaxed);
t2.join();
t1.join();
boost::posix_time::time_duration duration = boost::get_system_time() - start;
if (duration < timeout)
timeout = duration;
}
volatile int backoff_dummy;
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::thread1fn(void)
{
while (true)
{
a_.store(1, store_order);
int b = b_.load(load_order);
barrier_.wait();
vrfyb1_ = b;
barrier_.wait();
check_conflict();
/* both threads synchronize via barriers, so either
both threads must exit here, or they must both do
another round, otherwise one of them will wait forever */
if (terminate_threads_.load(boost::memory_order_relaxed))
{
while (true)
{
int tmp = termination_consensus_.fetch_or(1, boost::memory_order_relaxed);
if (tmp == 3)
return;
if (tmp & 4)
break;
}
}
termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
unsigned int delay = rand() % 10000;
a_.store(0, boost::memory_order_relaxed);
barrier_.wait();
while (delay--)
backoff_dummy = delay;
}
}
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::thread2fn(void)
{
while (true)
{
b_.store(1, store_order);
int a = a_.load(load_order);
barrier_.wait();
vrfya2_ = a;
barrier_.wait();
check_conflict();
/* both threads synchronize via barriers, so either
both threads must exit here, or they must both do
another round, otherwise one of them will wait forever */
if (terminate_threads_.load(boost::memory_order_relaxed))
{
while (true)
{
int tmp = termination_consensus_.fetch_or(2, boost::memory_order_relaxed);
if (tmp == 3)
return;
if (tmp & 4)
break;
}
}
termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
unsigned int delay = rand() % 10000;
b_.store(0, boost::memory_order_relaxed);
barrier_.wait();
while (delay--)
backoff_dummy = delay;
}
}
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::check_conflict(void)
{
if (vrfyb1_ == 0 && vrfya2_ == 0)
{
boost::lock_guard< boost::mutex > guard(m_);
detected_conflict_ = true;
terminate_threads_.store(true, boost::memory_order_relaxed);
c_.notify_all();
}
}
void test_seq_cst(void)
{
double sum = 0.0;
/* take 10 samples */
for (std::size_t n = 0; n < 10; n++)
{
boost::posix_time::time_duration timeout(0, 0, 10);
total_store_order_test<boost::memory_order_relaxed, boost::memory_order_relaxed> test;
test.run(timeout);
if (!test.detected_conflict())
{
std::cout << "Failed to detect order=seq_cst violation while ith order=relaxed -- intrinsic ordering too strong for this test\n";
return;
}
std::cout << "seq_cst violation with order=relaxed after " << timeout.total_microseconds() << " us\n";
sum = sum + timeout.total_microseconds();
}
/* determine maximum likelihood estimate for average time between
race observations */
double avg_race_time_mle = (sum / 10);
/* pick 0.995 confidence (7.44 = chi square 0.995 confidence) */
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::cout << "run seq_cst for " << timeout.total_microseconds() << " us\n";
total_store_order_test<boost::memory_order_seq_cst, boost::memory_order_seq_cst> test;
test.run(timeout);
BOOST_TEST(!test.detected_conflict()); // sequential consistency error
}
int main(int, char *[])
{
test_seq_cst();
return boost::report_errors();
}

View File

@@ -0,0 +1,276 @@
// Copyright (c) 2020 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)
// This test is based on ordering.cpp by Helge Bahmann and Tim Blechmann.
// The test Was modified to use atomic_ref template instead of atomic.
// Attempt to determine whether the memory ordering/ fence operations
// work as expected:
// Let two threads race accessing multiple shared variables and
// verify that "observable" order of operations matches with the
// ordering constraints specified.
//
// We assume that "memory ordering violation" events are exponentially
// distributed, with unknown "average time between violations"
// (which is just the reciprocal of exp distribution parameter lambda).
// Use a "relaxed ordering" implementation that intentionally exhibits
// a (hopefully observable) such violation to compute the maximum-likelihood
// estimate for this time. From this, compute an estimate that covers the
// unknown value with 0.995 confidence (using chi square quantile).
//
// Use this estimate to pick a timeout for the race tests of the
// atomic implementations such that under the assumed distribution
// we get 0.995 probability to detect a race (if there is one).
//
// Overall this yields 0.995 * 0.995 > 0.99 confidence that the
// fences work as expected if this test program does not
// report an error.
#include <boost/memory_order.hpp>
#include <boost/atomic/atomic.hpp>
#include <boost/atomic/atomic_ref.hpp>
#include <cstddef>
#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/thread_time.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/lock_types.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/barrier.hpp>
#include <boost/core/lightweight_test.hpp>
// Two threads perform the following operations:
//
// thread # 1 thread # 2
// store(a, 1) store(b, 1)
// x = read(b) y = read(a)
//
// Under relaxed memory ordering, the case (x, y) == (0, 0) is
// possible. Under sequential consistency, this case is impossible.
//
// This "problem" is reproducible on all platforms, even x86.
template<boost::memory_order store_order, boost::memory_order load_order>
class total_store_order_test
{
public:
total_store_order_test(void);
void run(boost::posix_time::time_duration & timeout);
bool detected_conflict(void) const { return detected_conflict_; }
private:
void thread1fn(void);
void thread2fn(void);
void check_conflict(void);
private:
int a_value_;
boost::atomic_ref<int> a_;
/* insert a bit of padding to push the two variables into
different cache lines and increase the likelihood of detecting
a conflict */
char pad1_[512];
int b_value_;
boost::atomic_ref<int> b_;
char pad2_[512];
boost::barrier barrier_;
int vrfyb1_, vrfya2_;
boost::atomic<bool> terminate_threads_;
boost::atomic<int> termination_consensus_;
bool detected_conflict_;
boost::mutex m_;
boost::condition_variable c_;
};
template<boost::memory_order store_order, boost::memory_order load_order>
total_store_order_test<store_order, load_order>::total_store_order_test(void) :
a_value_(0), a_(a_value_), b_value_(0), b_(b_value_), barrier_(2),
vrfyb1_(0), vrfya2_(0),
terminate_threads_(false), termination_consensus_(0),
detected_conflict_(false)
{
}
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::run(boost::posix_time::time_duration & timeout)
{
boost::system_time start = boost::get_system_time();
boost::system_time 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));
{
boost::unique_lock< boost::mutex > guard(m_);
while (boost::get_system_time() < end && !detected_conflict_)
c_.timed_wait(guard, end);
}
terminate_threads_.store(true, boost::memory_order_relaxed);
t2.join();
t1.join();
boost::posix_time::time_duration duration = boost::get_system_time() - start;
if (duration < timeout)
timeout = duration;
}
volatile int backoff_dummy;
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::thread1fn(void)
{
while (true)
{
a_.store(1, store_order);
int b = b_.load(load_order);
barrier_.wait();
vrfyb1_ = b;
barrier_.wait();
check_conflict();
/* both threads synchronize via barriers, so either
both threads must exit here, or they must both do
another round, otherwise one of them will wait forever */
if (terminate_threads_.load(boost::memory_order_relaxed))
{
while (true)
{
int tmp = termination_consensus_.fetch_or(1, boost::memory_order_relaxed);
if (tmp == 3)
return;
if (tmp & 4)
break;
}
}
termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
unsigned int delay = rand() % 10000;
a_.store(0, boost::memory_order_relaxed);
barrier_.wait();
while (delay--)
backoff_dummy = delay;
}
}
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::thread2fn(void)
{
while (true)
{
b_.store(1, store_order);
int a = a_.load(load_order);
barrier_.wait();
vrfya2_ = a;
barrier_.wait();
check_conflict();
/* both threads synchronize via barriers, so either
both threads must exit here, or they must both do
another round, otherwise one of them will wait forever */
if (terminate_threads_.load(boost::memory_order_relaxed))
{
while (true)
{
int tmp = termination_consensus_.fetch_or(2, boost::memory_order_relaxed);
if (tmp == 3)
return;
if (tmp & 4)
break;
}
}
termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
unsigned int delay = rand() % 10000;
b_.store(0, boost::memory_order_relaxed);
barrier_.wait();
while (delay--)
backoff_dummy = delay;
}
}
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::check_conflict(void)
{
if (vrfyb1_ == 0 && vrfya2_ == 0)
{
boost::lock_guard< boost::mutex > guard(m_);
detected_conflict_ = true;
terminate_threads_.store(true, boost::memory_order_relaxed);
c_.notify_all();
}
}
void test_seq_cst(void)
{
double sum = 0.0;
/* take 10 samples */
for (std::size_t n = 0; n < 10; n++)
{
boost::posix_time::time_duration timeout(0, 0, 10);
total_store_order_test<boost::memory_order_relaxed, boost::memory_order_relaxed> test;
test.run(timeout);
if (!test.detected_conflict())
{
std::cout << "Failed to detect order=seq_cst violation while ith order=relaxed -- intrinsic ordering too strong for this test\n";
return;
}
std::cout << "seq_cst violation with order=relaxed after " << timeout.total_microseconds() << " us\n";
sum = sum + timeout.total_microseconds();
}
/* determine maximum likelihood estimate for average time between
race observations */
double avg_race_time_mle = (sum / 10);
/* pick 0.995 confidence (7.44 = chi square 0.995 confidence) */
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::cout << "run seq_cst for " << timeout.total_microseconds() << " us\n";
total_store_order_test<boost::memory_order_seq_cst, boost::memory_order_seq_cst> test;
test.run(timeout);
BOOST_TEST(!test.detected_conflict()); // sequential consistency error
}
int main(int, char *[])
{
test_seq_cst();
return boost::report_errors();
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2020 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_TEST_CLOCK_HPP_INCLUDED_
#define BOOST_ATOMIC_TEST_TEST_CLOCK_HPP_INCLUDED_
#include <boost/config.hpp>
#if defined(BOOST_WINDOWS)
#include <boost/winapi/config.hpp>
#include <boost/winapi/basic_types.hpp>
#include <boost/winapi/time.hpp>
#include <boost/ratio/ratio.hpp>
#endif
#include <boost/chrono/chrono.hpp>
namespace chrono = boost::chrono;
#if defined(BOOST_WINDOWS)
// On Windows high precision clocks tend to cause spurious test failures because threads wake up earlier than expected.
// Use a lower precision steady clock for tests.
struct test_clock
{
#if BOOST_USE_WINAPI_VERSION >= BOOST_WINAPI_VERSION_WIN6
typedef boost::winapi::ULONGLONG_ rep;
#else
typedef boost::winapi::DWORD_ rep;
#endif
typedef boost::milli period;
typedef chrono::duration< rep, period > duration;
typedef chrono::time_point< test_clock, duration > time_point;
static BOOST_CONSTEXPR_OR_CONST bool is_steady = true;
static time_point now() BOOST_NOEXCEPT
{
#if BOOST_USE_WINAPI_VERSION >= BOOST_WINAPI_VERSION_WIN6
rep ticks = boost::winapi::GetTickCount64();
#else
rep ticks = boost::winapi::GetTickCount();
#endif
return time_point(duration(ticks));
}
};
#elif defined(BOOST_CHRONO_HAS_CLOCK_STEADY)
typedef chrono::steady_clock test_clock;
#else
typedef chrono::system_clock test_clock;
#endif
#endif // BOOST_ATOMIC_TEST_TEST_CLOCK_HPP_INCLUDED_

View File

@@ -0,0 +1,21 @@
# Copyright 2018 Mike Dev
# Copyright 2020 Andrey Semashev
#
# Distributed under the Boost Software License, Version 1.0.
# See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt
#
# NOTE: This does NOT run the unit tests for Boost.Atomic.
# It only tests if the CMakeLists.txt file in its root works as expected
cmake_minimum_required(VERSION 3.5)
project(BoostAtomicCMakeSelfTest)
# Use experimental superproject to pull library dependencies recursively
set(BOOST_ENABLE_CMAKE 1)
add_subdirectory(../../../.. "${CMAKE_CURRENT_BINARY_DIR}/boost_superproject")
add_definitions(-DBOOST_ALL_NO_LIB)
add_executable(boost_atomic_cmake_self_test main.cpp)
target_link_libraries(boost_atomic_cmake_self_test Boost::atomic)

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2018 Mike Dev
//
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
#include <boost/atomic.hpp>
struct Dummy
{
int x[128];
};
int main()
{
Dummy d = {};
boost::atomic<Dummy> ad;
// this operation requires functions from
// the compiled part of Boost.Atomic
ad = d;
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2018 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_TESTS_VALUE_WITH_EPSILON_H_INCLUDED_
#define BOOST_ATOMIC_TESTS_VALUE_WITH_EPSILON_H_INCLUDED_
#include <limits>
#include <iosfwd>
template< typename T >
class value_with_epsilon
{
private:
T m_value;
T m_epsilon;
public:
value_with_epsilon(T value, T epsilon) : m_value(value), m_epsilon(epsilon) {}
T value() const
{
return m_value;
}
T epsilon() const
{
return m_epsilon;
}
bool equal(T value) const
{
return value >= (m_value - m_epsilon) && value <= (m_value + m_epsilon);
}
friend bool operator== (T left, value_with_epsilon< T > const& right)
{
return right.equal(left);
}
friend bool operator== (value_with_epsilon< T > const& left, T right)
{
return left.equal(right);
}
friend bool operator!= (T left, value_with_epsilon< T > const& right)
{
return !right.equal(left);
}
friend bool operator!= (value_with_epsilon< T > const& left, T right)
{
return !left.equal(right);
}
};
template< typename Char, typename Traits, typename T >
inline std::basic_ostream< Char, Traits >& operator<< (std::basic_ostream< Char, Traits >& strm, value_with_epsilon< T > const& val)
{
// Note: libstdc++ does not provide output operators for __float128. There may also be no operators for long double.
// We don't use such floating point values in our tests where the cast would matter.
strm << static_cast< double >(val.value()) << " (+/-" << static_cast< double >(val.epsilon()) << ")";
return strm;
}
template< typename T, typename U >
inline value_with_epsilon< T > approx(T value, U epsilon)
{
return value_with_epsilon< T >(value, static_cast< T >(epsilon));
}
template< typename T >
inline value_with_epsilon< T > approx(T value)
{
return value_with_epsilon< T >(value, static_cast< T >(0.0000001));
}
#endif // BOOST_ATOMIC_TESTS_VALUE_WITH_EPSILON_H_INCLUDED_

View File

@@ -0,0 +1,42 @@
// Copyright (c) 2020 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)
#include <boost/atomic/atomic.hpp>
#include <boost/atomic/atomic_flag.hpp>
#include <boost/atomic/capabilities.hpp>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include "wait_test_helpers.hpp"
int main(int, char *[])
{
test_flag_wait_notify_api();
test_wait_notify_api< atomic_wrapper, boost::uint8_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT8_WAIT_NOTIFY);
test_wait_notify_api< atomic_wrapper, boost::uint16_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT16_WAIT_NOTIFY);
test_wait_notify_api< atomic_wrapper, boost::uint32_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT32_WAIT_NOTIFY);
test_wait_notify_api< atomic_wrapper, boost::uint64_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT64_WAIT_NOTIFY);
#if defined(BOOST_HAS_INT128) && !defined(BOOST_ATOMIC_TESTS_NO_INT128)
test_wait_notify_api< atomic_wrapper, boost::uint128_type >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT128_WAIT_NOTIFY);
#endif
{
struct_3_bytes s1 = {{ 1 }};
struct_3_bytes s2 = {{ 2 }};
struct_3_bytes s3 = {{ 3 }};
test_wait_notify_api< atomic_wrapper, struct_3_bytes >(s1, s2, s3);
}
{
large_struct s1 = {{ 1 }};
large_struct s2 = {{ 2 }};
large_struct s3 = {{ 3 }};
test_wait_notify_api< atomic_wrapper, large_struct >(s1, s2, s3);
}
return boost::report_errors();
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2020 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)
// This is a fuzzing test for waiting and notifying operations.
// The test creates a number of threads exceeding the number of hardware threads, each of which
// blocks on the atomic object. The main thread then notifies one or all threads repeatedly,
// while incrementing the atomic object. The test ends when the atomic counter reaches the predefined limit.
// The goal of the test is to verify that (a) it doesn't crash and (b) all threads get unblocked in the end.
#include <boost/memory_order.hpp>
#include <boost/atomic/atomic.hpp>
#include <iostream>
#include <boost/config.hpp>
#include <boost/bind/bind.hpp>
#include <boost/chrono/chrono.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>
#include <boost/smart_ptr/scoped_array.hpp>
namespace chrono = boost::chrono;
boost::atomic< unsigned int > g_atomic(0u);
BOOST_CONSTEXPR_OR_CONST unsigned int loop_count = 4096u;
void thread_func(boost::barrier* barrier)
{
barrier->wait();
unsigned int old_count = 0u;
while (true)
{
unsigned int new_count = g_atomic.wait(old_count, boost::memory_order_relaxed);
if (new_count >= loop_count)
break;
old_count = new_count;
}
}
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]);
for (unsigned int i = 0u; i < thread_count; ++i)
boost::thread(boost::bind(&thread_func, &barrier)).swap(threads[i]);
barrier.wait();
// Let the threads block on the atomic counter
boost::this_thread::sleep_for(chrono::milliseconds(100));
while (true)
{
for (unsigned int i = 0u; i < thread_count; ++i)
{
g_atomic.opaque_add(1u, boost::memory_order_relaxed);
g_atomic.notify_one();
}
unsigned int old_count = g_atomic.fetch_add(1u, boost::memory_order_relaxed);
g_atomic.notify_all();
if ((old_count + 1u) >= loop_count)
break;
}
for (unsigned int i = 0u; i < thread_count; ++i)
threads[i].join();
return 0u;
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2020 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)
#include <boost/atomic/atomic_ref.hpp>
#include <boost/atomic/capabilities.hpp>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include "wait_test_helpers.hpp"
int main(int, char *[])
{
test_wait_notify_api< atomic_ref_wrapper, boost::uint8_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT8_WAIT_NOTIFY);
test_wait_notify_api< atomic_ref_wrapper, boost::uint16_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT16_WAIT_NOTIFY);
test_wait_notify_api< atomic_ref_wrapper, boost::uint32_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT32_WAIT_NOTIFY);
test_wait_notify_api< atomic_ref_wrapper, boost::uint64_t >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT64_WAIT_NOTIFY);
#if defined(BOOST_HAS_INT128) && !defined(BOOST_ATOMIC_TESTS_NO_INT128)
test_wait_notify_api< atomic_ref_wrapper, boost::uint128_type >(1, 2, 3, BOOST_ATOMIC_HAS_NATIVE_INT128_WAIT_NOTIFY);
#endif
{
struct_3_bytes s1 = {{ 1 }};
struct_3_bytes s2 = {{ 2 }};
struct_3_bytes s3 = {{ 3 }};
test_wait_notify_api< atomic_ref_wrapper, struct_3_bytes >(s1, s2, s3);
}
{
large_struct s1 = {{ 1 }};
large_struct s2 = {{ 2 }};
large_struct s3 = {{ 3 }};
test_wait_notify_api< atomic_ref_wrapper, large_struct >(s1, s2, s3);
}
return boost::report_errors();
}

View File

@@ -0,0 +1,347 @@
// Copyright (c) 2020 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_WAIT_TEST_HELPERS_HPP_INCLUDED_
#define BOOST_ATOMIC_TEST_WAIT_TEST_HELPERS_HPP_INCLUDED_
#include <boost/memory_order.hpp>
#include <boost/atomic/atomic_flag.hpp>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <boost/config.hpp>
#include <boost/chrono/chrono.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>
#include "atomic_wrapper.hpp"
#include "lightweight_test_stream.hpp"
#include "test_clock.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;
//! The test verifies that the wait operation returns immediately if the passed value does not match the atomic value
template< template< typename > class Wrapper, typename T >
inline void test_wait_value_mismatch(T value1, T value2)
{
Wrapper< T > m_wrapper(value1);
T received_value = m_wrapper.a.wait(value2);
BOOST_TEST(received_value == value1);
}
/*!
* The test verifies that notify_one releases one blocked thread and that the released thread receives the modified atomic value.
*
* Technically, this test is allowed to fail since wait() is allowed to return spuriously. However, normally this should not happen.
*/
template< template< typename > class Wrapper, typename T >
class notify_one_test
{
private:
struct thread_state
{
T m_received_value;
test_clock::time_point m_wakeup_time;
explicit thread_state(T value) : m_received_value(value)
{
}
};
private:
Wrapper< T > m_wrapper;
char m_padding[1024];
T m_value1, m_value2, m_value3;
boost::barrier m_barrier;
thread_state m_thread1_state;
thread_state m_thread2_state;
public:
explicit notify_one_test(T value1, T value2, T value3) :
m_wrapper(value1),
m_value1(value1),
m_value2(value2),
m_value3(value3),
m_barrier(3),
m_thread1_state(value1),
m_thread2_state(value1)
{
}
bool run()
{
boost::thread thread1(&notify_one_test::thread_func, this, &m_thread1_state);
boost::thread thread2(&notify_one_test::thread_func, this, &m_thread2_state);
m_barrier.wait();
test_clock::time_point start_time = test_clock::now();
boost::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));
m_wrapper.a.store(m_value3, boost::memory_order_release);
m_wrapper.a.notify_one();
if (!thread1.try_join_for(chrono::seconds(3)))
{
BOOST_ERROR("Thread 1 failed to join");
std::abort();
}
if (!thread2.try_join_for(chrono::seconds(3)))
{
BOOST_ERROR("Thread 2 failed to join");
std::abort();
}
thread_state* first_state = &m_thread1_state;
thread_state* second_state = &m_thread2_state;
if (second_state->m_wakeup_time < first_state->m_wakeup_time)
std::swap(first_state, second_state);
if ((first_state->m_wakeup_time - start_time) < chrono::milliseconds(200))
{
std::cout << "notify_one_test: first thread woke up too soon: " << chrono::duration_cast< chrono::milliseconds >(first_state->m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
if ((first_state->m_wakeup_time - start_time) >= chrono::milliseconds(400))
{
std::cout << "notify_one_test: first thread woke up too late: " << chrono::duration_cast< chrono::milliseconds >(first_state->m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
if ((second_state->m_wakeup_time - start_time) < chrono::milliseconds(400))
{
std::cout << "notify_one_test: second thread woke up too soon: " << chrono::duration_cast< chrono::milliseconds >(second_state->m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
BOOST_TEST_EQ(first_state->m_received_value, m_value2);
BOOST_TEST_EQ(second_state->m_received_value, m_value3);
return true;
}
private:
void thread_func(thread_state* state)
{
m_barrier.wait();
state->m_received_value = m_wrapper.a.wait(m_value1);
state->m_wakeup_time = test_clock::now();
}
};
template< template< typename > class Wrapper, typename T >
inline void test_notify_one(T value1, T value2, T value3)
{
for (unsigned int i = 0u; i < test_retry_count; ++i)
{
notify_one_test< Wrapper, T > test(value1, value2, value3);
if (test.run())
return;
}
BOOST_ERROR("notify_one_test could not complete because blocked thread wake up too soon");
}
/*!
* The test verifies that notify_all releases all blocked threads and that the released threads receive the modified atomic value.
*
* Technically, this test is allowed to fail since wait() is allowed to return spuriously. However, normally this should not happen.
*/
template< template< typename > class Wrapper, typename T >
class notify_all_test
{
private:
struct thread_state
{
T m_received_value;
test_clock::time_point m_wakeup_time;
explicit thread_state(T value) : m_received_value(value)
{
}
};
private:
Wrapper< T > m_wrapper;
char m_padding[1024];
T m_value1, m_value2;
boost::barrier m_barrier;
thread_state m_thread1_state;
thread_state m_thread2_state;
public:
explicit notify_all_test(T value1, T value2) :
m_wrapper(value1),
m_value1(value1),
m_value2(value2),
m_barrier(3),
m_thread1_state(value1),
m_thread2_state(value1)
{
}
bool run()
{
boost::thread thread1(&notify_all_test::thread_func, this, &m_thread1_state);
boost::thread thread2(&notify_all_test::thread_func, this, &m_thread2_state);
m_barrier.wait();
test_clock::time_point start_time = test_clock::now();
boost::this_thread::sleep_for(chrono::milliseconds(200));
m_wrapper.a.store(m_value2, boost::memory_order_release);
m_wrapper.a.notify_all();
if (!thread1.try_join_for(chrono::seconds(3)))
{
BOOST_ERROR("Thread 1 failed to join");
std::abort();
}
if (!thread2.try_join_for(chrono::seconds(3)))
{
BOOST_ERROR("Thread 2 failed to join");
std::abort();
}
if ((m_thread1_state.m_wakeup_time - start_time) < chrono::milliseconds(200))
{
std::cout << "notify_all_test: first thread woke up too soon: " << chrono::duration_cast< chrono::milliseconds >(m_thread1_state.m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
if ((m_thread2_state.m_wakeup_time - start_time) < chrono::milliseconds(200))
{
std::cout << "notify_all_test: second thread woke up too soon: " << chrono::duration_cast< chrono::milliseconds >(m_thread2_state.m_wakeup_time - start_time).count() << " ms" << std::endl;
return false;
}
BOOST_TEST_EQ(m_thread1_state.m_received_value, m_value2);
BOOST_TEST_EQ(m_thread2_state.m_received_value, m_value2);
return true;
}
private:
void thread_func(thread_state* state)
{
m_barrier.wait();
state->m_received_value = m_wrapper.a.wait(m_value1);
state->m_wakeup_time = test_clock::now();
}
};
template< template< typename > class Wrapper, typename T >
inline void test_notify_all(T value1, T value2)
{
for (unsigned int i = 0u; i < test_retry_count; ++i)
{
notify_all_test< Wrapper, T > test(value1, value2);
if (test.run())
return;
}
BOOST_ERROR("notify_all_test could not complete because blocked thread wake up too soon");
}
//! Invokes all wait/notify tests
template< template< typename > class Wrapper, typename T >
void test_wait_notify_api(T value1, T value2, T value3)
{
test_wait_value_mismatch< Wrapper >(value1, value2);
test_notify_one< Wrapper >(value1, value2, value3);
test_notify_all< Wrapper >(value1, value2);
}
//! Invokes all wait/notify tests
template< template< typename > class Wrapper, typename T >
void test_wait_notify_api(T value1, T value2, T value3, int has_native_wait_notify_macro)
{
BOOST_TEST_EQ(Wrapper< T >::atomic_type::always_has_native_wait_notify, (has_native_wait_notify_macro == 2));
test_wait_notify_api< Wrapper >(value1, value2, value3);
}
inline void test_flag_wait_notify_api()
{
#ifndef BOOST_ATOMIC_NO_ATOMIC_FLAG_INIT
boost::atomic_flag f = BOOST_ATOMIC_FLAG_INIT;
#else
boost::atomic_flag f;
#endif
bool received_value = f.wait(true);
BOOST_TEST(!received_value);
f.notify_one();
f.notify_all();
}
struct struct_3_bytes
{
unsigned char data[3u];
inline bool operator==(struct_3_bytes const& c) const
{
return std::memcmp(data, &c.data, sizeof(data)) == 0;
}
inline bool operator!=(struct_3_bytes const& c) const
{
return std::memcmp(data, &c.data, sizeof(data)) != 0;
}
};
template< typename Char, typename Traits >
inline std::basic_ostream< Char, Traits >& operator<< (std::basic_ostream< Char, Traits >& strm, struct_3_bytes const& val)
{
strm << "[struct_3_bytes{ " << static_cast< unsigned int >(val.data[0])
<< ", " << static_cast< unsigned int >(val.data[1]) << ", " << static_cast< unsigned int >(val.data[2]) << " }]";
return strm;
}
struct large_struct
{
unsigned char data[256u];
inline bool operator==(large_struct const& c) const
{
return std::memcmp(data, &c.data, sizeof(data)) == 0;
}
inline bool operator!=(large_struct const& c) const
{
return std::memcmp(data, &c.data, sizeof(data)) != 0;
}
};
template< typename Char, typename Traits >
inline std::basic_ostream< Char, Traits >& operator<< (std::basic_ostream< Char, Traits >& strm, large_struct const&)
{
strm << "[large_struct]";
return strm;
}
#endif // BOOST_ATOMIC_TEST_WAIT_TEST_HELPERS_HPP_INCLUDED_