1 //
2 // system_timer.cpp
3 // ~~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2021 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/executor_work_guard.hpp>
29 #include <boost/asio/io_context.hpp>
30 #include <boost/asio/detail/thread.hpp>
31
32 #if defined(BOOST_ASIO_HAS_BOOST_BIND)
33 # include <boost/bind/bind.hpp>
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
increment(int * count)44 void increment(int* count)
45 {
46 ++(*count);
47 }
48
decrement_to_zero(boost::asio::system_timer * t,int * count)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
57 t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
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
increment_if_not_cancelled(int * count,const boost::system::error_code & ec)65 void increment_if_not_cancelled(int* count,
66 const boost::system::error_code& ec)
67 {
68 if (!ec)
69 ++(*count);
70 }
71
cancel_timer(boost::asio::system_timer * t)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
cancel_one_timer(boost::asio::system_timer * t)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
now()84 boost::asio::system_timer::time_point now()
85 {
86 return boost::asio::system_timer::clock_type::now();
87 }
88
system_timer_test()89 void system_timer_test()
90 {
91 using boost::asio::chrono::seconds;
92 using boost::asio::chrono::microseconds;
93 using bindns::placeholders::_1;
94 using bindns::placeholders::_2;
95
96 boost::asio::io_context ioc;
97 const boost::asio::io_context::executor_type ioc_ex = ioc.get_executor();
98 int count = 0;
99
100 boost::asio::system_timer::time_point start = now();
101
102 boost::asio::system_timer t1(ioc, seconds(1));
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
112 boost::asio::system_timer t2(ioc_ex, seconds(1) + microseconds(500000));
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
120 t2.expires_at(t2.expiry() + seconds(1));
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
130 t2.expires_after(seconds(1) + microseconds(200000));
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
140 boost::asio::system_timer t3(ioc, seconds(5));
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
146 ioc.run();
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
158 boost::asio::system_timer t4(ioc, seconds(1));
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
164 ioc.restart();
165 ioc.run();
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
177 boost::asio::system_timer t5(ioc, seconds(10));
178 t5.async_wait(bindns::bind(increment_if_not_cancelled, &count, _1));
179 boost::asio::system_timer t6(ioc, seconds(1));
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
185 ioc.restart();
186 ioc.run();
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
200 ioc.restart();
201 ioc.run();
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.
216 boost::asio::system_timer t7(ioc, seconds(3));
217 t7.async_wait(bindns::bind(increment_if_not_cancelled, &count, _1));
218 t7.async_wait(bindns::bind(increment_if_not_cancelled, &count, _1));
219 boost::asio::system_timer t8(ioc, seconds(1));
220 t8.async_wait(bindns::bind(cancel_one_timer, &t7));
221
222 ioc.restart();
223 ioc.run();
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
234 struct timer_handler
235 {
timer_handlertimer_handler236 timer_handler() {}
operator ()timer_handler237 void operator()(const boost::system::error_code&) {}
238 #if defined(BOOST_ASIO_HAS_MOVE)
timer_handlertimer_handler239 timer_handler(timer_handler&&) {}
240 private:
241 timer_handler(const timer_handler&);
242 #endif // defined(BOOST_ASIO_HAS_MOVE)
243 };
244
system_timer_cancel_test()245 void system_timer_cancel_test()
246 {
247 static boost::asio::io_context io_context;
248 struct timer
249 {
250 boost::asio::system_timer t;
251 timer() : t(io_context)
252 {
253 t.expires_at((boost::asio::system_timer::time_point::max)());
254 }
255 } timers[50];
256
257 timers[2].t.async_wait(timer_handler());
258 timers[41].t.async_wait(timer_handler());
259 for (int i = 10; i < 20; ++i)
260 timers[i].t.async_wait(timer_handler());
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 {
custom_allocation_timer_handlercustom_allocation_timer_handler270 custom_allocation_timer_handler(int* count) : count_(count) {}
operator ()custom_allocation_timer_handler271 void operator()(const boost::system::error_code&) {}
272 int* count_;
273
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 };
290
allocatorcustom_allocation_timer_handler::allocator291 explicit allocator(int* count) BOOST_ASIO_NOEXCEPT
292 : count_(count)
293 {
294 }
295
allocatorcustom_allocation_timer_handler::allocator296 allocator(const allocator& other) BOOST_ASIO_NOEXCEPT
297 : count_(other.count_)
298 {
299 }
300
301 template <typename U>
allocatorcustom_allocation_timer_handler::allocator302 allocator(const allocator<U>& other) BOOST_ASIO_NOEXCEPT
303 : count_(other.count_)
304 {
305 }
306
allocatecustom_allocation_timer_handler::allocator307 pointer allocate(size_type n, const void* = 0)
308 {
309 ++(*count_);
310 return static_cast<T*>(::operator new(sizeof(T) * n));
311 }
312
deallocatecustom_allocation_timer_handler::allocator313 void deallocate(pointer p, size_type)
314 {
315 --(*count_);
316 ::operator delete(p);
317 }
318
max_sizecustom_allocation_timer_handler::allocator319 size_type max_size() const
320 {
321 return ~size_type(0);
322 }
323
constructcustom_allocation_timer_handler::allocator324 void construct(pointer p, const T& v)
325 {
326 new (p) T(v);
327 }
328
destroycustom_allocation_timer_handler::allocator329 void destroy(pointer p)
330 {
331 p->~T();
332 }
333
334 int* count_;
335 };
336
337 typedef allocator<int> allocator_type;
338
get_allocatorcustom_allocation_timer_handler339 allocator_type get_allocator() const BOOST_ASIO_NOEXCEPT
340 {
341 return allocator_type(count_);
342 }
343 };
344
system_timer_custom_allocation_test()345 void system_timer_custom_allocation_test()
346 {
347 static boost::asio::io_context io_context;
348 struct timer
349 {
350 boost::asio::system_timer t;
351 timer() : t(io_context) {}
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
371 io_context.run();
372
373 BOOST_ASIO_CHECK(allocation_count == 0);
374 }
375
io_context_run(boost::asio::io_context * ioc)376 void io_context_run(boost::asio::io_context* ioc)
377 {
378 ioc->run();
379 }
380
system_timer_thread_test()381 void system_timer_thread_test()
382 {
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);
388 int count = 0;
389
390 boost::asio::detail::thread th(bindns::bind(io_context_run, &ioc));
391
392 t2.expires_after(boost::asio::chrono::seconds(2));
393 t2.wait();
394
395 t1.expires_after(boost::asio::chrono::seconds(2));
396 t1.async_wait(bindns::bind(increment, &count));
397
398 t2.expires_after(boost::asio::chrono::seconds(4));
399 t2.wait();
400
401 ioc.stop();
402 th.join();
403
404 BOOST_ASIO_CHECK(count == 1);
405 }
406
407 #if defined(BOOST_ASIO_HAS_MOVE)
make_timer(boost::asio::io_context & ioc,int * count)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 }
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
make_convertible_timer(boost::asio::io_context & ioc,int * count)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 }
428 #endif
429
system_timer_move_test()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);
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);
466 #endif // defined(BOOST_ASIO_HAS_MOVE)
467 }
468
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)
476 BOOST_ASIO_TEST_CASE(system_timer_move_test)
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)
485