1 | // |
2 | // system_timer.cpp |
3 | // ~~~~~~~~~~~~~~~~ |
4 | // |
5 | // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
6 | // |
7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying |
8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
9 | // |
10 | |
11 | // Disable autolinking for unit tests. |
12 | #if !defined(BOOST_ALL_NO_LIB) |
13 | #define BOOST_ALL_NO_LIB 1 |
14 | #endif // !defined(BOOST_ALL_NO_LIB) |
15 | |
16 | // Prevent link dependency on the Boost.System library. |
17 | #if !defined(BOOST_SYSTEM_NO_DEPRECATED) |
18 | #define BOOST_SYSTEM_NO_DEPRECATED |
19 | #endif // !defined(BOOST_SYSTEM_NO_DEPRECATED) |
20 | |
21 | // Test that header file is self-contained. |
22 | #include <boost/asio/system_timer.hpp> |
23 | |
24 | #include "unit_test.hpp" |
25 | |
26 | #if defined(BOOST_ASIO_HAS_STD_CHRONO) |
27 | |
28 | #include <boost/asio/io_service.hpp> |
29 | #include <boost/asio/detail/thread.hpp> |
30 | |
31 | #if defined(BOOST_ASIO_HAS_BOOST_BIND) |
32 | # include <boost/bind.hpp> |
33 | #else // defined(BOOST_ASIO_HAS_BOOST_BIND) |
34 | # include <functional> |
35 | #endif // defined(BOOST_ASIO_HAS_BOOST_BIND) |
36 | |
37 | #if defined(BOOST_ASIO_HAS_BOOST_BIND) |
38 | namespace bindns = boost; |
39 | #else // defined(BOOST_ASIO_HAS_BOOST_BIND) |
40 | namespace bindns = std; |
41 | #endif // defined(BOOST_ASIO_HAS_BOOST_BIND) |
42 | |
43 | namespace chronons = std::chrono; |
44 | |
45 | void increment(int* count) |
46 | { |
47 | ++(*count); |
48 | } |
49 | |
50 | void decrement_to_zero(boost::asio::system_timer* t, int* count) |
51 | { |
52 | if (*count > 0) |
53 | { |
54 | --(*count); |
55 | |
56 | int before_value = *count; |
57 | |
58 | t->expires_at(expiry_time: t->expires_at() + chronons::seconds(1)); |
59 | t->async_wait(handler: bindns::bind(f: decrement_to_zero, a1: t, a2: count)); |
60 | |
61 | // Completion cannot nest, so count value should remain unchanged. |
62 | BOOST_ASIO_CHECK(*count == before_value); |
63 | } |
64 | } |
65 | |
66 | void increment_if_not_cancelled(int* count, |
67 | const boost::system::error_code& ec) |
68 | { |
69 | if (!ec) |
70 | ++(*count); |
71 | } |
72 | |
73 | void cancel_timer(boost::asio::system_timer* t) |
74 | { |
75 | std::size_t num_cancelled = t->cancel(); |
76 | BOOST_ASIO_CHECK(num_cancelled == 1); |
77 | } |
78 | |
79 | void cancel_one_timer(boost::asio::system_timer* t) |
80 | { |
81 | std::size_t num_cancelled = t->cancel_one(); |
82 | BOOST_ASIO_CHECK(num_cancelled == 1); |
83 | } |
84 | |
85 | boost::asio::system_timer::time_point now() |
86 | { |
87 | return boost::asio::system_timer::clock_type::now(); |
88 | } |
89 | |
90 | void system_timer_test() |
91 | { |
92 | using chronons::seconds; |
93 | using chronons::microseconds; |
94 | #if !defined(BOOST_ASIO_HAS_BOOST_BIND) |
95 | using std::placeholders::_1; |
96 | using std::placeholders::_2; |
97 | #endif // !defined(BOOST_ASIO_HAS_BOOST_BIND) |
98 | |
99 | boost::asio::io_service ios; |
100 | int count = 0; |
101 | |
102 | boost::asio::system_timer::time_point start = now(); |
103 | |
104 | boost::asio::system_timer t1(ios, seconds(1)); |
105 | t1.wait(); |
106 | |
107 | // The timer must block until after its expiry time. |
108 | boost::asio::system_timer::time_point end = now(); |
109 | boost::asio::system_timer::time_point expected_end = start + seconds(1); |
110 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); |
111 | |
112 | start = now(); |
113 | |
114 | boost::asio::system_timer t2(ios, seconds(1) + microseconds(500000)); |
115 | t2.wait(); |
116 | |
117 | // The timer must block until after its expiry time. |
118 | end = now(); |
119 | expected_end = start + seconds(1) + microseconds(500000); |
120 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); |
121 | |
122 | t2.expires_at(expiry_time: t2.expires_at() + seconds(1)); |
123 | t2.wait(); |
124 | |
125 | // The timer must block until after its expiry time. |
126 | end = now(); |
127 | expected_end += seconds(1); |
128 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); |
129 | |
130 | start = now(); |
131 | |
132 | t2.expires_from_now(expiry_time: seconds(1) + microseconds(200000)); |
133 | t2.wait(); |
134 | |
135 | // The timer must block until after its expiry time. |
136 | end = now(); |
137 | expected_end = start + seconds(1) + microseconds(200000); |
138 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); |
139 | |
140 | start = now(); |
141 | |
142 | boost::asio::system_timer t3(ios, seconds(5)); |
143 | t3.async_wait(handler: bindns::bind(f: increment, a1: &count)); |
144 | |
145 | // No completions can be delivered until run() is called. |
146 | BOOST_ASIO_CHECK(count == 0); |
147 | |
148 | ios.run(); |
149 | |
150 | // The run() call will not return until all operations have finished, and |
151 | // this should not be until after the timer's expiry time. |
152 | BOOST_ASIO_CHECK(count == 1); |
153 | end = now(); |
154 | expected_end = start + seconds(1); |
155 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); |
156 | |
157 | count = 3; |
158 | start = now(); |
159 | |
160 | boost::asio::system_timer t4(ios, seconds(1)); |
161 | t4.async_wait(handler: bindns::bind(f: decrement_to_zero, a1: &t4, a2: &count)); |
162 | |
163 | // No completions can be delivered until run() is called. |
164 | BOOST_ASIO_CHECK(count == 3); |
165 | |
166 | ios.reset(); |
167 | ios.run(); |
168 | |
169 | // The run() call will not return until all operations have finished, and |
170 | // this should not be until after the timer's final expiry time. |
171 | BOOST_ASIO_CHECK(count == 0); |
172 | end = now(); |
173 | expected_end = start + seconds(3); |
174 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); |
175 | |
176 | count = 0; |
177 | start = now(); |
178 | |
179 | boost::asio::system_timer t5(ios, seconds(10)); |
180 | t5.async_wait(handler: bindns::bind(f: increment_if_not_cancelled, a1: &count, a2: _1)); |
181 | boost::asio::system_timer t6(ios, seconds(1)); |
182 | t6.async_wait(handler: bindns::bind(f: cancel_timer, a1: &t5)); |
183 | |
184 | // No completions can be delivered until run() is called. |
185 | BOOST_ASIO_CHECK(count == 0); |
186 | |
187 | ios.reset(); |
188 | ios.run(); |
189 | |
190 | // The timer should have been cancelled, so count should not have changed. |
191 | // The total run time should not have been much more than 1 second (and |
192 | // certainly far less than 10 seconds). |
193 | BOOST_ASIO_CHECK(count == 0); |
194 | end = now(); |
195 | expected_end = start + seconds(2); |
196 | BOOST_ASIO_CHECK(end < expected_end); |
197 | |
198 | // Wait on the timer again without cancelling it. This time the asynchronous |
199 | // wait should run to completion and increment the counter. |
200 | t5.async_wait(handler: bindns::bind(f: increment_if_not_cancelled, a1: &count, a2: _1)); |
201 | |
202 | ios.reset(); |
203 | ios.run(); |
204 | |
205 | // The timer should not have been cancelled, so count should have changed. |
206 | // The total time since the timer was created should be more than 10 seconds. |
207 | BOOST_ASIO_CHECK(count == 1); |
208 | end = now(); |
209 | expected_end = start + seconds(10); |
210 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); |
211 | |
212 | count = 0; |
213 | start = now(); |
214 | |
215 | // Start two waits on a timer, one of which will be cancelled. The one |
216 | // which is not cancelled should still run to completion and increment the |
217 | // counter. |
218 | boost::asio::system_timer t7(ios, seconds(3)); |
219 | t7.async_wait(handler: bindns::bind(f: increment_if_not_cancelled, a1: &count, a2: _1)); |
220 | t7.async_wait(handler: bindns::bind(f: increment_if_not_cancelled, a1: &count, a2: _1)); |
221 | boost::asio::system_timer t8(ios, seconds(1)); |
222 | t8.async_wait(handler: bindns::bind(f: cancel_one_timer, a1: &t7)); |
223 | |
224 | ios.reset(); |
225 | ios.run(); |
226 | |
227 | // One of the waits should not have been cancelled, so count should have |
228 | // changed. The total time since the timer was created should be more than 3 |
229 | // seconds. |
230 | BOOST_ASIO_CHECK(count == 1); |
231 | end = now(); |
232 | expected_end = start + seconds(3); |
233 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); |
234 | } |
235 | |
236 | void timer_handler(const boost::system::error_code&) |
237 | { |
238 | } |
239 | |
240 | void system_timer_cancel_test() |
241 | { |
242 | static boost::asio::io_service io_service; |
243 | struct timer |
244 | { |
245 | boost::asio::system_timer t; |
246 | timer() : t(io_service) |
247 | { |
248 | t.expires_at(expiry_time: (boost::asio::system_timer::time_point::max)()); |
249 | } |
250 | } timers[50]; |
251 | |
252 | timers[2].t.async_wait(handler: &timer_handler); |
253 | timers[41].t.async_wait(handler: &timer_handler); |
254 | for (int i = 10; i < 20; ++i) |
255 | timers[i].t.async_wait(handler: &timer_handler); |
256 | |
257 | BOOST_ASIO_CHECK(timers[2].t.cancel() == 1); |
258 | BOOST_ASIO_CHECK(timers[41].t.cancel() == 1); |
259 | for (int i = 10; i < 20; ++i) |
260 | BOOST_ASIO_CHECK(timers[i].t.cancel() == 1); |
261 | } |
262 | |
263 | struct custom_allocation_timer_handler |
264 | { |
265 | custom_allocation_timer_handler(int* count) : count_(count) {} |
266 | void operator()(const boost::system::error_code&) {} |
267 | int* count_; |
268 | }; |
269 | |
270 | void* asio_handler_allocate(std::size_t size, |
271 | custom_allocation_timer_handler* handler) |
272 | { |
273 | ++(*handler->count_); |
274 | return ::operator new(size); |
275 | } |
276 | |
277 | void asio_handler_deallocate(void* pointer, std::size_t, |
278 | custom_allocation_timer_handler* handler) |
279 | { |
280 | --(*handler->count_); |
281 | ::operator delete(pointer); |
282 | } |
283 | |
284 | void system_timer_custom_allocation_test() |
285 | { |
286 | static boost::asio::io_service io_service; |
287 | struct timer |
288 | { |
289 | boost::asio::system_timer t; |
290 | timer() : t(io_service) {} |
291 | } timers[100]; |
292 | |
293 | int allocation_count = 0; |
294 | |
295 | for (int i = 0; i < 50; ++i) |
296 | { |
297 | timers[i].t.expires_at(expiry_time: (boost::asio::system_timer::time_point::max)()); |
298 | timers[i].t.async_wait(handler: custom_allocation_timer_handler(&allocation_count)); |
299 | } |
300 | |
301 | for (int i = 50; i < 100; ++i) |
302 | { |
303 | timers[i].t.expires_at(expiry_time: (boost::asio::system_timer::time_point::min)()); |
304 | timers[i].t.async_wait(handler: custom_allocation_timer_handler(&allocation_count)); |
305 | } |
306 | |
307 | for (int i = 0; i < 50; ++i) |
308 | timers[i].t.cancel(); |
309 | |
310 | io_service.run(); |
311 | |
312 | BOOST_ASIO_CHECK(allocation_count == 0); |
313 | } |
314 | |
315 | void io_service_run(boost::asio::io_service* ios) |
316 | { |
317 | ios->run(); |
318 | } |
319 | |
320 | void system_timer_thread_test() |
321 | { |
322 | boost::asio::io_service ios; |
323 | boost::asio::io_service::work w(ios); |
324 | boost::asio::system_timer t1(ios); |
325 | boost::asio::system_timer t2(ios); |
326 | int count = 0; |
327 | |
328 | boost::asio::detail::thread th(bindns::bind(f: io_service_run, a1: &ios)); |
329 | |
330 | t2.expires_from_now(expiry_time: chronons::seconds(2)); |
331 | t2.wait(); |
332 | |
333 | t1.expires_from_now(expiry_time: chronons::seconds(2)); |
334 | t1.async_wait(handler: bindns::bind(f: increment, a1: &count)); |
335 | |
336 | t2.expires_from_now(expiry_time: chronons::seconds(4)); |
337 | t2.wait(); |
338 | |
339 | ios.stop(); |
340 | th.join(); |
341 | |
342 | BOOST_ASIO_CHECK(count == 1); |
343 | } |
344 | |
345 | BOOST_ASIO_TEST_SUITE |
346 | ( |
347 | "system_timer" , |
348 | BOOST_ASIO_TEST_CASE(system_timer_test) |
349 | BOOST_ASIO_TEST_CASE(system_timer_cancel_test) |
350 | BOOST_ASIO_TEST_CASE(system_timer_custom_allocation_test) |
351 | BOOST_ASIO_TEST_CASE(system_timer_thread_test) |
352 | ) |
353 | #else // defined(BOOST_ASIO_HAS_STD_CHRONO) |
354 | BOOST_ASIO_TEST_SUITE |
355 | ( |
356 | "system_timer" , |
357 | BOOST_ASIO_TEST_CASE(null_test) |
358 | ) |
359 | #endif // defined(BOOST_ASIO_HAS_STD_CHRONO) |
360 | |