1 //
2 // thread_pool.cpp
3 // ~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2020 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/thread_pool.hpp>
18 
19 #include <boost/asio/dispatch.hpp>
20 #include <boost/asio/post.hpp>
21 #include "unit_test.hpp"
22 
23 #if defined(BOOST_ASIO_HAS_BOOST_BIND)
24 # include <boost/bind/bind.hpp>
25 #else // defined(BOOST_ASIO_HAS_BOOST_BIND)
26 # include <functional>
27 #endif // defined(BOOST_ASIO_HAS_BOOST_BIND)
28 
29 using namespace boost::asio;
30 
31 #if defined(BOOST_ASIO_HAS_BOOST_BIND)
32 namespace bindns = boost;
33 #else // defined(BOOST_ASIO_HAS_BOOST_BIND)
34 namespace bindns = std;
35 #endif
36 
increment(int * count)37 void increment(int* count)
38 {
39   ++(*count);
40 }
41 
decrement_to_zero(thread_pool * pool,int * count)42 void decrement_to_zero(thread_pool* pool, int* count)
43 {
44   if (*count > 0)
45   {
46     --(*count);
47 
48     int before_value = *count;
49     boost::asio::post(*pool, bindns::bind(decrement_to_zero, pool, count));
50 
51     // Handler execution cannot nest, so count value should remain unchanged.
52     BOOST_ASIO_CHECK(*count == before_value);
53   }
54 }
55 
nested_decrement_to_zero(thread_pool * pool,int * count)56 void nested_decrement_to_zero(thread_pool* pool, int* count)
57 {
58   if (*count > 0)
59   {
60     --(*count);
61 
62     boost::asio::dispatch(*pool,
63         bindns::bind(nested_decrement_to_zero, pool, count));
64 
65     // Handler execution is nested, so count value should now be zero.
66     BOOST_ASIO_CHECK(*count == 0);
67   }
68 }
69 
thread_pool_test()70 void thread_pool_test()
71 {
72   thread_pool pool(1);
73 
74   int count1 = 0;
75   boost::asio::post(pool, bindns::bind(increment, &count1));
76 
77   int count2 = 10;
78   boost::asio::post(pool, bindns::bind(decrement_to_zero, &pool, &count2));
79 
80   int count3 = 10;
81   boost::asio::post(pool, bindns::bind(nested_decrement_to_zero, &pool, &count3));
82 
83   pool.wait();
84 
85   BOOST_ASIO_CHECK(count1 == 1);
86   BOOST_ASIO_CHECK(count2 == 0);
87   BOOST_ASIO_CHECK(count3 == 0);
88 }
89 
90 class test_service : public boost::asio::execution_context::service
91 {
92 public:
93 #if defined(BOOST_ASIO_NO_TYPEID)
94   static boost::asio::execution_context::id id;
95 #endif // defined(BOOST_ASIO_NO_TYPEID)
96 
97   typedef test_service key_type;
98 
test_service(boost::asio::execution_context & ctx)99   test_service(boost::asio::execution_context& ctx)
100     : boost::asio::execution_context::service(ctx)
101   {
102   }
103 
104 private:
shutdown()105   virtual void shutdown() {}
106 };
107 
108 #if defined(BOOST_ASIO_NO_TYPEID)
109 boost::asio::execution_context::id test_service::id;
110 #endif // defined(BOOST_ASIO_NO_TYPEID)
111 
thread_pool_service_test()112 void thread_pool_service_test()
113 {
114   boost::asio::thread_pool pool1(1);
115   boost::asio::thread_pool pool2(1);
116   boost::asio::thread_pool pool3(1);
117 
118   // Implicit service registration.
119 
120   boost::asio::use_service<test_service>(pool1);
121 
122   BOOST_ASIO_CHECK(boost::asio::has_service<test_service>(pool1));
123 
124   test_service* svc1 = new test_service(pool1);
125   try
126   {
127     boost::asio::add_service(pool1, svc1);
128     BOOST_ASIO_ERROR("add_service did not throw");
129   }
130   catch (boost::asio::service_already_exists&)
131   {
132   }
133   delete svc1;
134 
135   // Explicit service registration.
136 
137   test_service& svc2 = boost::asio::make_service<test_service>(pool2);
138 
139   BOOST_ASIO_CHECK(boost::asio::has_service<test_service>(pool2));
140   BOOST_ASIO_CHECK(&boost::asio::use_service<test_service>(pool2) == &svc2);
141 
142   test_service* svc3 = new test_service(pool2);
143   try
144   {
145     boost::asio::add_service(pool2, svc3);
146     BOOST_ASIO_ERROR("add_service did not throw");
147   }
148   catch (boost::asio::service_already_exists&)
149   {
150   }
151   delete svc3;
152 
153   // Explicit registration with invalid owner.
154 
155   test_service* svc4 = new test_service(pool2);
156   try
157   {
158     boost::asio::add_service(pool3, svc4);
159     BOOST_ASIO_ERROR("add_service did not throw");
160   }
161   catch (boost::asio::invalid_service_owner&)
162   {
163   }
164   delete svc4;
165 
166   BOOST_ASIO_CHECK(!boost::asio::has_service<test_service>(pool3));
167 }
168 
thread_pool_executor_query_test()169 void thread_pool_executor_query_test()
170 {
171   thread_pool pool(1);
172 
173   BOOST_ASIO_CHECK(
174       &boost::asio::query(pool.executor(),
175         boost::asio::execution::context)
176       == &pool);
177 
178   BOOST_ASIO_CHECK(
179       boost::asio::query(pool.executor(),
180         boost::asio::execution::blocking)
181       == boost::asio::execution::blocking.possibly);
182 
183   BOOST_ASIO_CHECK(
184       boost::asio::query(pool.executor(),
185         boost::asio::execution::blocking.possibly)
186       == boost::asio::execution::blocking.possibly);
187 
188   BOOST_ASIO_CHECK(
189       boost::asio::query(pool.executor(),
190         boost::asio::execution::outstanding_work)
191       == boost::asio::execution::outstanding_work.untracked);
192 
193   BOOST_ASIO_CHECK(
194       boost::asio::query(pool.executor(),
195         boost::asio::execution::outstanding_work.untracked)
196       == boost::asio::execution::outstanding_work.untracked);
197 
198   BOOST_ASIO_CHECK(
199       boost::asio::query(pool.executor(),
200         boost::asio::execution::relationship)
201       == boost::asio::execution::relationship.fork);
202 
203   BOOST_ASIO_CHECK(
204       boost::asio::query(pool.executor(),
205         boost::asio::execution::relationship.fork)
206       == boost::asio::execution::relationship.fork);
207 
208   BOOST_ASIO_CHECK(
209       boost::asio::query(pool.executor(),
210         boost::asio::execution::bulk_guarantee)
211       == boost::asio::execution::bulk_guarantee.parallel);
212 
213   BOOST_ASIO_CHECK(
214       boost::asio::query(pool.executor(),
215         boost::asio::execution::mapping)
216       == boost::asio::execution::mapping.thread);
217 
218   BOOST_ASIO_CHECK(
219       boost::asio::query(pool.executor(),
220         boost::asio::execution::allocator)
221       == std::allocator<void>());
222 
223   BOOST_ASIO_CHECK(
224       boost::asio::query(pool.executor(),
225         boost::asio::execution::occupancy)
226       == 1);
227 }
228 
thread_pool_executor_execute_test()229 void thread_pool_executor_execute_test()
230 {
231   int count = 0;
232   thread_pool pool(1);
233 
234   boost::asio::execution::execute(pool.executor(),
235       bindns::bind(increment, &count));
236 
237   boost::asio::execution::execute(
238       boost::asio::require(pool.executor(),
239         boost::asio::execution::blocking.possibly),
240       bindns::bind(increment, &count));
241 
242   boost::asio::execution::execute(
243       boost::asio::require(pool.executor(),
244         boost::asio::execution::blocking.always),
245       bindns::bind(increment, &count));
246 
247   boost::asio::execution::execute(
248       boost::asio::require(pool.executor(),
249         boost::asio::execution::blocking.never),
250       bindns::bind(increment, &count));
251 
252   boost::asio::execution::execute(
253       boost::asio::require(pool.executor(),
254         boost::asio::execution::blocking.never,
255         boost::asio::execution::outstanding_work.tracked),
256       bindns::bind(increment, &count));
257 
258   boost::asio::execution::execute(
259       boost::asio::require(pool.executor(),
260         boost::asio::execution::blocking.never,
261         boost::asio::execution::outstanding_work.untracked),
262       bindns::bind(increment, &count));
263 
264   boost::asio::execution::execute(
265       boost::asio::require(pool.executor(),
266         boost::asio::execution::blocking.never,
267         boost::asio::execution::outstanding_work.untracked,
268         boost::asio::execution::relationship.fork),
269       bindns::bind(increment, &count));
270 
271   boost::asio::execution::execute(
272       boost::asio::require(pool.executor(),
273         boost::asio::execution::blocking.never,
274         boost::asio::execution::outstanding_work.untracked,
275         boost::asio::execution::relationship.continuation),
276       bindns::bind(increment, &count));
277 
278   boost::asio::execution::execute(
279       boost::asio::prefer(
280         boost::asio::require(pool.executor(),
281           boost::asio::execution::blocking.never,
282           boost::asio::execution::outstanding_work.untracked,
283           boost::asio::execution::relationship.continuation),
284         boost::asio::execution::allocator(std::allocator<void>())),
285       bindns::bind(increment, &count));
286 
287   boost::asio::execution::execute(
288       boost::asio::prefer(
289         boost::asio::require(pool.executor(),
290           boost::asio::execution::blocking.never,
291           boost::asio::execution::outstanding_work.untracked,
292           boost::asio::execution::relationship.continuation),
293         boost::asio::execution::allocator),
294       bindns::bind(increment, &count));
295 
296   pool.wait();
297 
298   BOOST_ASIO_CHECK(count == 10);
299 }
300 
301 struct receiver
302 {
303   int* count_;
304 
receiverreceiver305   receiver(int* count)
306     : count_(count)
307   {
308   }
309 
receiverreceiver310   receiver(const receiver& other) BOOST_ASIO_NOEXCEPT
311     : count_(other.count_)
312   {
313   }
314 
315 #if defined(BOOST_ASIO_HAS_MOVE)
receiverreceiver316   receiver(receiver&& other) BOOST_ASIO_NOEXCEPT
317     : count_(other.count_)
318   {
319     other.count_ = 0;
320   }
321 #endif // defined(BOOST_ASIO_HAS_MOVE)
322 
set_valuereceiver323   void set_value() BOOST_ASIO_NOEXCEPT
324   {
325     ++(*count_);
326   }
327 
328   template <typename E>
set_errorreceiver329   void set_error(BOOST_ASIO_MOVE_ARG(E) e) BOOST_ASIO_NOEXCEPT
330   {
331     (void)e;
332   }
333 
set_donereceiver334   void set_done() BOOST_ASIO_NOEXCEPT
335   {
336   }
337 };
338 
339 namespace boost {
340 namespace asio {
341 namespace traits {
342 
343 #if !defined(BOOST_ASIO_HAS_DEDUCED_SET_VALUE_MEMBER_TRAIT)
344 
345 template <>
346 struct set_value_member<receiver, void()>
347 {
348   BOOST_ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
349   BOOST_ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
350   typedef void result_type;
351 };
352 
353 #endif // !defined(BOOST_ASIO_HAS_DEDUCED_SET_VALUE_MEMBER_TRAIT)
354 
355 #if !defined(BOOST_ASIO_HAS_DEDUCED_SET_ERROR_MEMBER_TRAIT)
356 
357 template <typename E>
358 struct set_error_member<receiver, E>
359 {
360   BOOST_ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
361   BOOST_ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
362   typedef void result_type;
363 };
364 
365 #endif // !defined(BOOST_ASIO_HAS_DEDUCED_SET_ERROR_MEMBER_TRAIT)
366 
367 #if !defined(BOOST_ASIO_HAS_DEDUCED_SET_DONE_MEMBER_TRAIT)
368 
369 template <>
370 struct set_done_member<receiver>
371 {
372   BOOST_ASIO_STATIC_CONSTEXPR(bool, is_valid = true);
373   BOOST_ASIO_STATIC_CONSTEXPR(bool, is_noexcept = true);
374   typedef void result_type;
375 };
376 
377 #endif // !defined(BOOST_ASIO_HAS_DEDUCED_SET_DONE_MEMBER_TRAIT)
378 
379 } // namespace traits
380 } // namespace asio
381 } // namespace boost
382 
thread_pool_scheduler_test()383 void thread_pool_scheduler_test()
384 {
385   int count = 0;
386   receiver r(&count);
387   thread_pool pool(1);
388 
389   boost::asio::execution::submit(
390     boost::asio::execution::schedule(pool.scheduler()), r);
391 
392   boost::asio::execution::submit(
393       boost::asio::require(
394         boost::asio::execution::schedule(pool.executor()),
395         boost::asio::execution::blocking.possibly), r);
396 
397   boost::asio::execution::submit(
398       boost::asio::require(
399         boost::asio::execution::schedule(pool.executor()),
400         boost::asio::execution::blocking.always), r);
401 
402   boost::asio::execution::submit(
403       boost::asio::require(
404         boost::asio::execution::schedule(pool.executor()),
405         boost::asio::execution::blocking.never), r);
406 
407   boost::asio::execution::submit(
408       boost::asio::require(
409         boost::asio::execution::schedule(pool.executor()),
410         boost::asio::execution::blocking.never,
411         boost::asio::execution::outstanding_work.tracked), r);
412 
413   boost::asio::execution::submit(
414       boost::asio::require(
415         boost::asio::execution::schedule(pool.executor()),
416         boost::asio::execution::blocking.never,
417         boost::asio::execution::outstanding_work.untracked), r);
418 
419   boost::asio::execution::submit(
420       boost::asio::require(
421         boost::asio::execution::schedule(pool.executor()),
422         boost::asio::execution::blocking.never,
423         boost::asio::execution::outstanding_work.untracked,
424         boost::asio::execution::relationship.fork), r);
425 
426   boost::asio::execution::submit(
427       boost::asio::require(
428         boost::asio::execution::schedule(pool.executor()),
429         boost::asio::execution::blocking.never,
430         boost::asio::execution::outstanding_work.untracked,
431         boost::asio::execution::relationship.continuation), r);
432 
433   boost::asio::execution::submit(
434       boost::asio::prefer(
435         boost::asio::require(
436           boost::asio::execution::schedule(pool.executor()),
437           boost::asio::execution::blocking.never,
438           boost::asio::execution::outstanding_work.untracked,
439           boost::asio::execution::relationship.continuation),
440         boost::asio::execution::allocator(std::allocator<void>())), r);
441 
442   boost::asio::execution::submit(
443       boost::asio::prefer(
444         boost::asio::require(
445           boost::asio::execution::schedule(pool.executor()),
446           boost::asio::execution::blocking.never,
447           boost::asio::execution::outstanding_work.untracked,
448           boost::asio::execution::relationship.continuation),
449         boost::asio::execution::allocator), r);
450 
451   pool.wait();
452 
453   BOOST_ASIO_CHECK(count == 10);
454 }
455 
thread_pool_executor_bulk_execute_test()456 void thread_pool_executor_bulk_execute_test()
457 {
458   int count = 0;
459   thread_pool pool(1);
460 
461   pool.executor().bulk_execute(
462       bindns::bind(increment, &count), 2);
463 
464   boost::asio::require(pool.executor(),
465     boost::asio::execution::blocking.possibly).bulk_execute(
466       bindns::bind(increment, &count), 2);
467 
468   boost::asio::require(pool.executor(),
469     boost::asio::execution::blocking.always).bulk_execute(
470       bindns::bind(increment, &count), 2);
471 
472   boost::asio::require(pool.executor(),
473     boost::asio::execution::blocking.never).bulk_execute(
474       bindns::bind(increment, &count), 2);
475 
476   boost::asio::require(pool.executor(),
477     boost::asio::execution::blocking.never,
478     boost::asio::execution::outstanding_work.tracked).bulk_execute(
479       bindns::bind(increment, &count), 2);
480 
481   boost::asio::require(pool.executor(),
482     boost::asio::execution::blocking.never,
483     boost::asio::execution::outstanding_work.untracked).bulk_execute(
484       bindns::bind(increment, &count), 2);
485 
486   boost::asio::require(pool.executor(),
487     boost::asio::execution::blocking.never,
488     boost::asio::execution::outstanding_work.untracked,
489     boost::asio::execution::relationship.fork).bulk_execute(
490       bindns::bind(increment, &count), 2);
491 
492   boost::asio::require(pool.executor(),
493     boost::asio::execution::blocking.never,
494     boost::asio::execution::outstanding_work.untracked,
495     boost::asio::execution::relationship.continuation).bulk_execute(
496       bindns::bind(increment, &count), 2);
497 
498   boost::asio::prefer(
499     boost::asio::require(pool.executor(),
500       boost::asio::execution::blocking.never,
501       boost::asio::execution::outstanding_work.untracked,
502       boost::asio::execution::relationship.continuation),
503     boost::asio::execution::allocator(std::allocator<void>())).bulk_execute(
504       bindns::bind(increment, &count), 2);
505 
506   boost::asio::prefer(
507     boost::asio::require(pool.executor(),
508       boost::asio::execution::blocking.never,
509       boost::asio::execution::outstanding_work.untracked,
510       boost::asio::execution::relationship.continuation),
511     boost::asio::execution::allocator).bulk_execute(
512       bindns::bind(increment, &count), 2);
513 
514   pool.wait();
515 
516   BOOST_ASIO_CHECK(count == 20);
517 }
518 
519 BOOST_ASIO_TEST_SUITE
520 (
521   "thread_pool",
522   BOOST_ASIO_TEST_CASE(thread_pool_test)
523   BOOST_ASIO_TEST_CASE(thread_pool_service_test)
524   BOOST_ASIO_TEST_CASE(thread_pool_executor_query_test)
525   BOOST_ASIO_TEST_CASE(thread_pool_executor_execute_test)
526   BOOST_ASIO_TEST_CASE(thread_pool_executor_bulk_execute_test)
527   BOOST_ASIO_TEST_CASE(thread_pool_scheduler_test)
528 )
529