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