• 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 #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(&notify_one_test::thread_func, this, &m_thread1_state);
85         boost::thread thread2(&notify_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(&notify_all_test::thread_func, this, &m_thread1_state);
209         boost::thread thread2(&notify_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