• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // 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/strand.hpp>
18 
19 #include <sstream>
20 #include <boost/asio/executor.hpp>
21 #include <boost/asio/io_context.hpp>
22 #include <boost/asio/dispatch.hpp>
23 #include <boost/asio/post.hpp>
24 #include <boost/asio/detail/thread.hpp>
25 #include "unit_test.hpp"
26 
27 #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
28 # include <boost/asio/deadline_timer.hpp>
29 #else // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
30 # include <boost/asio/steady_timer.hpp>
31 #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
32 
33 #if defined(BOOST_ASIO_HAS_BOOST_BIND)
34 # include <boost/bind/bind.hpp>
35 #else // defined(BOOST_ASIO_HAS_BOOST_BIND)
36 # include <functional>
37 #endif // defined(BOOST_ASIO_HAS_BOOST_BIND)
38 
39 using namespace boost::asio;
40 
41 #if defined(BOOST_ASIO_HAS_BOOST_BIND)
42 namespace bindns = boost;
43 #else // defined(BOOST_ASIO_HAS_BOOST_BIND)
44 namespace bindns = std;
45 #endif
46 
47 #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
48 typedef deadline_timer timer;
49 namespace chronons = boost::posix_time;
50 #elif defined(BOOST_ASIO_HAS_CHRONO)
51 typedef steady_timer timer;
52 namespace chronons = boost::asio::chrono;
53 #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
54 
increment(int * count)55 void increment(int* count)
56 {
57   ++(*count);
58 }
59 
increment_without_lock(strand<io_context::executor_type> * s,int * count)60 void increment_without_lock(strand<io_context::executor_type>* s, int* count)
61 {
62   BOOST_ASIO_CHECK(!s->running_in_this_thread());
63 
64   int original_count = *count;
65 
66   dispatch(*s, bindns::bind(increment, count));
67 
68   // No other functions are currently executing through the locking dispatcher,
69   // so the previous call to dispatch should have successfully nested.
70   BOOST_ASIO_CHECK(*count == original_count + 1);
71 }
72 
increment_with_lock(strand<io_context::executor_type> * s,int * count)73 void increment_with_lock(strand<io_context::executor_type>* s, int* count)
74 {
75   BOOST_ASIO_CHECK(s->running_in_this_thread());
76 
77   int original_count = *count;
78 
79   dispatch(*s, bindns::bind(increment, count));
80 
81   // The current function already holds the strand's lock, so the
82   // previous call to dispatch should have successfully nested.
83   BOOST_ASIO_CHECK(*count == original_count + 1);
84 }
85 
sleep_increment(io_context * ioc,int * count)86 void sleep_increment(io_context* ioc, int* count)
87 {
88   timer t(*ioc, chronons::seconds(2));
89   t.wait();
90 
91   ++(*count);
92 }
93 
increment_by_a(int * count,int a)94 void increment_by_a(int* count, int a)
95 {
96   (*count) += a;
97 }
98 
increment_by_a_b(int * count,int a,int b)99 void increment_by_a_b(int* count, int a, int b)
100 {
101   (*count) += a + b;
102 }
103 
increment_by_a_b_c(int * count,int a,int b,int c)104 void increment_by_a_b_c(int* count, int a, int b, int c)
105 {
106   (*count) += a + b + c;
107 }
108 
increment_by_a_b_c_d(int * count,int a,int b,int c,int d)109 void increment_by_a_b_c_d(int* count, int a, int b, int c, int d)
110 {
111   (*count) += a + b + c + d;
112 }
113 
start_sleep_increments(io_context * ioc,strand<io_context::executor_type> * s,int * count)114 void start_sleep_increments(io_context* ioc,
115     strand<io_context::executor_type>* s, int* count)
116 {
117   // Give all threads a chance to start.
118   timer t(*ioc, chronons::seconds(2));
119   t.wait();
120 
121   // Start three increments.
122   post(*s, bindns::bind(sleep_increment, ioc, count));
123   post(*s, bindns::bind(sleep_increment, ioc, count));
124   post(*s, bindns::bind(sleep_increment, ioc, count));
125 }
126 
throw_exception()127 void throw_exception()
128 {
129   throw 1;
130 }
131 
io_context_run(io_context * ioc)132 void io_context_run(io_context* ioc)
133 {
134   ioc->run();
135 }
136 
strand_test()137 void strand_test()
138 {
139   io_context ioc;
140   strand<io_context::executor_type> s = make_strand(ioc);
141   int count = 0;
142 
143   post(ioc, bindns::bind(increment_without_lock, &s, &count));
144 
145   // No handlers can be called until run() is called.
146   BOOST_ASIO_CHECK(count == 0);
147 
148   ioc.run();
149 
150   // The run() call will not return until all work has finished.
151   BOOST_ASIO_CHECK(count == 1);
152 
153   count = 0;
154   ioc.restart();
155   post(s, bindns::bind(increment_with_lock, &s, &count));
156 
157   // No handlers can be called until run() is called.
158   BOOST_ASIO_CHECK(count == 0);
159 
160   ioc.run();
161 
162   // The run() call will not return until all work has finished.
163   BOOST_ASIO_CHECK(count == 1);
164 
165   count = 0;
166   ioc.restart();
167   post(ioc, bindns::bind(start_sleep_increments, &ioc, &s, &count));
168   boost::asio::detail::thread thread1(bindns::bind(io_context_run, &ioc));
169   boost::asio::detail::thread thread2(bindns::bind(io_context_run, &ioc));
170 
171   // Check all events run one after another even though there are two threads.
172   timer timer1(ioc, chronons::seconds(3));
173   timer1.wait();
174   BOOST_ASIO_CHECK(count == 0);
175 #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
176   timer1.expires_at(timer1.expires_at() + chronons::seconds(2));
177 #else // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
178   timer1.expires_at(timer1.expiry() + chronons::seconds(2));
179 #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
180   timer1.wait();
181   BOOST_ASIO_CHECK(count == 1);
182 #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
183   timer1.expires_at(timer1.expires_at() + chronons::seconds(2));
184 #else // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
185   timer1.expires_at(timer1.expiry() + chronons::seconds(2));
186 #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME)
187   timer1.wait();
188   BOOST_ASIO_CHECK(count == 2);
189 
190   thread1.join();
191   thread2.join();
192 
193   // The run() calls will not return until all work has finished.
194   BOOST_ASIO_CHECK(count == 3);
195 
196   count = 0;
197   int exception_count = 0;
198   ioc.restart();
199   post(s, throw_exception);
200   post(s, bindns::bind(increment, &count));
201   post(s, bindns::bind(increment, &count));
202   post(s, throw_exception);
203   post(s, bindns::bind(increment, &count));
204 
205   // No handlers can be called until run() is called.
206   BOOST_ASIO_CHECK(count == 0);
207   BOOST_ASIO_CHECK(exception_count == 0);
208 
209   for (;;)
210   {
211     try
212     {
213       ioc.run();
214       break;
215     }
216     catch (int)
217     {
218       ++exception_count;
219     }
220   }
221 
222   // The run() calls will not return until all work has finished.
223   BOOST_ASIO_CHECK(count == 3);
224   BOOST_ASIO_CHECK(exception_count == 2);
225 
226   count = 0;
227   ioc.restart();
228 
229   // Check for clean shutdown when handlers posted through an orphaned strand
230   // are abandoned.
231   {
232     strand<io_context::executor_type> s2 = make_strand(ioc.get_executor());
233     post(s2, bindns::bind(increment, &count));
234     post(s2, bindns::bind(increment, &count));
235     post(s2, bindns::bind(increment, &count));
236   }
237 
238   // No handlers can be called until run() is called.
239   BOOST_ASIO_CHECK(count == 0);
240 }
241 
strand_conversion_test()242 void strand_conversion_test()
243 {
244   io_context ioc;
245   strand<io_context::executor_type> s1 = make_strand(ioc);
246 
247   // Converting constructors.
248 
249   strand<executor> s2(s1);
250   strand<executor> s3 = strand<io_context::executor_type>(s1);
251 
252   // Converting assignment.
253 
254   s3 = s1;
255   s3 = strand<io_context::executor_type>(s1);
256 }
257 
strand_query_test()258 void strand_query_test()
259 {
260   io_context ioc;
261   strand<io_context::executor_type> s1 = make_strand(ioc);
262 
263   BOOST_ASIO_CHECK(
264       &boost::asio::query(s1, boost::asio::execution::context)
265         == &ioc);
266 
267   BOOST_ASIO_CHECK(
268       boost::asio::query(s1, boost::asio::execution::blocking)
269         == boost::asio::execution::blocking.possibly);
270 
271   BOOST_ASIO_CHECK(
272       boost::asio::query(s1, boost::asio::execution::blocking.possibly)
273         == boost::asio::execution::blocking.possibly);
274 
275   BOOST_ASIO_CHECK(
276       boost::asio::query(s1, boost::asio::execution::outstanding_work)
277         == boost::asio::execution::outstanding_work.untracked);
278 
279   BOOST_ASIO_CHECK(
280       boost::asio::query(s1, boost::asio::execution::outstanding_work.untracked)
281         == boost::asio::execution::outstanding_work.untracked);
282 
283   BOOST_ASIO_CHECK(
284       boost::asio::query(s1, boost::asio::execution::relationship)
285         == boost::asio::execution::relationship.fork);
286 
287   BOOST_ASIO_CHECK(
288       boost::asio::query(s1, boost::asio::execution::relationship.fork)
289         == boost::asio::execution::relationship.fork);
290 
291   BOOST_ASIO_CHECK(
292       boost::asio::query(s1, boost::asio::execution::mapping)
293         == boost::asio::execution::mapping.thread);
294 
295   BOOST_ASIO_CHECK(
296       boost::asio::query(s1, boost::asio::execution::allocator)
297         == std::allocator<void>());
298 }
299 
strand_execute_test()300 void strand_execute_test()
301 {
302   io_context ioc;
303   strand<io_context::executor_type> s1 = make_strand(ioc);
304   int count = 0;
305 
306   boost::asio::execution::execute(s1, bindns::bind(increment, &count));
307 
308   // No handlers can be called until run() is called.
309   BOOST_ASIO_CHECK(!ioc.stopped());
310   BOOST_ASIO_CHECK(count == 0);
311 
312   ioc.run();
313 
314   // The run() call will not return until all work has finished.
315   BOOST_ASIO_CHECK(ioc.stopped());
316   BOOST_ASIO_CHECK(count == 1);
317 
318   count = 0;
319   ioc.restart();
320   boost::asio::execution::execute(
321       boost::asio::require(s1, boost::asio::execution::blocking.possibly),
322       bindns::bind(increment, &count));
323 
324   // No handlers can be called until run() is called.
325   BOOST_ASIO_CHECK(!ioc.stopped());
326   BOOST_ASIO_CHECK(count == 0);
327 
328   ioc.run();
329 
330   // The run() call will not return until all work has finished.
331   BOOST_ASIO_CHECK(ioc.stopped());
332   BOOST_ASIO_CHECK(count == 1);
333 
334   count = 0;
335   ioc.restart();
336   boost::asio::execution::execute(
337       boost::asio::require(s1, boost::asio::execution::blocking.never),
338       bindns::bind(increment, &count));
339 
340   // No handlers can be called until run() is called.
341   BOOST_ASIO_CHECK(!ioc.stopped());
342   BOOST_ASIO_CHECK(count == 0);
343 
344   ioc.run();
345 
346   // The run() call will not return until all work has finished.
347   BOOST_ASIO_CHECK(ioc.stopped());
348   BOOST_ASIO_CHECK(count == 1);
349 
350   count = 0;
351   ioc.restart();
352   BOOST_ASIO_CHECK(!ioc.stopped());
353 
354   boost::asio::execution::execute(
355       boost::asio::require(s1,
356         boost::asio::execution::blocking.never,
357         boost::asio::execution::outstanding_work.tracked),
358       bindns::bind(increment, &count));
359 
360   // No handlers can be called until run() is called.
361   BOOST_ASIO_CHECK(!ioc.stopped());
362   BOOST_ASIO_CHECK(count == 0);
363 
364   ioc.run();
365 
366   // The run() call will not return until all work has finished.
367   BOOST_ASIO_CHECK(ioc.stopped());
368   BOOST_ASIO_CHECK(count == 1);
369 
370   count = 0;
371   ioc.restart();
372   boost::asio::execution::execute(
373       boost::asio::require(s1,
374         boost::asio::execution::blocking.never,
375         boost::asio::execution::outstanding_work.untracked),
376       bindns::bind(increment, &count));
377 
378   // No handlers can be called until run() is called.
379   BOOST_ASIO_CHECK(!ioc.stopped());
380   BOOST_ASIO_CHECK(count == 0);
381 
382   ioc.run();
383 
384   // The run() call will not return until all work has finished.
385   BOOST_ASIO_CHECK(ioc.stopped());
386   BOOST_ASIO_CHECK(count == 1);
387 
388   count = 0;
389   ioc.restart();
390   boost::asio::execution::execute(
391       boost::asio::require(s1,
392         boost::asio::execution::blocking.never,
393         boost::asio::execution::outstanding_work.untracked,
394         boost::asio::execution::relationship.fork),
395       bindns::bind(increment, &count));
396 
397   // No handlers can be called until run() is called.
398   BOOST_ASIO_CHECK(!ioc.stopped());
399   BOOST_ASIO_CHECK(count == 0);
400 
401   ioc.run();
402 
403   // The run() call will not return until all work has finished.
404   BOOST_ASIO_CHECK(ioc.stopped());
405   BOOST_ASIO_CHECK(count == 1);
406 
407   count = 0;
408   ioc.restart();
409   boost::asio::execution::execute(
410       boost::asio::require(s1,
411         boost::asio::execution::blocking.never,
412         boost::asio::execution::outstanding_work.untracked,
413         boost::asio::execution::relationship.continuation),
414       bindns::bind(increment, &count));
415 
416   // No handlers can be called until run() is called.
417   BOOST_ASIO_CHECK(!ioc.stopped());
418   BOOST_ASIO_CHECK(count == 0);
419 
420   ioc.run();
421 
422   // The run() call will not return until all work has finished.
423   BOOST_ASIO_CHECK(ioc.stopped());
424   BOOST_ASIO_CHECK(count == 1);
425 
426   count = 0;
427   ioc.restart();
428   boost::asio::execution::execute(
429       boost::asio::prefer(
430         boost::asio::require(s1,
431           boost::asio::execution::blocking.never,
432           boost::asio::execution::outstanding_work.untracked,
433           boost::asio::execution::relationship.continuation),
434         boost::asio::execution::allocator(std::allocator<void>())),
435       bindns::bind(increment, &count));
436 
437   // No handlers can be called until run() is called.
438   BOOST_ASIO_CHECK(!ioc.stopped());
439   BOOST_ASIO_CHECK(count == 0);
440 
441   ioc.run();
442 
443   // The run() call will not return until all work has finished.
444   BOOST_ASIO_CHECK(ioc.stopped());
445   BOOST_ASIO_CHECK(count == 1);
446 
447   count = 0;
448   ioc.restart();
449   boost::asio::execution::execute(
450       boost::asio::prefer(
451         boost::asio::require(s1,
452           boost::asio::execution::blocking.never,
453           boost::asio::execution::outstanding_work.untracked,
454           boost::asio::execution::relationship.continuation),
455         boost::asio::execution::allocator),
456       bindns::bind(increment, &count));
457 
458   // No handlers can be called until run() is called.
459   BOOST_ASIO_CHECK(!ioc.stopped());
460   BOOST_ASIO_CHECK(count == 0);
461 
462   ioc.run();
463 
464   // The run() call will not return until all work has finished.
465   BOOST_ASIO_CHECK(ioc.stopped());
466   BOOST_ASIO_CHECK(count == 1);
467 }
468 
469 BOOST_ASIO_TEST_SUITE
470 (
471   "strand",
472   BOOST_ASIO_TEST_CASE(strand_test)
473   BOOST_ASIO_COMPILE_TEST_CASE(strand_conversion_test)
474   BOOST_ASIO_TEST_CASE(strand_query_test)
475   BOOST_ASIO_TEST_CASE(strand_execute_test)
476 )
477