• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  //  Copyright (c) 2020 Andrey Semashev
2  //
3  //  Distributed under the Boost Software License, Version 1.0.
4  //  See accompanying file LICENSE_1_0.txt or copy at
5  //  http://www.boost.org/LICENSE_1_0.txt)
6  
7  // This test is based on atomicity.cpp by Helge Bahmann. The test
8  // Was modified to use atomic_ref template instead of atomic.
9  
10  // Attempt to determine whether the operations on atomic variables
11  // do in fact behave atomically: Let multiple threads race modifying
12  // a shared atomic variable and verify that it behaves as expected.
13  //
14  // We assume that "observable race condition" events are exponentially
15  // distributed, with unknown "average time between observable races"
16  // (which is just the reciprocal of exp distribution parameter lambda).
17  // Use a non-atomic implementation that intentionally exhibits a
18  // (hopefully tight) race to compute the maximum-likelihood estimate
19  // for this time. From this, compute an estimate that covers the
20  // unknown value with 0.995 confidence (using chi square quantile).
21  //
22  // Use this estimate to pick a timeout for the race tests of the
23  // atomic implementations such that under the assumed distribution
24  // we get 0.995 probability to detect a race (if there is one).
25  //
26  // Overall this yields 0.995 * 0.995 > 0.99 confidence that the
27  // operations truly behave atomic if this test program does not
28  // report an error.
29  
30  #include <boost/memory_order.hpp>
31  #include <boost/atomic/atomic.hpp>
32  #include <boost/atomic/atomic_ref.hpp>
33  
34  #include <cstddef>
35  #include <algorithm>
36  #include <boost/config.hpp>
37  #include <boost/ref.hpp>
38  #include <boost/function.hpp>
39  #include <boost/bind/bind.hpp>
40  #include <boost/date_time/posix_time/posix_time_types.hpp>
41  #include <boost/thread/thread.hpp>
42  #include <boost/thread/thread_time.hpp>
43  #include <boost/thread/lock_guard.hpp>
44  #include <boost/thread/lock_types.hpp>
45  #include <boost/thread/mutex.hpp>
46  #include <boost/thread/condition_variable.hpp>
47  #include <boost/core/lightweight_test.hpp>
48  
49  /* helper class to let two instances of a function race against each
50  other, with configurable timeout and early abort on detection of error */
51  class concurrent_runner
52  {
53  public:
54      /* concurrently run the function in two threads, until either timeout
55      or one of the functions returns "false"; returns true if timeout
56      was reached, or false if early abort and updates timeout accordingly */
execute(const boost::function<bool (std::size_t)> & fn,boost::posix_time::time_duration & timeout)57      static bool execute(const boost::function<bool(std::size_t)> & fn, boost::posix_time::time_duration & timeout)
58      {
59          concurrent_runner runner(fn);
60          runner.wait_finish(timeout);
61          return !runner.failure();
62      }
63  
64  
concurrent_runner(const boost::function<bool (std::size_t)> & fn)65      concurrent_runner(const boost::function<bool(std::size_t)> & fn) :
66          finished_(false), failure_(false)
67      {
68          boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 0)).swap(first_thread_);
69          boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 1)).swap(second_thread_);
70      }
71  
wait_finish(boost::posix_time::time_duration & timeout)72      void wait_finish(boost::posix_time::time_duration & timeout)
73      {
74          boost::system_time start = boost::get_system_time();
75          boost::system_time end = start + timeout;
76  
77          {
78              boost::unique_lock< boost::mutex > guard(m_);
79              while (boost::get_system_time() < end && !finished())
80                  c_.timed_wait(guard, end);
81          }
82  
83          finished_.store(true, boost::memory_order_relaxed);
84  
85          first_thread_.join();
86          second_thread_.join();
87  
88          boost::posix_time::time_duration duration = boost::get_system_time() - start;
89          if (duration < timeout)
90              timeout = duration;
91      }
92  
finished(void) const93      bool finished(void) const BOOST_NOEXCEPT_OR_NOTHROW
94      {
95          return finished_.load(boost::memory_order_relaxed);
96      }
97  
failure(void) const98      bool failure(void) const BOOST_NOEXCEPT_OR_NOTHROW
99      {
100          return failure_;
101      }
102  
103  private:
thread_function(boost::function<bool (std::size_t)> function,std::size_t instance)104      void thread_function(boost::function<bool(std::size_t)> function, std::size_t instance)
105      {
106          while (!finished())
107          {
108              if (!function(instance))
109              {
110                  boost::lock_guard< boost::mutex > guard(m_);
111                  failure_ = true;
112                  finished_.store(true, boost::memory_order_relaxed);
113                  c_.notify_all();
114                  break;
115              }
116          }
117      }
118  
119  private:
120      boost::mutex m_;
121      boost::condition_variable c_;
122  
123      boost::atomic<bool> finished_;
124      bool failure_;
125  
126      boost::thread first_thread_;
127      boost::thread second_thread_;
128  };
129  
racy_add(volatile unsigned int & value,std::size_t instance)130  bool racy_add(volatile unsigned int & value, std::size_t instance)
131  {
132      std::size_t shift = instance * 8;
133      unsigned int mask = 0xff << shift;
134      for (std::size_t n = 0; n < 255; ++n)
135      {
136          unsigned int tmp = value;
137          value = tmp + (1 << shift);
138  
139          if ((tmp & mask) != (n << shift))
140              return false;
141      }
142  
143      unsigned int tmp = value;
144      value = tmp & ~mask;
145      if ((tmp & mask) != mask)
146          return false;
147  
148      return true;
149  }
150  
151  /* compute estimate for average time between races being observable, in usecs */
estimate_avg_race_time(void)152  double estimate_avg_race_time(void)
153  {
154      double sum = 0.0;
155  
156      /* take 10 samples */
157      for (std::size_t n = 0; n < 10; n++)
158      {
159          boost::posix_time::time_duration timeout(0, 0, 10);
160  
161          volatile unsigned int value(0);
162          bool success = concurrent_runner::execute(
163              boost::bind(racy_add, boost::ref(value), boost::placeholders::_1),
164              timeout
165          );
166  
167          if (success)
168          {
169              BOOST_ERROR("Failed to establish baseline time for reproducing race condition");
170          }
171  
172          sum = sum + timeout.total_microseconds();
173      }
174  
175      /* determine maximum likelihood estimate for average time between
176      race observations */
177      double avg_race_time_mle = (sum / 10);
178  
179      /* pick 0.995 confidence (7.44 = chi square 0.995 confidence) */
180      double avg_race_time_995 = avg_race_time_mle * 2 * 10 / 7.44;
181  
182      return avg_race_time_995;
183  }
184  
185  template<typename value_type, std::size_t shift_>
test_arithmetic(value_type & shared_value,std::size_t instance)186  bool test_arithmetic(value_type& shared_value, std::size_t instance)
187  {
188      std::size_t shift = instance * 8;
189      value_type mask = 0xff << shift;
190      value_type increment = 1 << shift;
191  
192      value_type expected = 0;
193      boost::atomic_ref<value_type> shared_value_ref(shared_value);
194  
195      for (std::size_t n = 0; n < 255; ++n)
196      {
197          value_type tmp = shared_value_ref.fetch_add(increment, boost::memory_order_relaxed);
198          if ( (tmp & mask) != (expected << shift) )
199              return false;
200          ++expected;
201      }
202      for (std::size_t n = 0; n < 255; ++n)
203      {
204          value_type tmp = shared_value_ref.fetch_sub(increment, boost::memory_order_relaxed);
205          if ( (tmp & mask) != (expected << shift) )
206              return false;
207          --expected;
208      }
209  
210      return true;
211  }
212  
213  template<typename value_type, std::size_t shift_>
test_bitops(value_type & shared_value,std::size_t instance)214  bool test_bitops(value_type& shared_value, std::size_t instance)
215  {
216      std::size_t shift = instance * 8;
217      value_type mask = 0xff << shift;
218  
219      value_type expected = 0;
220      boost::atomic_ref<value_type> shared_value_ref(shared_value);
221  
222      for (std::size_t k = 0; k < 8; ++k)
223      {
224          value_type mod = 1u << k;
225          value_type tmp = shared_value_ref.fetch_or(mod << shift, boost::memory_order_relaxed);
226          if ( (tmp & mask) != (expected << shift))
227              return false;
228          expected = expected | mod;
229      }
230      for (std::size_t k = 0; k < 8; ++k)
231      {
232          value_type tmp = shared_value_ref.fetch_and(~(1u << (shift + k)), boost::memory_order_relaxed);
233          if ( (tmp & mask) != (expected << shift))
234              return false;
235          expected = expected & ~(1u << k);
236      }
237      for (std::size_t k = 0; k < 8; ++k)
238      {
239          value_type mod = 255u ^ (1u << k);
240          value_type tmp = shared_value_ref.fetch_xor(mod << shift, boost::memory_order_relaxed);
241          if ( (tmp & mask) != (expected << shift))
242              return false;
243          expected = expected ^ mod;
244      }
245  
246      value_type tmp = shared_value_ref.fetch_and(~mask, boost::memory_order_relaxed);
247      if ( (tmp & mask) != (expected << shift) )
248          return false;
249  
250      return true;
251  }
252  
main(int,char * [])253  int main(int, char *[])
254  {
255      double avg_race_time = estimate_avg_race_time();
256  
257      /* 5.298 = 0.995 quantile of exponential distribution */
258      const boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time));
259  
260      {
261          unsigned int value = 0;
262  
263          /* testing two different operations in this loop, therefore
264          enlarge timeout */
265          boost::posix_time::time_duration tmp(timeout * 2);
266  
267          bool success = concurrent_runner::execute(
268              boost::bind(test_arithmetic<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
269              tmp
270          );
271  
272          BOOST_TEST(success); // concurrent arithmetic error
273      }
274  
275      {
276          unsigned int value = 0;
277  
278          /* testing three different operations in this loop, therefore
279          enlarge timeout */
280          boost::posix_time::time_duration tmp(timeout * 3);
281  
282          bool success = concurrent_runner::execute(
283              boost::bind(test_bitops<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
284              tmp
285          );
286  
287          BOOST_TEST(success); // concurrent bit operations error
288      }
289  
290      return boost::report_errors();
291  }
292