• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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