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 #ifndef BOOST_ATOMIC_TEST_WAIT_TEST_HELPERS_HPP_INCLUDED_
8 #define BOOST_ATOMIC_TEST_WAIT_TEST_HELPERS_HPP_INCLUDED_
9
10 #include <boost/memory_order.hpp>
11 #include <boost/atomic/atomic_flag.hpp>
12
13 #include <cstdlib>
14 #include <cstring>
15 #include <iostream>
16 #include <algorithm>
17 #include <boost/config.hpp>
18 #include <boost/chrono/chrono.hpp>
19 #include <boost/bind/bind.hpp>
20 #include <boost/thread/thread.hpp>
21 #include <boost/thread/barrier.hpp>
22 #include "atomic_wrapper.hpp"
23 #include "lightweight_test_stream.hpp"
24 #include "test_clock.hpp"
25
26 //! Since some of the tests below are allowed to fail, we retry up to this many times to pass the test
27 BOOST_CONSTEXPR_OR_CONST unsigned int test_retry_count = 5u;
28
29 //! The test verifies that the wait operation returns immediately if the passed value does not match the atomic value
30 template< template< typename > class Wrapper, typename T >
test_wait_value_mismatch(T value1,T value2)31 inline void test_wait_value_mismatch(T value1, T value2)
32 {
33 Wrapper< T > m_wrapper(value1);
34
35 T received_value = m_wrapper.a.wait(value2);
36 BOOST_TEST(received_value == value1);
37 }
38
39 /*!
40 * The test verifies that notify_one releases one blocked thread and that the released thread receives the modified atomic value.
41 *
42 * Technically, this test is allowed to fail since wait() is allowed to return spuriously. However, normally this should not happen.
43 */
44 template< template< typename > class Wrapper, typename T >
45 class notify_one_test
46 {
47 private:
48 struct thread_state
49 {
50 T m_received_value;
51 test_clock::time_point m_wakeup_time;
52
thread_statenotify_one_test::thread_state53 explicit thread_state(T value) : m_received_value(value)
54 {
55 }
56 };
57
58 private:
59 Wrapper< T > m_wrapper;
60
61 char m_padding[1024];
62
63 T m_value1, m_value2, m_value3;
64
65 boost::barrier m_barrier;
66
67 thread_state m_thread1_state;
68 thread_state m_thread2_state;
69
70 public:
notify_one_test(T value1,T value2,T value3)71 explicit notify_one_test(T value1, T value2, T value3) :
72 m_wrapper(value1),
73 m_value1(value1),
74 m_value2(value2),
75 m_value3(value3),
76 m_barrier(3),
77 m_thread1_state(value1),
78 m_thread2_state(value1)
79 {
80 }
81
run()82 bool run()
83 {
84 boost::thread thread1(¬ify_one_test::thread_func, this, &m_thread1_state);
85 boost::thread thread2(¬ify_one_test::thread_func, this, &m_thread2_state);
86
87 m_barrier.wait();
88
89 test_clock::time_point start_time = test_clock::now();
90
91 boost::this_thread::sleep_for(chrono::milliseconds(200));
92
93 m_wrapper.a.store(m_value2, boost::memory_order_release);
94 m_wrapper.a.notify_one();
95
96 boost::this_thread::sleep_for(chrono::milliseconds(200));
97
98 m_wrapper.a.store(m_value3, boost::memory_order_release);
99 m_wrapper.a.notify_one();
100
101 if (!thread1.try_join_for(chrono::seconds(3)))
102 {
103 BOOST_ERROR("Thread 1 failed to join");
104 std::abort();
105 }
106 if (!thread2.try_join_for(chrono::seconds(3)))
107 {
108 BOOST_ERROR("Thread 2 failed to join");
109 std::abort();
110 }
111
112 thread_state* first_state = &m_thread1_state;
113 thread_state* second_state = &m_thread2_state;
114 if (second_state->m_wakeup_time < first_state->m_wakeup_time)
115 std::swap(first_state, second_state);
116
117 if ((first_state->m_wakeup_time - start_time) < chrono::milliseconds(200))
118 {
119 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;
120 return false;
121 }
122
123 if ((first_state->m_wakeup_time - start_time) >= chrono::milliseconds(400))
124 {
125 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;
126 return false;
127 }
128
129 if ((second_state->m_wakeup_time - start_time) < chrono::milliseconds(400))
130 {
131 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;
132 return false;
133 }
134
135 BOOST_TEST_EQ(first_state->m_received_value, m_value2);
136 BOOST_TEST_EQ(second_state->m_received_value, m_value3);
137
138 return true;
139 }
140
141 private:
thread_func(thread_state * state)142 void thread_func(thread_state* state)
143 {
144 m_barrier.wait();
145
146 state->m_received_value = m_wrapper.a.wait(m_value1);
147 state->m_wakeup_time = test_clock::now();
148 }
149 };
150
151 template< template< typename > class Wrapper, typename T >
test_notify_one(T value1,T value2,T value3)152 inline void test_notify_one(T value1, T value2, T value3)
153 {
154 for (unsigned int i = 0u; i < test_retry_count; ++i)
155 {
156 notify_one_test< Wrapper, T > test(value1, value2, value3);
157 if (test.run())
158 return;
159 }
160
161 BOOST_ERROR("notify_one_test could not complete because blocked thread wake up too soon");
162 }
163
164 /*!
165 * The test verifies that notify_all releases all blocked threads and that the released threads receive the modified atomic value.
166 *
167 * Technically, this test is allowed to fail since wait() is allowed to return spuriously. However, normally this should not happen.
168 */
169 template< template< typename > class Wrapper, typename T >
170 class notify_all_test
171 {
172 private:
173 struct thread_state
174 {
175 T m_received_value;
176 test_clock::time_point m_wakeup_time;
177
thread_statenotify_all_test::thread_state178 explicit thread_state(T value) : m_received_value(value)
179 {
180 }
181 };
182
183 private:
184 Wrapper< T > m_wrapper;
185
186 char m_padding[1024];
187
188 T m_value1, m_value2;
189
190 boost::barrier m_barrier;
191
192 thread_state m_thread1_state;
193 thread_state m_thread2_state;
194
195 public:
notify_all_test(T value1,T value2)196 explicit notify_all_test(T value1, T value2) :
197 m_wrapper(value1),
198 m_value1(value1),
199 m_value2(value2),
200 m_barrier(3),
201 m_thread1_state(value1),
202 m_thread2_state(value1)
203 {
204 }
205
run()206 bool run()
207 {
208 boost::thread thread1(¬ify_all_test::thread_func, this, &m_thread1_state);
209 boost::thread thread2(¬ify_all_test::thread_func, this, &m_thread2_state);
210
211 m_barrier.wait();
212
213 test_clock::time_point start_time = test_clock::now();
214
215 boost::this_thread::sleep_for(chrono::milliseconds(200));
216
217 m_wrapper.a.store(m_value2, boost::memory_order_release);
218 m_wrapper.a.notify_all();
219
220 if (!thread1.try_join_for(chrono::seconds(3)))
221 {
222 BOOST_ERROR("Thread 1 failed to join");
223 std::abort();
224 }
225 if (!thread2.try_join_for(chrono::seconds(3)))
226 {
227 BOOST_ERROR("Thread 2 failed to join");
228 std::abort();
229 }
230
231 if ((m_thread1_state.m_wakeup_time - start_time) < chrono::milliseconds(200))
232 {
233 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;
234 return false;
235 }
236
237 if ((m_thread2_state.m_wakeup_time - start_time) < chrono::milliseconds(200))
238 {
239 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;
240 return false;
241 }
242
243 BOOST_TEST_EQ(m_thread1_state.m_received_value, m_value2);
244 BOOST_TEST_EQ(m_thread2_state.m_received_value, m_value2);
245
246 return true;
247 }
248
249 private:
thread_func(thread_state * state)250 void thread_func(thread_state* state)
251 {
252 m_barrier.wait();
253
254 state->m_received_value = m_wrapper.a.wait(m_value1);
255 state->m_wakeup_time = test_clock::now();
256 }
257 };
258
259 template< template< typename > class Wrapper, typename T >
test_notify_all(T value1,T value2)260 inline void test_notify_all(T value1, T value2)
261 {
262 for (unsigned int i = 0u; i < test_retry_count; ++i)
263 {
264 notify_all_test< Wrapper, T > test(value1, value2);
265 if (test.run())
266 return;
267 }
268
269 BOOST_ERROR("notify_all_test could not complete because blocked thread wake up too soon");
270 }
271
272 //! Invokes all wait/notify tests
273 template< template< typename > class Wrapper, typename T >
test_wait_notify_api(T value1,T value2,T value3)274 void test_wait_notify_api(T value1, T value2, T value3)
275 {
276 test_wait_value_mismatch< Wrapper >(value1, value2);
277 test_notify_one< Wrapper >(value1, value2, value3);
278 test_notify_all< Wrapper >(value1, value2);
279 }
280
281
test_flag_wait_notify_api()282 inline void test_flag_wait_notify_api()
283 {
284 #ifndef BOOST_ATOMIC_NO_ATOMIC_FLAG_INIT
285 boost::atomic_flag f = BOOST_ATOMIC_FLAG_INIT;
286 #else
287 boost::atomic_flag f;
288 #endif
289
290 bool received_value = f.wait(true);
291 BOOST_TEST(!received_value);
292 f.notify_one();
293 f.notify_all();
294 }
295
296 struct struct_3_bytes
297 {
298 unsigned char data[3u];
299
operator ==struct_3_bytes300 inline bool operator==(struct_3_bytes const& c) const
301 {
302 return std::memcmp(data, &c.data, sizeof(data)) == 0;
303 }
operator !=struct_3_bytes304 inline bool operator!=(struct_3_bytes const& c) const
305 {
306 return std::memcmp(data, &c.data, sizeof(data)) != 0;
307 }
308 };
309
310 template< typename Char, typename Traits >
operator <<(std::basic_ostream<Char,Traits> & strm,struct_3_bytes const & val)311 inline std::basic_ostream< Char, Traits >& operator<< (std::basic_ostream< Char, Traits >& strm, struct_3_bytes const& val)
312 {
313 strm << "[struct_3_bytes{ " << static_cast< unsigned int >(val.data[0])
314 << ", " << static_cast< unsigned int >(val.data[1]) << ", " << static_cast< unsigned int >(val.data[2]) << " }]";
315 return strm;
316 }
317
318 struct large_struct
319 {
320 unsigned char data[256u];
321
operator ==large_struct322 inline bool operator==(large_struct const& c) const
323 {
324 return std::memcmp(data, &c.data, sizeof(data)) == 0;
325 }
operator !=large_struct326 inline bool operator!=(large_struct const& c) const
327 {
328 return std::memcmp(data, &c.data, sizeof(data)) != 0;
329 }
330 };
331
332 template< typename Char, typename Traits >
operator <<(std::basic_ostream<Char,Traits> & strm,large_struct const &)333 inline std::basic_ostream< Char, Traits >& operator<< (std::basic_ostream< Char, Traits >& strm, large_struct const&)
334 {
335 strm << "[large_struct]";
336 return strm;
337 }
338
339 #endif // BOOST_ATOMIC_TEST_WAIT_TEST_HELPERS_HPP_INCLUDED_
340