• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // io_context_strand.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 // Test that header file is self-contained.
17 #include <boost/asio/io_context_strand.hpp>
18 
19 #include <sstream>
20 #include <boost/asio/io_context.hpp>
21 #include <boost/asio/dispatch.hpp>
22 #include <boost/asio/post.hpp>
23 #include <boost/asio/detail/thread.hpp>
24 #include "unit_test.hpp"
25 
26 #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
27 # include <boost/asio/deadline_timer.hpp>
28 #else // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
29 # include <boost/asio/steady_timer.hpp>
30 #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
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 using namespace boost::asio;
39 
40 #if defined(BOOST_ASIO_HAS_BOOST_BIND)
41 namespace bindns = boost;
42 #else // defined(BOOST_ASIO_HAS_BOOST_BIND)
43 namespace bindns = std;
44 #endif
45 
46 #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
47 typedef deadline_timer timer;
48 namespace chronons = boost::posix_time;
49 #elif defined(BOOST_ASIO_HAS_CHRONO)
50 typedef steady_timer timer;
51 namespace chronons = boost::asio::chrono;
52 #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
53 
increment(int * count)54 void increment(int* count)
55 {
56   ++(*count);
57 }
58 
increment_without_lock(io_context::strand * s,int * count)59 void increment_without_lock(io_context::strand* s, int* count)
60 {
61   BOOST_ASIO_CHECK(!s->running_in_this_thread());
62 
63   int original_count = *count;
64 
65   dispatch(*s, bindns::bind(increment, count));
66 
67   // No other functions are currently executing through the locking dispatcher,
68   // so the previous call to dispatch should have successfully nested.
69   BOOST_ASIO_CHECK(*count == original_count + 1);
70 }
71 
increment_with_lock(io_context::strand * s,int * count)72 void increment_with_lock(io_context::strand* s, int* count)
73 {
74   BOOST_ASIO_CHECK(s->running_in_this_thread());
75 
76   int original_count = *count;
77 
78   dispatch(*s, bindns::bind(increment, count));
79 
80   // The current function already holds the strand's lock, so the
81   // previous call to dispatch should have successfully nested.
82   BOOST_ASIO_CHECK(*count == original_count + 1);
83 }
84 
sleep_increment(io_context * ioc,int * count)85 void sleep_increment(io_context* ioc, int* count)
86 {
87   timer t(*ioc, chronons::seconds(2));
88   t.wait();
89 
90   ++(*count);
91 }
92 
increment_by_a(int * count,int a)93 void increment_by_a(int* count, int a)
94 {
95   (*count) += a;
96 }
97 
increment_by_a_b(int * count,int a,int b)98 void increment_by_a_b(int* count, int a, int b)
99 {
100   (*count) += a + b;
101 }
102 
increment_by_a_b_c(int * count,int a,int b,int c)103 void increment_by_a_b_c(int* count, int a, int b, int c)
104 {
105   (*count) += a + b + c;
106 }
107 
increment_by_a_b_c_d(int * count,int a,int b,int c,int d)108 void increment_by_a_b_c_d(int* count, int a, int b, int c, int d)
109 {
110   (*count) += a + b + c + d;
111 }
112 
start_sleep_increments(io_context * ioc,io_context::strand * s,int * count)113 void start_sleep_increments(io_context* ioc, io_context::strand* s, int* count)
114 {
115   // Give all threads a chance to start.
116   timer t(*ioc, chronons::seconds(2));
117   t.wait();
118 
119   // Start three increments.
120   post(*s, bindns::bind(sleep_increment, ioc, count));
121   post(*s, bindns::bind(sleep_increment, ioc, count));
122   post(*s, bindns::bind(sleep_increment, ioc, count));
123 }
124 
throw_exception()125 void throw_exception()
126 {
127   throw 1;
128 }
129 
io_context_run(io_context * ioc)130 void io_context_run(io_context* ioc)
131 {
132   ioc->run();
133 }
134 
strand_test()135 void strand_test()
136 {
137   io_context ioc;
138   io_context::strand s(ioc);
139   int count = 0;
140 
141   post(ioc, bindns::bind(increment_without_lock, &s, &count));
142 
143   // No handlers can be called until run() is called.
144   BOOST_ASIO_CHECK(count == 0);
145 
146   ioc.run();
147 
148   // The run() call will not return until all work has finished.
149   BOOST_ASIO_CHECK(count == 1);
150 
151   count = 0;
152   ioc.restart();
153   post(s, bindns::bind(increment_with_lock, &s, &count));
154 
155   // No handlers can be called until run() is called.
156   BOOST_ASIO_CHECK(count == 0);
157 
158   ioc.run();
159 
160   // The run() call will not return until all work has finished.
161   BOOST_ASIO_CHECK(count == 1);
162 
163   count = 0;
164   ioc.restart();
165   post(ioc, bindns::bind(start_sleep_increments, &ioc, &s, &count));
166   boost::asio::detail::thread thread1(bindns::bind(io_context_run, &ioc));
167   boost::asio::detail::thread thread2(bindns::bind(io_context_run, &ioc));
168 
169   // Check all events run one after another even though there are two threads.
170   timer timer1(ioc, chronons::seconds(3));
171   timer1.wait();
172   BOOST_ASIO_CHECK(count == 0);
173 #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
174   timer1.expires_at(timer1.expires_at() + chronons::seconds(2));
175 #else // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
176   timer1.expires_at(timer1.expiry() + chronons::seconds(2));
177 #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
178   timer1.wait();
179   BOOST_ASIO_CHECK(count == 1);
180 #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
181   timer1.expires_at(timer1.expires_at() + chronons::seconds(2));
182 #else // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
183   timer1.expires_at(timer1.expiry() + chronons::seconds(2));
184 #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
185   timer1.wait();
186   BOOST_ASIO_CHECK(count == 2);
187 
188   thread1.join();
189   thread2.join();
190 
191   // The run() calls will not return until all work has finished.
192   BOOST_ASIO_CHECK(count == 3);
193 
194   count = 0;
195   int exception_count = 0;
196   ioc.restart();
197   post(s, throw_exception);
198   post(s, bindns::bind(increment, &count));
199   post(s, bindns::bind(increment, &count));
200   post(s, throw_exception);
201   post(s, bindns::bind(increment, &count));
202 
203   // No handlers can be called until run() is called.
204   BOOST_ASIO_CHECK(count == 0);
205   BOOST_ASIO_CHECK(exception_count == 0);
206 
207   for (;;)
208   {
209     try
210     {
211       ioc.run();
212       break;
213     }
214     catch (int)
215     {
216       ++exception_count;
217     }
218   }
219 
220   // The run() calls will not return until all work has finished.
221   BOOST_ASIO_CHECK(count == 3);
222   BOOST_ASIO_CHECK(exception_count == 2);
223 
224   count = 0;
225   ioc.restart();
226 
227   // Check for clean shutdown when handlers posted through an orphaned strand
228   // are abandoned.
229   {
230     io_context::strand s2(ioc);
231     post(s2, bindns::bind(increment, &count));
232     post(s2, bindns::bind(increment, &count));
233     post(s2, bindns::bind(increment, &count));
234   }
235 
236   // No handlers can be called until run() is called.
237   BOOST_ASIO_CHECK(count == 0);
238 }
239 
strand_wrap_test()240 void strand_wrap_test()
241 {
242 #if !defined(BOOST_ASIO_NO_DEPRECATED)
243   io_context ioc;
244   io_context::strand s(ioc);
245   int count = 0;
246 
247   s.wrap(bindns::bind(increment, &count))();
248 
249   // No handlers can be called until run() is called.
250   BOOST_ASIO_CHECK(count == 0);
251 
252   ioc.restart();
253   ioc.run();
254 
255   // The run() calls will not return until all work has finished.
256   BOOST_ASIO_CHECK(count == 1);
257 
258   count = 0;
259   s.wrap(increment)(&count);
260 
261   // No handlers can be called until run() is called.
262   BOOST_ASIO_CHECK(count == 0);
263 
264   ioc.restart();
265   ioc.run();
266 
267   // The run() calls will not return until all work has finished.
268   BOOST_ASIO_CHECK(count == 1);
269 
270   count = 0;
271   s.wrap(increment_by_a)(&count, 1);
272 
273   // No handlers can be called until run() is called.
274   BOOST_ASIO_CHECK(count == 0);
275 
276   ioc.restart();
277   ioc.run();
278 
279   // The run() calls will not return until all work has finished.
280   BOOST_ASIO_CHECK(count == 1);
281 
282   count = 0;
283   s.wrap(increment_by_a_b)(&count, 1, 2);
284 
285   // No handlers can be called until run() is called.
286   BOOST_ASIO_CHECK(count == 0);
287 
288   ioc.restart();
289   ioc.run();
290 
291   // The run() calls will not return until all work has finished.
292   BOOST_ASIO_CHECK(count == 3);
293 
294   count = 0;
295   s.wrap(increment_by_a_b_c)(&count, 1, 2, 3);
296 
297   // No handlers can be called until run() is called.
298   BOOST_ASIO_CHECK(count == 0);
299 
300   ioc.restart();
301   ioc.run();
302 
303   // The run() calls will not return until all work has finished.
304   BOOST_ASIO_CHECK(count == 6);
305 
306   count = 0;
307   s.wrap(increment_by_a_b_c_d)(&count, 1, 2, 3, 4);
308 
309   // No handlers can be called until run() is called.
310   BOOST_ASIO_CHECK(count == 0);
311 
312   ioc.restart();
313   ioc.run();
314 
315   // The run() calls will not return until all work has finished.
316   BOOST_ASIO_CHECK(count == 10);
317 #endif // !defined(BOOST_ASIO_NO_DEPRECATED)
318 }
319 
320 BOOST_ASIO_TEST_SUITE
321 (
322   "strand",
323   BOOST_ASIO_TEST_CASE(strand_test)
324   BOOST_ASIO_TEST_CASE(strand_wrap_test)
325 )
326