]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // strand.cpp | |
3 | // ~~~~~~~~~~ | |
4 | // | |
1e59de90 | 5 | // Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
7c673cae FG |
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> | |
f67539c2 | 20 | #include <boost/asio/executor.hpp> |
b32b8144 FG |
21 | #include <boost/asio/io_context.hpp> |
22 | #include <boost/asio/dispatch.hpp> | |
23 | #include <boost/asio/post.hpp> | |
7c673cae FG |
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) | |
f67539c2 | 34 | # include <boost/bind/bind.hpp> |
7c673cae FG |
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; | |
b32b8144 | 50 | #elif defined(BOOST_ASIO_HAS_CHRONO) |
7c673cae | 51 | typedef steady_timer timer; |
b32b8144 | 52 | namespace chronons = boost::asio::chrono; |
7c673cae FG |
53 | #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME) |
54 | ||
55 | void increment(int* count) | |
56 | { | |
57 | ++(*count); | |
58 | } | |
59 | ||
92f5a8d4 | 60 | void increment_without_lock(strand<io_context::executor_type>* s, int* count) |
7c673cae FG |
61 | { |
62 | BOOST_ASIO_CHECK(!s->running_in_this_thread()); | |
63 | ||
64 | int original_count = *count; | |
65 | ||
b32b8144 | 66 | dispatch(*s, bindns::bind(increment, count)); |
7c673cae FG |
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 | ||
92f5a8d4 | 73 | void increment_with_lock(strand<io_context::executor_type>* s, int* count) |
7c673cae FG |
74 | { |
75 | BOOST_ASIO_CHECK(s->running_in_this_thread()); | |
76 | ||
77 | int original_count = *count; | |
78 | ||
b32b8144 | 79 | dispatch(*s, bindns::bind(increment, count)); |
7c673cae FG |
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 | ||
b32b8144 | 86 | void sleep_increment(io_context* ioc, int* count) |
7c673cae | 87 | { |
b32b8144 | 88 | timer t(*ioc, chronons::seconds(2)); |
7c673cae FG |
89 | t.wait(); |
90 | ||
91 | ++(*count); | |
92 | } | |
93 | ||
b32b8144 FG |
94 | void increment_by_a(int* count, int a) |
95 | { | |
96 | (*count) += a; | |
97 | } | |
98 | ||
99 | void increment_by_a_b(int* count, int a, int b) | |
100 | { | |
101 | (*count) += a + b; | |
102 | } | |
103 | ||
104 | void increment_by_a_b_c(int* count, int a, int b, int c) | |
105 | { | |
106 | (*count) += a + b + c; | |
107 | } | |
108 | ||
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 | ||
92f5a8d4 TL |
114 | void start_sleep_increments(io_context* ioc, |
115 | strand<io_context::executor_type>* s, int* count) | |
7c673cae FG |
116 | { |
117 | // Give all threads a chance to start. | |
b32b8144 | 118 | timer t(*ioc, chronons::seconds(2)); |
7c673cae FG |
119 | t.wait(); |
120 | ||
121 | // Start three increments. | |
b32b8144 FG |
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)); | |
7c673cae FG |
125 | } |
126 | ||
127 | void throw_exception() | |
128 | { | |
129 | throw 1; | |
130 | } | |
131 | ||
b32b8144 | 132 | void io_context_run(io_context* ioc) |
7c673cae | 133 | { |
b32b8144 | 134 | ioc->run(); |
7c673cae FG |
135 | } |
136 | ||
137 | void strand_test() | |
138 | { | |
b32b8144 | 139 | io_context ioc; |
92f5a8d4 | 140 | strand<io_context::executor_type> s = make_strand(ioc); |
7c673cae FG |
141 | int count = 0; |
142 | ||
b32b8144 | 143 | post(ioc, bindns::bind(increment_without_lock, &s, &count)); |
7c673cae FG |
144 | |
145 | // No handlers can be called until run() is called. | |
146 | BOOST_ASIO_CHECK(count == 0); | |
147 | ||
b32b8144 | 148 | ioc.run(); |
7c673cae FG |
149 | |
150 | // The run() call will not return until all work has finished. | |
151 | BOOST_ASIO_CHECK(count == 1); | |
152 | ||
153 | count = 0; | |
b32b8144 FG |
154 | ioc.restart(); |
155 | post(s, bindns::bind(increment_with_lock, &s, &count)); | |
7c673cae FG |
156 | |
157 | // No handlers can be called until run() is called. | |
158 | BOOST_ASIO_CHECK(count == 0); | |
159 | ||
b32b8144 | 160 | ioc.run(); |
7c673cae FG |
161 | |
162 | // The run() call will not return until all work has finished. | |
163 | BOOST_ASIO_CHECK(count == 1); | |
164 | ||
165 | count = 0; | |
b32b8144 FG |
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)); | |
7c673cae FG |
170 | |
171 | // Check all events run one after another even though there are two threads. | |
b32b8144 | 172 | timer timer1(ioc, chronons::seconds(3)); |
7c673cae FG |
173 | timer1.wait(); |
174 | BOOST_ASIO_CHECK(count == 0); | |
b32b8144 | 175 | #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME) |
7c673cae | 176 | timer1.expires_at(timer1.expires_at() + chronons::seconds(2)); |
b32b8144 FG |
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) | |
7c673cae FG |
180 | timer1.wait(); |
181 | BOOST_ASIO_CHECK(count == 1); | |
b32b8144 | 182 | #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME) |
7c673cae | 183 | timer1.expires_at(timer1.expires_at() + chronons::seconds(2)); |
b32b8144 FG |
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) | |
7c673cae FG |
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; | |
b32b8144 FG |
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)); | |
7c673cae FG |
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 | { | |
b32b8144 | 213 | ioc.run(); |
7c673cae FG |
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; | |
b32b8144 | 227 | ioc.restart(); |
7c673cae FG |
228 | |
229 | // Check for clean shutdown when handlers posted through an orphaned strand | |
230 | // are abandoned. | |
231 | { | |
92f5a8d4 | 232 | strand<io_context::executor_type> s2 = make_strand(ioc.get_executor()); |
b32b8144 FG |
233 | post(s2, bindns::bind(increment, &count)); |
234 | post(s2, bindns::bind(increment, &count)); | |
235 | post(s2, bindns::bind(increment, &count)); | |
7c673cae FG |
236 | } |
237 | ||
238 | // No handlers can be called until run() is called. | |
239 | BOOST_ASIO_CHECK(count == 0); | |
240 | } | |
241 | ||
f67539c2 TL |
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 | ||
20effc67 TL |
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 | ||
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 | ||
7c673cae FG |
469 | BOOST_ASIO_TEST_SUITE |
470 | ( | |
471 | "strand", | |
472 | BOOST_ASIO_TEST_CASE(strand_test) | |
f67539c2 | 473 | BOOST_ASIO_COMPILE_TEST_CASE(strand_conversion_test) |
20effc67 TL |
474 | BOOST_ASIO_TEST_CASE(strand_query_test) |
475 | BOOST_ASIO_TEST_CASE(strand_execute_test) | |
7c673cae | 476 | ) |