1 | // |
2 | // io_service.cpp |
3 | // ~~~~~~~~~~~~~~ |
4 | // |
5 | // Copyright (c) 2003-2015 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_service.hpp> |
18 | |
19 | #include <sstream> |
20 | #include <boost/asio/detail/thread.hpp> |
21 | #include "unit_test.hpp" |
22 | |
23 | #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME) |
24 | # include <boost/asio/deadline_timer.hpp> |
25 | #else // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME) |
26 | # include <boost/asio/steady_timer.hpp> |
27 | #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME) |
28 | |
29 | #if defined(BOOST_ASIO_HAS_BOOST_BIND) |
30 | # include <boost/bind.hpp> |
31 | #else // defined(BOOST_ASIO_HAS_BOOST_BIND) |
32 | # include <functional> |
33 | #endif // defined(BOOST_ASIO_HAS_BOOST_BIND) |
34 | |
35 | using namespace boost::asio; |
36 | |
37 | #if defined(BOOST_ASIO_HAS_BOOST_BIND) |
38 | namespace bindns = boost; |
39 | #else // defined(BOOST_ASIO_HAS_BOOST_BIND) |
40 | namespace bindns = std; |
41 | #endif |
42 | |
43 | #if defined(BOOST_ASIO_HAS_BOOST_DATE_TIME) |
44 | typedef deadline_timer timer; |
45 | namespace chronons = boost::posix_time; |
46 | #elif defined(BOOST_ASIO_HAS_STD_CHRONO) |
47 | typedef steady_timer timer; |
48 | namespace chronons = std::chrono; |
49 | #elif defined(BOOST_ASIO_HAS_BOOST_CHRONO) |
50 | typedef steady_timer timer; |
51 | namespace chronons = boost::chrono; |
52 | #endif // defined(BOOST_ASIO_HAS_BOOST_DATE_TIME) |
53 | |
54 | void increment(int* count) |
55 | { |
56 | ++(*count); |
57 | } |
58 | |
59 | void decrement_to_zero(io_service* ios, int* count) |
60 | { |
61 | if (*count > 0) |
62 | { |
63 | --(*count); |
64 | |
65 | int before_value = *count; |
66 | ios->post(handler: bindns::bind(f: decrement_to_zero, a1: ios, a2: count)); |
67 | |
68 | // Handler execution cannot nest, so count value should remain unchanged. |
69 | BOOST_ASIO_CHECK(*count == before_value); |
70 | } |
71 | } |
72 | |
73 | void nested_decrement_to_zero(io_service* ios, int* count) |
74 | { |
75 | if (*count > 0) |
76 | { |
77 | --(*count); |
78 | |
79 | ios->dispatch(handler: bindns::bind(f: nested_decrement_to_zero, a1: ios, a2: count)); |
80 | |
81 | // Handler execution is nested, so count value should now be zero. |
82 | BOOST_ASIO_CHECK(*count == 0); |
83 | } |
84 | } |
85 | |
86 | void sleep_increment(io_service* ios, int* count) |
87 | { |
88 | timer t(*ios, chronons::seconds(2)); |
89 | t.wait(); |
90 | |
91 | if (++(*count) < 3) |
92 | ios->post(handler: bindns::bind(f: sleep_increment, a1: ios, a2: count)); |
93 | } |
94 | |
95 | void start_sleep_increments(io_service* ios, int* count) |
96 | { |
97 | // Give all threads a chance to start. |
98 | timer t(*ios, chronons::seconds(2)); |
99 | t.wait(); |
100 | |
101 | // Start the first of three increments. |
102 | ios->post(handler: bindns::bind(f: sleep_increment, a1: ios, a2: count)); |
103 | } |
104 | |
105 | void throw_exception() |
106 | { |
107 | throw 1; |
108 | } |
109 | |
110 | void io_service_run(io_service* ios) |
111 | { |
112 | ios->run(); |
113 | } |
114 | |
115 | void io_service_test() |
116 | { |
117 | io_service ios; |
118 | int count = 0; |
119 | |
120 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
121 | |
122 | // No handlers can be called until run() is called. |
123 | BOOST_ASIO_CHECK(!ios.stopped()); |
124 | BOOST_ASIO_CHECK(count == 0); |
125 | |
126 | ios.run(); |
127 | |
128 | // The run() call will not return until all work has finished. |
129 | BOOST_ASIO_CHECK(ios.stopped()); |
130 | BOOST_ASIO_CHECK(count == 1); |
131 | |
132 | count = 0; |
133 | ios.reset(); |
134 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
135 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
136 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
137 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
138 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
139 | |
140 | // No handlers can be called until run() is called. |
141 | BOOST_ASIO_CHECK(!ios.stopped()); |
142 | BOOST_ASIO_CHECK(count == 0); |
143 | |
144 | ios.run(); |
145 | |
146 | // The run() call will not return until all work has finished. |
147 | BOOST_ASIO_CHECK(ios.stopped()); |
148 | BOOST_ASIO_CHECK(count == 5); |
149 | |
150 | count = 0; |
151 | ios.reset(); |
152 | io_service::work* w = new io_service::work(ios); |
153 | ios.post(handler: bindns::bind(f: &io_service::stop, a1: &ios)); |
154 | BOOST_ASIO_CHECK(!ios.stopped()); |
155 | ios.run(); |
156 | |
157 | // The only operation executed should have been to stop run(). |
158 | BOOST_ASIO_CHECK(ios.stopped()); |
159 | BOOST_ASIO_CHECK(count == 0); |
160 | |
161 | ios.reset(); |
162 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
163 | delete w; |
164 | |
165 | // No handlers can be called until run() is called. |
166 | BOOST_ASIO_CHECK(!ios.stopped()); |
167 | BOOST_ASIO_CHECK(count == 0); |
168 | |
169 | ios.run(); |
170 | |
171 | // The run() call will not return until all work has finished. |
172 | BOOST_ASIO_CHECK(ios.stopped()); |
173 | BOOST_ASIO_CHECK(count == 1); |
174 | |
175 | count = 10; |
176 | ios.reset(); |
177 | ios.post(handler: bindns::bind(f: decrement_to_zero, a1: &ios, a2: &count)); |
178 | |
179 | // No handlers can be called until run() is called. |
180 | BOOST_ASIO_CHECK(!ios.stopped()); |
181 | BOOST_ASIO_CHECK(count == 10); |
182 | |
183 | ios.run(); |
184 | |
185 | // The run() call will not return until all work has finished. |
186 | BOOST_ASIO_CHECK(ios.stopped()); |
187 | BOOST_ASIO_CHECK(count == 0); |
188 | |
189 | count = 10; |
190 | ios.reset(); |
191 | ios.post(handler: bindns::bind(f: nested_decrement_to_zero, a1: &ios, a2: &count)); |
192 | |
193 | // No handlers can be called until run() is called. |
194 | BOOST_ASIO_CHECK(!ios.stopped()); |
195 | BOOST_ASIO_CHECK(count == 10); |
196 | |
197 | ios.run(); |
198 | |
199 | // The run() call will not return until all work has finished. |
200 | BOOST_ASIO_CHECK(ios.stopped()); |
201 | BOOST_ASIO_CHECK(count == 0); |
202 | |
203 | count = 10; |
204 | ios.reset(); |
205 | ios.dispatch(handler: bindns::bind(f: nested_decrement_to_zero, a1: &ios, a2: &count)); |
206 | |
207 | // No handlers can be called until run() is called, even though nested |
208 | // delivery was specifically allowed in the previous call. |
209 | BOOST_ASIO_CHECK(!ios.stopped()); |
210 | BOOST_ASIO_CHECK(count == 10); |
211 | |
212 | ios.run(); |
213 | |
214 | // The run() call will not return until all work has finished. |
215 | BOOST_ASIO_CHECK(ios.stopped()); |
216 | BOOST_ASIO_CHECK(count == 0); |
217 | |
218 | count = 0; |
219 | int count2 = 0; |
220 | ios.reset(); |
221 | BOOST_ASIO_CHECK(!ios.stopped()); |
222 | ios.post(handler: bindns::bind(f: start_sleep_increments, a1: &ios, a2: &count)); |
223 | ios.post(handler: bindns::bind(f: start_sleep_increments, a1: &ios, a2: &count2)); |
224 | boost::asio::detail::thread thread1(bindns::bind(f: io_service_run, a1: &ios)); |
225 | boost::asio::detail::thread thread2(bindns::bind(f: io_service_run, a1: &ios)); |
226 | thread1.join(); |
227 | thread2.join(); |
228 | |
229 | // The run() calls will not return until all work has finished. |
230 | BOOST_ASIO_CHECK(ios.stopped()); |
231 | BOOST_ASIO_CHECK(count == 3); |
232 | BOOST_ASIO_CHECK(count2 == 3); |
233 | |
234 | count = 10; |
235 | io_service ios2; |
236 | ios.dispatch(handler: ios2.wrap(handler: bindns::bind(f: decrement_to_zero, a1: &ios2, a2: &count))); |
237 | ios.reset(); |
238 | BOOST_ASIO_CHECK(!ios.stopped()); |
239 | ios.run(); |
240 | |
241 | // No decrement_to_zero handlers can be called until run() is called on the |
242 | // second io_service object. |
243 | BOOST_ASIO_CHECK(ios.stopped()); |
244 | BOOST_ASIO_CHECK(count == 10); |
245 | |
246 | ios2.run(); |
247 | |
248 | // The run() call will not return until all work has finished. |
249 | BOOST_ASIO_CHECK(count == 0); |
250 | |
251 | count = 0; |
252 | int exception_count = 0; |
253 | ios.reset(); |
254 | ios.post(handler: &throw_exception); |
255 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
256 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
257 | ios.post(handler: &throw_exception); |
258 | ios.post(handler: bindns::bind(f: increment, a1: &count)); |
259 | |
260 | // No handlers can be called until run() is called. |
261 | BOOST_ASIO_CHECK(!ios.stopped()); |
262 | BOOST_ASIO_CHECK(count == 0); |
263 | BOOST_ASIO_CHECK(exception_count == 0); |
264 | |
265 | for (;;) |
266 | { |
267 | try |
268 | { |
269 | ios.run(); |
270 | break; |
271 | } |
272 | catch (int) |
273 | { |
274 | ++exception_count; |
275 | } |
276 | } |
277 | |
278 | // The run() calls will not return until all work has finished. |
279 | BOOST_ASIO_CHECK(ios.stopped()); |
280 | BOOST_ASIO_CHECK(count == 3); |
281 | BOOST_ASIO_CHECK(exception_count == 2); |
282 | } |
283 | |
284 | class test_service : public boost::asio::io_service::service |
285 | { |
286 | public: |
287 | static boost::asio::io_service::id id; |
288 | test_service(boost::asio::io_service& s) |
289 | : boost::asio::io_service::service(s) {} |
290 | private: |
291 | virtual void shutdown_service() {} |
292 | }; |
293 | |
294 | boost::asio::io_service::id test_service::id; |
295 | |
296 | void io_service_service_test() |
297 | { |
298 | boost::asio::io_service ios1; |
299 | boost::asio::io_service ios2; |
300 | boost::asio::io_service ios3; |
301 | |
302 | // Implicit service registration. |
303 | |
304 | boost::asio::use_service<test_service>(ios&: ios1); |
305 | |
306 | BOOST_ASIO_CHECK(boost::asio::has_service<test_service>(ios1)); |
307 | |
308 | test_service* svc1 = new test_service(ios1); |
309 | try |
310 | { |
311 | boost::asio::add_service(ios&: ios1, svc: svc1); |
312 | BOOST_ASIO_ERROR("add_service did not throw" ); |
313 | } |
314 | catch (boost::asio::service_already_exists&) |
315 | { |
316 | } |
317 | delete svc1; |
318 | |
319 | // Explicit service registration. |
320 | |
321 | test_service* svc2 = new test_service(ios2); |
322 | boost::asio::add_service(ios&: ios2, svc: svc2); |
323 | |
324 | BOOST_ASIO_CHECK(boost::asio::has_service<test_service>(ios2)); |
325 | BOOST_ASIO_CHECK(&boost::asio::use_service<test_service>(ios2) == svc2); |
326 | |
327 | test_service* svc3 = new test_service(ios2); |
328 | try |
329 | { |
330 | boost::asio::add_service(ios&: ios2, svc: svc3); |
331 | BOOST_ASIO_ERROR("add_service did not throw" ); |
332 | } |
333 | catch (boost::asio::service_already_exists&) |
334 | { |
335 | } |
336 | delete svc3; |
337 | |
338 | // Explicit registration with invalid owner. |
339 | |
340 | test_service* svc4 = new test_service(ios2); |
341 | try |
342 | { |
343 | boost::asio::add_service(ios&: ios3, svc: svc4); |
344 | BOOST_ASIO_ERROR("add_service did not throw" ); |
345 | } |
346 | catch (boost::asio::invalid_service_owner&) |
347 | { |
348 | } |
349 | delete svc4; |
350 | |
351 | BOOST_ASIO_CHECK(!boost::asio::has_service<test_service>(ios3)); |
352 | } |
353 | |
354 | BOOST_ASIO_TEST_SUITE |
355 | ( |
356 | "io_service" , |
357 | BOOST_ASIO_TEST_CASE(io_service_test) |
358 | BOOST_ASIO_TEST_CASE(io_service_service_test) |
359 | ) |
360 | |