• 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