• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //  Copyright (c) 2011 Helge Bahmann
2 //  Copyright (c) 2012 Tim Blechmann
3 //
4 //  Distributed under the Boost Software License, Version 1.0.
5 //  See accompanying file LICENSE_1_0.txt or copy at
6 //  http://www.boost.org/LICENSE_1_0.txt)
7 
8 // Attempt to determine whether the memory ordering/ fence operations
9 // work as expected:
10 // Let two threads race accessing multiple shared variables and
11 // verify that "observable" order of operations matches with the
12 // ordering constraints specified.
13 //
14 // We assume that "memory ordering violation" events are exponentially
15 // distributed, with unknown "average time between violations"
16 // (which is just the reciprocal of exp distribution parameter lambda).
17 // Use a "relaxed ordering" implementation that intentionally exhibits
18 // a (hopefully observable) such violation to compute the maximum-likelihood
19 // estimate 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 // fences work as expected if this test program does not
28 // report an error.
29 
30 #include <boost/atomic.hpp>
31 
32 #include <cstddef>
33 #include <boost/bind/bind.hpp>
34 #include <boost/date_time/posix_time/posix_time_types.hpp>
35 #include <boost/thread/thread.hpp>
36 #include <boost/thread/thread_time.hpp>
37 #include <boost/thread/lock_guard.hpp>
38 #include <boost/thread/lock_types.hpp>
39 #include <boost/thread/mutex.hpp>
40 #include <boost/thread/condition_variable.hpp>
41 #include <boost/thread/barrier.hpp>
42 #include <boost/core/lightweight_test.hpp>
43 
44 // Two threads perform the following operations:
45 //
46 // thread # 1        thread # 2
47 // store(a, 1)       store(b, 1)
48 // x = read(b)       y = read(a)
49 //
50 // Under relaxed memory ordering, the case (x, y) == (0, 0) is
51 // possible. Under sequential consistency, this case is impossible.
52 //
53 // This "problem" is reproducible on all platforms, even x86.
54 template<boost::memory_order store_order, boost::memory_order load_order>
55 class total_store_order_test
56 {
57 public:
58     total_store_order_test(void);
59 
60     void run(boost::posix_time::time_duration & timeout);
detected_conflict(void) const61     bool detected_conflict(void) const { return detected_conflict_; }
62 
63 private:
64     void thread1fn(void);
65     void thread2fn(void);
66     void check_conflict(void);
67 
68 private:
69     boost::atomic<int> a_;
70     /* insert a bit of padding to push the two variables into
71     different cache lines and increase the likelihood of detecting
72     a conflict */
73     char pad1_[512];
74     boost::atomic<int> b_;
75 
76     char pad2_[512];
77     boost::barrier barrier_;
78 
79     int vrfyb1_, vrfya2_;
80 
81     boost::atomic<bool> terminate_threads_;
82     boost::atomic<int> termination_consensus_;
83 
84     bool detected_conflict_;
85     boost::mutex m_;
86     boost::condition_variable c_;
87 };
88 
89 template<boost::memory_order store_order, boost::memory_order load_order>
total_store_order_test(void)90 total_store_order_test<store_order, load_order>::total_store_order_test(void) :
91     a_(0), b_(0), barrier_(2),
92     vrfyb1_(0), vrfya2_(0),
93     terminate_threads_(false), termination_consensus_(0),
94     detected_conflict_(false)
95 {
96 }
97 
98 template<boost::memory_order store_order, boost::memory_order load_order>
run(boost::posix_time::time_duration & timeout)99 void total_store_order_test<store_order, load_order>::run(boost::posix_time::time_duration & timeout)
100 {
101     boost::system_time start = boost::get_system_time();
102     boost::system_time end = start + timeout;
103 
104     boost::thread t1(boost::bind(&total_store_order_test::thread1fn, this));
105     boost::thread t2(boost::bind(&total_store_order_test::thread2fn, this));
106 
107     {
108         boost::unique_lock< boost::mutex > guard(m_);
109         while (boost::get_system_time() < end && !detected_conflict_)
110             c_.timed_wait(guard, end);
111     }
112 
113     terminate_threads_.store(true, boost::memory_order_relaxed);
114 
115     t2.join();
116     t1.join();
117 
118     boost::posix_time::time_duration duration = boost::get_system_time() - start;
119     if (duration < timeout)
120         timeout = duration;
121 }
122 
123 volatile int backoff_dummy;
124 
125 template<boost::memory_order store_order, boost::memory_order load_order>
thread1fn(void)126 void total_store_order_test<store_order, load_order>::thread1fn(void)
127 {
128     while (true)
129     {
130         a_.store(1, store_order);
131         int b = b_.load(load_order);
132 
133         barrier_.wait();
134 
135         vrfyb1_ = b;
136 
137         barrier_.wait();
138 
139         check_conflict();
140 
141         /* both threads synchronize via barriers, so either
142         both threads must exit here, or they must both do
143         another round, otherwise one of them will wait forever */
144         if (terminate_threads_.load(boost::memory_order_relaxed))
145         {
146             while (true)
147             {
148                 int tmp = termination_consensus_.fetch_or(1, boost::memory_order_relaxed);
149 
150                 if (tmp == 3)
151                     return;
152                 if (tmp & 4)
153                     break;
154             }
155         }
156 
157         termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
158 
159         unsigned int delay = rand() % 10000;
160         a_.store(0, boost::memory_order_relaxed);
161 
162         barrier_.wait();
163 
164         while (delay--)
165             backoff_dummy = delay;
166     }
167 }
168 
169 template<boost::memory_order store_order, boost::memory_order load_order>
thread2fn(void)170 void total_store_order_test<store_order, load_order>::thread2fn(void)
171 {
172     while (true)
173     {
174         b_.store(1, store_order);
175         int a = a_.load(load_order);
176 
177         barrier_.wait();
178 
179         vrfya2_ = a;
180 
181         barrier_.wait();
182 
183         check_conflict();
184 
185         /* both threads synchronize via barriers, so either
186         both threads must exit here, or they must both do
187         another round, otherwise one of them will wait forever */
188         if (terminate_threads_.load(boost::memory_order_relaxed))
189         {
190             while (true)
191             {
192                 int tmp = termination_consensus_.fetch_or(2, boost::memory_order_relaxed);
193 
194                 if (tmp == 3)
195                     return;
196                 if (tmp & 4)
197                     break;
198             }
199         }
200 
201         termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
202 
203         unsigned int delay = rand() % 10000;
204         b_.store(0, boost::memory_order_relaxed);
205 
206         barrier_.wait();
207 
208         while (delay--)
209             backoff_dummy = delay;
210     }
211 }
212 
213 template<boost::memory_order store_order, boost::memory_order load_order>
check_conflict(void)214 void total_store_order_test<store_order, load_order>::check_conflict(void)
215 {
216     if (vrfyb1_ == 0 && vrfya2_ == 0)
217     {
218         boost::lock_guard< boost::mutex > guard(m_);
219         detected_conflict_ = true;
220         terminate_threads_.store(true, boost::memory_order_relaxed);
221         c_.notify_all();
222     }
223 }
224 
test_seq_cst(void)225 void test_seq_cst(void)
226 {
227     double sum = 0.0;
228 
229     /* take 10 samples */
230     for (std::size_t n = 0; n < 10; n++)
231     {
232         boost::posix_time::time_duration timeout(0, 0, 10);
233 
234         total_store_order_test<boost::memory_order_relaxed, boost::memory_order_relaxed> test;
235         test.run(timeout);
236         if (!test.detected_conflict())
237         {
238             std::cout << "Failed to detect order=seq_cst violation while ith order=relaxed -- intrinsic ordering too strong for this test\n";
239             return;
240         }
241 
242         std::cout << "seq_cst violation with order=relaxed after " << timeout.total_microseconds() << " us\n";
243 
244         sum = sum + timeout.total_microseconds();
245     }
246 
247     /* determine maximum likelihood estimate for average time between
248     race observations */
249     double avg_race_time_mle = (sum / 10);
250 
251     /* pick 0.995 confidence (7.44 = chi square 0.995 confidence) */
252     double avg_race_time_995 = avg_race_time_mle * 2 * 10 / 7.44;
253 
254     /* 5.298 = 0.995 quantile of exponential distribution */
255     boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time_995));
256 
257     std::cout << "run seq_cst for " << timeout.total_microseconds() << " us\n";
258 
259     total_store_order_test<boost::memory_order_seq_cst, boost::memory_order_seq_cst> test;
260     test.run(timeout);
261 
262     BOOST_TEST(!test.detected_conflict()); // sequential consistency error
263 }
264 
main(int,char * [])265 int main(int, char *[])
266 {
267     test_seq_cst();
268 
269     return boost::report_errors();
270 }
271