]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // system_timer.cpp | |
3 | // ~~~~~~~~~~~~~~~~ | |
4 | // | |
f67539c2 | 5 | // Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
7c673cae FG |
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 | ||
b32b8144 FG |
28 | #include <boost/asio/executor_work_guard.hpp> |
29 | #include <boost/asio/io_context.hpp> | |
7c673cae FG |
30 | #include <boost/asio/detail/thread.hpp> |
31 | ||
32 | #if defined(BOOST_ASIO_HAS_BOOST_BIND) | |
f67539c2 | 33 | # include <boost/bind/bind.hpp> |
7c673cae FG |
34 | #else // defined(BOOST_ASIO_HAS_BOOST_BIND) |
35 | # include <functional> | |
36 | #endif // defined(BOOST_ASIO_HAS_BOOST_BIND) | |
37 | ||
38 | #if defined(BOOST_ASIO_HAS_BOOST_BIND) | |
39 | namespace bindns = boost; | |
40 | #else // defined(BOOST_ASIO_HAS_BOOST_BIND) | |
41 | namespace bindns = std; | |
42 | #endif // defined(BOOST_ASIO_HAS_BOOST_BIND) | |
43 | ||
7c673cae FG |
44 | void increment(int* count) |
45 | { | |
46 | ++(*count); | |
47 | } | |
48 | ||
49 | void decrement_to_zero(boost::asio::system_timer* t, int* count) | |
50 | { | |
51 | if (*count > 0) | |
52 | { | |
53 | --(*count); | |
54 | ||
55 | int before_value = *count; | |
56 | ||
b32b8144 | 57 | t->expires_at(t->expiry() + boost::asio::chrono::seconds(1)); |
7c673cae FG |
58 | t->async_wait(bindns::bind(decrement_to_zero, t, count)); |
59 | ||
60 | // Completion cannot nest, so count value should remain unchanged. | |
61 | BOOST_ASIO_CHECK(*count == before_value); | |
62 | } | |
63 | } | |
64 | ||
65 | void increment_if_not_cancelled(int* count, | |
66 | const boost::system::error_code& ec) | |
67 | { | |
68 | if (!ec) | |
69 | ++(*count); | |
70 | } | |
71 | ||
72 | void cancel_timer(boost::asio::system_timer* t) | |
73 | { | |
74 | std::size_t num_cancelled = t->cancel(); | |
75 | BOOST_ASIO_CHECK(num_cancelled == 1); | |
76 | } | |
77 | ||
78 | void cancel_one_timer(boost::asio::system_timer* t) | |
79 | { | |
80 | std::size_t num_cancelled = t->cancel_one(); | |
81 | BOOST_ASIO_CHECK(num_cancelled == 1); | |
82 | } | |
83 | ||
84 | boost::asio::system_timer::time_point now() | |
85 | { | |
86 | return boost::asio::system_timer::clock_type::now(); | |
87 | } | |
88 | ||
89 | void system_timer_test() | |
90 | { | |
b32b8144 FG |
91 | using boost::asio::chrono::seconds; |
92 | using boost::asio::chrono::microseconds; | |
f67539c2 TL |
93 | using bindns::placeholders::_1; |
94 | using bindns::placeholders::_2; | |
7c673cae | 95 | |
b32b8144 | 96 | boost::asio::io_context ioc; |
92f5a8d4 | 97 | const boost::asio::io_context::executor_type ioc_ex = ioc.get_executor(); |
7c673cae FG |
98 | int count = 0; |
99 | ||
100 | boost::asio::system_timer::time_point start = now(); | |
101 | ||
b32b8144 | 102 | boost::asio::system_timer t1(ioc, seconds(1)); |
7c673cae FG |
103 | t1.wait(); |
104 | ||
105 | // The timer must block until after its expiry time. | |
106 | boost::asio::system_timer::time_point end = now(); | |
107 | boost::asio::system_timer::time_point expected_end = start + seconds(1); | |
108 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); | |
109 | ||
110 | start = now(); | |
111 | ||
92f5a8d4 | 112 | boost::asio::system_timer t2(ioc_ex, seconds(1) + microseconds(500000)); |
7c673cae FG |
113 | t2.wait(); |
114 | ||
115 | // The timer must block until after its expiry time. | |
116 | end = now(); | |
117 | expected_end = start + seconds(1) + microseconds(500000); | |
118 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); | |
119 | ||
b32b8144 | 120 | t2.expires_at(t2.expiry() + seconds(1)); |
7c673cae FG |
121 | t2.wait(); |
122 | ||
123 | // The timer must block until after its expiry time. | |
124 | end = now(); | |
125 | expected_end += seconds(1); | |
126 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); | |
127 | ||
128 | start = now(); | |
129 | ||
b32b8144 | 130 | t2.expires_after(seconds(1) + microseconds(200000)); |
7c673cae FG |
131 | t2.wait(); |
132 | ||
133 | // The timer must block until after its expiry time. | |
134 | end = now(); | |
135 | expected_end = start + seconds(1) + microseconds(200000); | |
136 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); | |
137 | ||
138 | start = now(); | |
139 | ||
b32b8144 | 140 | boost::asio::system_timer t3(ioc, seconds(5)); |
7c673cae FG |
141 | t3.async_wait(bindns::bind(increment, &count)); |
142 | ||
143 | // No completions can be delivered until run() is called. | |
144 | BOOST_ASIO_CHECK(count == 0); | |
145 | ||
b32b8144 | 146 | ioc.run(); |
7c673cae FG |
147 | |
148 | // The run() call will not return until all operations have finished, and | |
149 | // this should not be until after the timer's expiry time. | |
150 | BOOST_ASIO_CHECK(count == 1); | |
151 | end = now(); | |
152 | expected_end = start + seconds(1); | |
153 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); | |
154 | ||
155 | count = 3; | |
156 | start = now(); | |
157 | ||
b32b8144 | 158 | boost::asio::system_timer t4(ioc, seconds(1)); |
7c673cae FG |
159 | t4.async_wait(bindns::bind(decrement_to_zero, &t4, &count)); |
160 | ||
161 | // No completions can be delivered until run() is called. | |
162 | BOOST_ASIO_CHECK(count == 3); | |
163 | ||
b32b8144 FG |
164 | ioc.restart(); |
165 | ioc.run(); | |
7c673cae FG |
166 | |
167 | // The run() call will not return until all operations have finished, and | |
168 | // this should not be until after the timer's final expiry time. | |
169 | BOOST_ASIO_CHECK(count == 0); | |
170 | end = now(); | |
171 | expected_end = start + seconds(3); | |
172 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); | |
173 | ||
174 | count = 0; | |
175 | start = now(); | |
176 | ||
b32b8144 | 177 | boost::asio::system_timer t5(ioc, seconds(10)); |
7c673cae | 178 | t5.async_wait(bindns::bind(increment_if_not_cancelled, &count, _1)); |
b32b8144 | 179 | boost::asio::system_timer t6(ioc, seconds(1)); |
7c673cae FG |
180 | t6.async_wait(bindns::bind(cancel_timer, &t5)); |
181 | ||
182 | // No completions can be delivered until run() is called. | |
183 | BOOST_ASIO_CHECK(count == 0); | |
184 | ||
b32b8144 FG |
185 | ioc.restart(); |
186 | ioc.run(); | |
7c673cae FG |
187 | |
188 | // The timer should have been cancelled, so count should not have changed. | |
189 | // The total run time should not have been much more than 1 second (and | |
190 | // certainly far less than 10 seconds). | |
191 | BOOST_ASIO_CHECK(count == 0); | |
192 | end = now(); | |
193 | expected_end = start + seconds(2); | |
194 | BOOST_ASIO_CHECK(end < expected_end); | |
195 | ||
196 | // Wait on the timer again without cancelling it. This time the asynchronous | |
197 | // wait should run to completion and increment the counter. | |
198 | t5.async_wait(bindns::bind(increment_if_not_cancelled, &count, _1)); | |
199 | ||
b32b8144 FG |
200 | ioc.restart(); |
201 | ioc.run(); | |
7c673cae FG |
202 | |
203 | // The timer should not have been cancelled, so count should have changed. | |
204 | // The total time since the timer was created should be more than 10 seconds. | |
205 | BOOST_ASIO_CHECK(count == 1); | |
206 | end = now(); | |
207 | expected_end = start + seconds(10); | |
208 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); | |
209 | ||
210 | count = 0; | |
211 | start = now(); | |
212 | ||
213 | // Start two waits on a timer, one of which will be cancelled. The one | |
214 | // which is not cancelled should still run to completion and increment the | |
215 | // counter. | |
b32b8144 | 216 | boost::asio::system_timer t7(ioc, seconds(3)); |
7c673cae FG |
217 | t7.async_wait(bindns::bind(increment_if_not_cancelled, &count, _1)); |
218 | t7.async_wait(bindns::bind(increment_if_not_cancelled, &count, _1)); | |
b32b8144 | 219 | boost::asio::system_timer t8(ioc, seconds(1)); |
7c673cae FG |
220 | t8.async_wait(bindns::bind(cancel_one_timer, &t7)); |
221 | ||
b32b8144 FG |
222 | ioc.restart(); |
223 | ioc.run(); | |
7c673cae FG |
224 | |
225 | // One of the waits should not have been cancelled, so count should have | |
226 | // changed. The total time since the timer was created should be more than 3 | |
227 | // seconds. | |
228 | BOOST_ASIO_CHECK(count == 1); | |
229 | end = now(); | |
230 | expected_end = start + seconds(3); | |
231 | BOOST_ASIO_CHECK(expected_end < end || expected_end == end); | |
232 | } | |
233 | ||
b32b8144 | 234 | struct timer_handler |
7c673cae | 235 | { |
b32b8144 FG |
236 | timer_handler() {} |
237 | void operator()(const boost::system::error_code&) {} | |
238 | #if defined(BOOST_ASIO_HAS_MOVE) | |
239 | timer_handler(timer_handler&&) {} | |
240 | private: | |
241 | timer_handler(const timer_handler&); | |
242 | #endif // defined(BOOST_ASIO_HAS_MOVE) | |
243 | }; | |
7c673cae FG |
244 | |
245 | void system_timer_cancel_test() | |
246 | { | |
b32b8144 | 247 | static boost::asio::io_context io_context; |
7c673cae FG |
248 | struct timer |
249 | { | |
250 | boost::asio::system_timer t; | |
b32b8144 | 251 | timer() : t(io_context) |
7c673cae FG |
252 | { |
253 | t.expires_at((boost::asio::system_timer::time_point::max)()); | |
254 | } | |
255 | } timers[50]; | |
256 | ||
b32b8144 FG |
257 | timers[2].t.async_wait(timer_handler()); |
258 | timers[41].t.async_wait(timer_handler()); | |
7c673cae | 259 | for (int i = 10; i < 20; ++i) |
b32b8144 | 260 | timers[i].t.async_wait(timer_handler()); |
7c673cae FG |
261 | |
262 | BOOST_ASIO_CHECK(timers[2].t.cancel() == 1); | |
263 | BOOST_ASIO_CHECK(timers[41].t.cancel() == 1); | |
264 | for (int i = 10; i < 20; ++i) | |
265 | BOOST_ASIO_CHECK(timers[i].t.cancel() == 1); | |
266 | } | |
267 | ||
268 | struct custom_allocation_timer_handler | |
269 | { | |
270 | custom_allocation_timer_handler(int* count) : count_(count) {} | |
271 | void operator()(const boost::system::error_code&) {} | |
272 | int* count_; | |
7c673cae | 273 | |
20effc67 TL |
274 | template <typename T> |
275 | struct allocator | |
276 | { | |
277 | typedef size_t size_type; | |
278 | typedef ptrdiff_t difference_type; | |
279 | typedef T* pointer; | |
280 | typedef const T* const_pointer; | |
281 | typedef T& reference; | |
282 | typedef const T& const_reference; | |
283 | typedef T value_type; | |
284 | ||
285 | template <typename U> | |
286 | struct rebind | |
287 | { | |
288 | typedef allocator<U> other; | |
289 | }; | |
7c673cae | 290 | |
20effc67 TL |
291 | explicit allocator(int* count) BOOST_ASIO_NOEXCEPT |
292 | : count_(count) | |
293 | { | |
294 | } | |
295 | ||
296 | allocator(const allocator& other) BOOST_ASIO_NOEXCEPT | |
297 | : count_(other.count_) | |
298 | { | |
299 | } | |
300 | ||
301 | template <typename U> | |
302 | allocator(const allocator<U>& other) BOOST_ASIO_NOEXCEPT | |
303 | : count_(other.count_) | |
304 | { | |
305 | } | |
306 | ||
307 | pointer allocate(size_type n, const void* = 0) | |
308 | { | |
309 | ++(*count_); | |
310 | return static_cast<T*>(::operator new(sizeof(T) * n)); | |
311 | } | |
312 | ||
313 | void deallocate(pointer p, size_type) | |
314 | { | |
315 | --(*count_); | |
316 | ::operator delete(p); | |
317 | } | |
318 | ||
319 | size_type max_size() const | |
320 | { | |
321 | return ~size_type(0); | |
322 | } | |
323 | ||
324 | void construct(pointer p, const T& v) | |
325 | { | |
326 | new (p) T(v); | |
327 | } | |
328 | ||
329 | void destroy(pointer p) | |
330 | { | |
331 | p->~T(); | |
332 | } | |
333 | ||
334 | int* count_; | |
335 | }; | |
336 | ||
337 | typedef allocator<int> allocator_type; | |
338 | ||
339 | allocator_type get_allocator() const BOOST_ASIO_NOEXCEPT | |
340 | { | |
341 | return allocator_type(count_); | |
342 | } | |
343 | }; | |
7c673cae FG |
344 | |
345 | void system_timer_custom_allocation_test() | |
346 | { | |
b32b8144 | 347 | static boost::asio::io_context io_context; |
7c673cae FG |
348 | struct timer |
349 | { | |
350 | boost::asio::system_timer t; | |
b32b8144 | 351 | timer() : t(io_context) {} |
7c673cae FG |
352 | } timers[100]; |
353 | ||
354 | int allocation_count = 0; | |
355 | ||
356 | for (int i = 0; i < 50; ++i) | |
357 | { | |
358 | timers[i].t.expires_at((boost::asio::system_timer::time_point::max)()); | |
359 | timers[i].t.async_wait(custom_allocation_timer_handler(&allocation_count)); | |
360 | } | |
361 | ||
362 | for (int i = 50; i < 100; ++i) | |
363 | { | |
364 | timers[i].t.expires_at((boost::asio::system_timer::time_point::min)()); | |
365 | timers[i].t.async_wait(custom_allocation_timer_handler(&allocation_count)); | |
366 | } | |
367 | ||
368 | for (int i = 0; i < 50; ++i) | |
369 | timers[i].t.cancel(); | |
370 | ||
b32b8144 | 371 | io_context.run(); |
7c673cae FG |
372 | |
373 | BOOST_ASIO_CHECK(allocation_count == 0); | |
374 | } | |
375 | ||
b32b8144 | 376 | void io_context_run(boost::asio::io_context* ioc) |
7c673cae | 377 | { |
b32b8144 | 378 | ioc->run(); |
7c673cae FG |
379 | } |
380 | ||
381 | void system_timer_thread_test() | |
382 | { | |
b32b8144 FG |
383 | boost::asio::io_context ioc; |
384 | boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work | |
385 | = boost::asio::make_work_guard(ioc); | |
386 | boost::asio::system_timer t1(ioc); | |
387 | boost::asio::system_timer t2(ioc); | |
7c673cae FG |
388 | int count = 0; |
389 | ||
b32b8144 | 390 | boost::asio::detail::thread th(bindns::bind(io_context_run, &ioc)); |
7c673cae | 391 | |
b32b8144 | 392 | t2.expires_after(boost::asio::chrono::seconds(2)); |
7c673cae FG |
393 | t2.wait(); |
394 | ||
b32b8144 | 395 | t1.expires_after(boost::asio::chrono::seconds(2)); |
7c673cae FG |
396 | t1.async_wait(bindns::bind(increment, &count)); |
397 | ||
b32b8144 | 398 | t2.expires_after(boost::asio::chrono::seconds(4)); |
7c673cae FG |
399 | t2.wait(); |
400 | ||
b32b8144 | 401 | ioc.stop(); |
7c673cae FG |
402 | th.join(); |
403 | ||
404 | BOOST_ASIO_CHECK(count == 1); | |
405 | } | |
406 | ||
b32b8144 FG |
407 | #if defined(BOOST_ASIO_HAS_MOVE) |
408 | boost::asio::system_timer make_timer(boost::asio::io_context& ioc, int* count) | |
409 | { | |
410 | boost::asio::system_timer t(ioc); | |
411 | t.expires_after(boost::asio::chrono::seconds(1)); | |
412 | t.async_wait(bindns::bind(increment, count)); | |
413 | return t; | |
414 | } | |
20effc67 TL |
415 | |
416 | typedef boost::asio::basic_waitable_timer< | |
417 | boost::asio::system_timer::clock_type, | |
418 | boost::asio::system_timer::traits_type, | |
419 | boost::asio::io_context::executor_type> io_context_system_timer; | |
420 | ||
421 | io_context_system_timer make_convertible_timer(boost::asio::io_context& ioc, int* count) | |
422 | { | |
423 | io_context_system_timer t(ioc); | |
424 | t.expires_after(boost::asio::chrono::seconds(1)); | |
425 | t.async_wait(bindns::bind(increment, count)); | |
426 | return t; | |
427 | } | |
b32b8144 FG |
428 | #endif |
429 | ||
430 | void system_timer_move_test() | |
431 | { | |
432 | #if defined(BOOST_ASIO_HAS_MOVE) | |
433 | boost::asio::io_context io_context1; | |
434 | boost::asio::io_context io_context2; | |
435 | int count = 0; | |
436 | ||
437 | boost::asio::system_timer t1 = make_timer(io_context1, &count); | |
438 | boost::asio::system_timer t2 = make_timer(io_context2, &count); | |
439 | boost::asio::system_timer t3 = std::move(t1); | |
440 | ||
441 | t2 = std::move(t1); | |
442 | ||
443 | io_context2.run(); | |
444 | ||
445 | BOOST_ASIO_CHECK(count == 1); | |
446 | ||
447 | io_context1.run(); | |
448 | ||
449 | BOOST_ASIO_CHECK(count == 2); | |
20effc67 TL |
450 | |
451 | boost::asio::system_timer t4 = make_convertible_timer(io_context1, &count); | |
452 | boost::asio::system_timer t5 = make_convertible_timer(io_context2, &count); | |
453 | boost::asio::system_timer t6 = std::move(t4); | |
454 | ||
455 | t2 = std::move(t4); | |
456 | ||
457 | io_context2.restart(); | |
458 | io_context2.run(); | |
459 | ||
460 | BOOST_ASIO_CHECK(count == 3); | |
461 | ||
462 | io_context1.restart(); | |
463 | io_context1.run(); | |
464 | ||
465 | BOOST_ASIO_CHECK(count == 4); | |
b32b8144 FG |
466 | #endif // defined(BOOST_ASIO_HAS_MOVE) |
467 | } | |
468 | ||
7c673cae FG |
469 | BOOST_ASIO_TEST_SUITE |
470 | ( | |
471 | "system_timer", | |
472 | BOOST_ASIO_TEST_CASE(system_timer_test) | |
473 | BOOST_ASIO_TEST_CASE(system_timer_cancel_test) | |
474 | BOOST_ASIO_TEST_CASE(system_timer_custom_allocation_test) | |
475 | BOOST_ASIO_TEST_CASE(system_timer_thread_test) | |
b32b8144 | 476 | BOOST_ASIO_TEST_CASE(system_timer_move_test) |
7c673cae FG |
477 | ) |
478 | #else // defined(BOOST_ASIO_HAS_STD_CHRONO) | |
479 | BOOST_ASIO_TEST_SUITE | |
480 | ( | |
481 | "system_timer", | |
482 | BOOST_ASIO_TEST_CASE(null_test) | |
483 | ) | |
484 | #endif // defined(BOOST_ASIO_HAS_STD_CHRONO) |