1//
2// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
3//
4// Distributed under the Boost Software License, Version 1.0. (See accompanying
5// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6//
7// Official repository: https://github.com/boostorg/beast
8//
9
10// Test that header file is self-contained.
11#include <boost/beast/core/async_base.hpp>
12
13#include "test_handler.hpp"
14
15#include <boost/beast/_experimental/unit_test/suite.hpp>
16#include <boost/beast/_experimental/test/handler.hpp>
17#include <boost/beast/_experimental/test/stream.hpp>
18#include <boost/beast/core/error.hpp>
19#include <boost/asio/async_result.hpp>
20#include <boost/asio/coroutine.hpp>
21#include <boost/asio/io_context.hpp>
22#include <boost/asio/steady_timer.hpp>
23#include <boost/asio/system_executor.hpp>
24#include <boost/asio/write.hpp>
25#include <boost/core/ignore_unused.hpp>
26#include <stdexcept>
27
28
29//------------------------------------------------------------------------------
30
31namespace boost {
32namespace beast {
33
34namespace {
35
36#if defined(BOOST_ASIO_NO_TS_EXECUTORS)
37
38static struct ex1_context : net::execution_context
39{
40
41} ex1ctx;
42
43struct ex1_type
44{
45
46 net::execution_context &
47 query(net::execution::context_t c) const noexcept
48 { return *reinterpret_cast<net::execution_context *>(&ex1ctx); }
49
50 net::execution::blocking_t
51 query(net::execution::blocking_t) const noexcept
52 { return net::execution::blocking; };
53
54 net::execution::outstanding_work_t
55 query(net::execution::outstanding_work_t w) const noexcept
56 { return net::execution::outstanding_work; }
57
58 ex1_type
59 require(net::execution::blocking_t::possibly_t b) const
60 { return *this; }
61
62 ex1_type
63 require(net::execution::blocking_t::never_t b) const
64 { return *this; };
65
66 ex1_type
67 prefer(net::execution::outstanding_work_t::untracked_t w) const
68 { return *this; };
69
70 ex1_type
71 prefer(net::execution::outstanding_work_t::tracked_t w) const
72 { return *this; };
73
74 template<class F>
75 void
76 execute(F &&) const
77 {}
78
79 bool
80 operator==(ex1_type const &) const noexcept
81 { return true; }
82 bool
83 operator!=(ex1_type const &) const noexcept
84 { return false; }
85};
86BOOST_STATIC_ASSERT(net::execution::is_executor<ex1_type>::value);
87#else
88struct ex1_type
89{
90 void* context() { return nullptr; }
91 void on_work_started() {}
92 void on_work_finished() {}
93 template<class F> void dispatch(F&&) {}
94 template<class F> void post(F&&) {}
95 template<class F> void defer(F&&) {}
96};
97BOOST_STATIC_ASSERT(net::is_executor<ex1_type>::value);
98#endif
99
100
101struct no_alloc
102{
103};
104
105struct nested_alloc
106{
107 struct allocator_type
108 {
109 };
110
111 allocator_type get_allocator() const noexcept
112 {
113 return allocator_type{};
114 }
115};
116
117struct intrusive_alloc
118{
119 struct allocator_type
120 {
121 };
122
123 allocator_type get_allocator() const noexcept
124 {
125 return allocator_type{};
126 }
127};
128
129struct no_ex
130{
131 using executor_type = net::system_executor;
132
133 executor_type get_executor() const noexcept
134 {
135 return executor_type{};
136 }
137};
138
139struct nested_ex
140{
141 struct executor_type
142 {
143 };
144
145 executor_type get_executor() const noexcept
146 {
147 return executor_type{};
148 }
149};
150
151struct intrusive_ex
152{
153 struct executor_type
154 {
155 };
156
157 executor_type get_executor() const noexcept
158 {
159 return executor_type{};
160 }
161};
162
163template<class E, class A>
164struct handler;
165
166template<>
167struct handler<no_ex, no_alloc>
168{
169};
170
171template<>
172struct handler<no_ex, nested_alloc>
173 : nested_alloc
174{
175};
176
177template<>
178struct handler<no_ex, intrusive_alloc>
179{
180};
181
182template<>
183struct handler<nested_ex, no_alloc>
184 : nested_ex
185{
186};
187
188template<>
189struct handler<intrusive_ex, no_alloc>
190{
191};
192
193} // (anon)
194
195} // beast
196} // boost
197
198namespace boost {
199namespace asio {
200
201template<class Allocator>
202struct associated_allocator<
203 boost::beast::handler<
204 boost::beast::no_ex,
205 boost::beast::intrusive_alloc>,
206 Allocator>
207{
208 using type =
209 boost::beast::intrusive_alloc::allocator_type;
210
211 static type get(
212 boost::beast::handler<
213 boost::beast::no_ex,
214 boost::beast::intrusive_alloc> const&,
215 Allocator const& = Allocator()) noexcept
216 {
217 return type{};
218 }
219};
220
221template<class Executor>
222struct associated_executor<
223 boost::beast::handler<
224 boost::beast::intrusive_ex,
225 boost::beast::no_alloc>,
226 Executor>
227{
228 using type =
229 boost::beast::intrusive_ex::executor_type;
230
231 static type get(
232 boost::beast::handler<
233 boost::beast::intrusive_ex,
234 boost::beast::no_alloc> const&,
235 Executor const& = Executor()) noexcept
236 {
237 return type{};
238 }
239};
240
241} // asio
242} // boost
243
244//------------------------------------------------------------------------------
245
246namespace boost {
247namespace beast {
248
249class async_base_test : public beast::unit_test::suite
250{
251public:
252 // no associated allocator
253
254 BOOST_STATIC_ASSERT(
255 std::is_same<
256 std::allocator<void>,
257 net::associated_allocator_t<
258 async_base<
259 handler<no_ex, no_alloc>,
260 net::io_context::executor_type>
261 >>::value);
262
263 BOOST_STATIC_ASSERT(
264 std::is_same<
265 std::allocator<int>,
266 net::associated_allocator_t<
267 async_base<
268 handler<no_ex, no_alloc>,
269 net::io_context::executor_type,
270 std::allocator<int>>
271 >>::value);
272
273 BOOST_STATIC_ASSERT(
274 std::is_same<
275 std::allocator<void>,
276 net::associated_allocator_t<
277 async_base<
278 handler<no_ex, no_alloc>,
279 net::io_context::executor_type>,
280 std::allocator<int> // ignored
281 >>::value);
282
283 BOOST_STATIC_ASSERT(
284 std::is_same<
285 std::allocator<int>,
286 net::associated_allocator_t<
287 async_base<
288 handler<no_ex, no_alloc>,
289 net::io_context::executor_type,
290 std::allocator<int>>,
291 std::allocator<double> // ignored
292 >>::value);
293
294 // nested associated allocator
295
296 BOOST_STATIC_ASSERT(
297 std::is_same<
298 nested_alloc::allocator_type,
299 net::associated_allocator_t<
300 async_base<
301 handler<no_ex, nested_alloc>,
302 net::io_context::executor_type>
303 >>::value);
304
305 BOOST_STATIC_ASSERT(
306 std::is_same<
307 nested_alloc::allocator_type,
308 net::associated_allocator_t<
309 async_base<
310 handler<no_ex, nested_alloc>,
311 net::io_context::executor_type,
312 std::allocator<int>> // ignored
313 >>::value);
314
315 BOOST_STATIC_ASSERT(
316 std::is_same<
317 nested_alloc::allocator_type,
318 net::associated_allocator_t<
319 async_base<
320 handler<no_ex, nested_alloc>,
321 net::io_context::executor_type>,
322 std::allocator<int> // ignored
323 >>::value);
324
325 BOOST_STATIC_ASSERT(
326 std::is_same<
327 nested_alloc::allocator_type,
328 net::associated_allocator_t<
329 async_base<
330 handler<no_ex, nested_alloc>,
331 net::io_context::executor_type,
332 std::allocator<int>>, // ignored
333 std::allocator<int> // ignored
334 >>::value);
335
336 // intrusive associated allocator
337
338 BOOST_STATIC_ASSERT(
339 std::is_same<
340 intrusive_alloc::allocator_type,
341 net::associated_allocator_t<
342 async_base<
343 handler<no_ex, intrusive_alloc>,
344 net::io_context::executor_type>
345 >>::value);
346
347 BOOST_STATIC_ASSERT(
348 std::is_same<
349 intrusive_alloc::allocator_type,
350 net::associated_allocator_t<
351 async_base<
352 handler<no_ex, intrusive_alloc>,
353 net::io_context::executor_type,
354 std::allocator<int>> // ignored
355 >>::value);
356
357 BOOST_STATIC_ASSERT(
358 std::is_same<
359 intrusive_alloc::allocator_type,
360 net::associated_allocator_t<
361 async_base<
362 handler<no_ex, intrusive_alloc>,
363 net::io_context::executor_type>,
364 std::allocator<int> // ignored
365 >>::value);
366
367 BOOST_STATIC_ASSERT(
368 std::is_same<
369 intrusive_alloc::allocator_type,
370 net::associated_allocator_t<
371 async_base<
372 handler<no_ex, intrusive_alloc>,
373 net::io_context::executor_type,
374 std::allocator<int>>, // ignored
375 std::allocator<int> // ignored
376 >>::value);
377
378 // no associated executor
379
380 BOOST_STATIC_ASSERT(
381 std::is_same<
382 ex1_type,
383 net::associated_executor_t<
384 async_base<
385 handler<no_ex, no_alloc>,
386 ex1_type>
387 >>::value);
388
389 BOOST_STATIC_ASSERT(
390 std::is_same<
391 ex1_type,
392 net::associated_executor_t<
393 async_base<
394 handler<no_ex, no_alloc>,
395 ex1_type>,
396 net::system_executor // ignored
397 >>::value);
398
399 // nested associated executor
400
401 BOOST_STATIC_ASSERT(
402 std::is_same<
403 nested_ex::executor_type,
404 net::associated_executor_t<
405 async_base<
406 handler<nested_ex, no_alloc>,
407 ex1_type>
408 >>::value);
409
410 BOOST_STATIC_ASSERT(
411 std::is_same<
412 nested_ex::executor_type,
413 net::associated_executor_t<
414 async_base<
415 handler<nested_ex, no_alloc>,
416 ex1_type>,
417 net::system_executor // ignored
418 >>::value);
419
420 // intrusive associated executor
421
422 BOOST_STATIC_ASSERT(
423 std::is_same<
424 intrusive_ex::executor_type,
425 net::associated_executor_t<
426 async_base<
427 handler<intrusive_ex, no_alloc>,
428 ex1_type>
429 >>::value);
430
431 BOOST_STATIC_ASSERT(
432 std::is_same<
433 intrusive_ex::executor_type,
434 net::associated_executor_t<
435 async_base<
436 handler<intrusive_ex, no_alloc>,
437 ex1_type>,
438 net::system_executor // ignored
439 >>::value);
440
441 struct final_handler
442 {
443 bool& invoked;
444
445 void
446 operator()()
447 {
448 invoked = true;
449 }
450 };
451
452 void
453 testBase()
454 {
455 // get_allocator
456 {
457 simple_allocator alloc;
458 simple_allocator alloc2;
459 async_base<
460 move_only_handler,
461 simple_executor,
462 simple_allocator> op(
463 move_only_handler{}, {}, alloc);
464 BEAST_EXPECT(op.get_allocator() == alloc);
465 BEAST_EXPECT(op.get_allocator() != alloc2);
466 }
467
468 // get_executor
469 {
470 simple_executor ex;
471 simple_executor ex2;
472 async_base<
473 move_only_handler,
474 simple_executor> op(
475 move_only_handler{}, ex);
476 BEAST_EXPECT(op.get_executor() == ex);
477 BEAST_EXPECT(op.get_executor() != ex2);
478 }
479
480 // move construction
481 {
482 async_base<
483 move_only_handler,
484 simple_executor> op(
485 move_only_handler{}, {});
486 auto op2 = std::move(op);
487 }
488
489 // observers
490 {
491 bool b = false;
492 async_base<
493 legacy_handler,
494 simple_executor> op(
495 legacy_handler{.hook_invoked: b}, {});
496 BEAST_EXPECT(! op.handler().hook_invoked);
497 b = true;
498 BEAST_EXPECT(op.handler().hook_invoked);
499 b = false;
500 BEAST_EXPECT(! op.release_handler().hook_invoked);
501 }
502
503 // invocation
504 {
505 net::io_context ioc;
506 async_base<
507 test::handler,
508 net::io_context::executor_type> op(
509 test::any_handler(), ioc.get_executor());
510 op.complete(is_continuation: true);
511 }
512 {
513 net::io_context ioc;
514 auto op = new
515 async_base<
516 test::handler,
517 net::io_context::executor_type>(
518 test::any_handler(), ioc.get_executor());
519 op->complete(is_continuation: false);
520 delete op;
521 ioc.run();
522 }
523 {
524 async_base<
525 test::handler,
526 simple_executor> op(
527 test::any_handler(), {});
528 op.complete_now();
529 }
530
531 // legacy hooks
532 legacy_handler::test(
533 f: [](legacy_handler h)
534 {
535 return async_base<
536 legacy_handler,
537 simple_executor>(
538 std::move(h), {});
539 });
540 }
541
542 void
543 testStableBase()
544 {
545 // get_allocator
546 {
547 simple_allocator alloc;
548 simple_allocator alloc2;
549 stable_async_base<
550 move_only_handler,
551 simple_executor,
552 simple_allocator> op(
553 move_only_handler{}, {}, alloc);
554 BEAST_EXPECT(op.get_allocator() == alloc);
555 BEAST_EXPECT(op.get_allocator() != alloc2);
556 }
557
558 // get_executor
559 {
560 simple_executor ex;
561 simple_executor ex2;
562 stable_async_base<
563 move_only_handler,
564 simple_executor> op(
565 move_only_handler{}, ex);
566 BEAST_EXPECT(op.get_executor() == ex);
567 BEAST_EXPECT(op.get_executor() != ex2);
568 }
569
570 // move construction
571 {
572 stable_async_base<
573 move_only_handler,
574 simple_executor> op(
575 move_only_handler{}, {});
576 auto op2 = std::move(op);
577 }
578
579 // invocation
580 {
581 net::io_context ioc;
582 stable_async_base<
583 test::handler,
584 net::io_context::executor_type> op(
585 test::any_handler(), ioc.get_executor());
586 op.complete(is_continuation: true);
587 }
588 {
589 net::io_context ioc1;
590 net::io_context ioc2;
591 auto h = net::bind_executor(ctx&: ioc2, t: test::any_handler());
592 auto op = new stable_async_base<
593 decltype(h),
594 net::io_context::executor_type>(
595 std::move(h),
596 ioc1.get_executor());
597 op->complete(is_continuation: false);
598 delete op;
599 BEAST_EXPECT(ioc1.run() == 1);
600 BEAST_EXPECT(ioc2.run() == 1);
601 }
602 {
603 stable_async_base<
604 test::handler,
605 simple_executor> op(
606 test::any_handler(), {});
607 op.complete_now();
608 }
609
610 // legacy hooks
611 legacy_handler::test(
612 f: [](legacy_handler h)
613 {
614 return stable_async_base<
615 legacy_handler,
616 simple_executor>(
617 std::move(h), {});
618 });
619
620 // allocate_stable
621
622 {
623 bool destroyed = false;
624 {
625 struct data
626 {
627 bool& destroyed;
628
629 ~data()
630 {
631 destroyed = true;
632 }
633 };
634 stable_async_base<
635 move_only_handler,
636 simple_executor> op(
637 move_only_handler{}, {});
638 BEAST_EXPECT(! destroyed);
639 auto& d = allocate_stable<data>(base&: op, args&: destroyed);
640 BEAST_EXPECT(! d.destroyed);
641 }
642 BEAST_EXPECT(destroyed);
643 }
644 {
645 struct throwing_data
646 {
647 throwing_data()
648 {
649 BOOST_THROW_EXCEPTION(
650 std::exception{});
651 }
652 };
653 stable_async_base<
654 move_only_handler,
655 simple_executor> op(
656 move_only_handler{}, {});
657 try
658 {
659 allocate_stable<throwing_data>(base&: op);
660 fail();
661 }
662 catch(std::exception const&)
663 {
664 pass();
665 }
666 }
667 }
668
669 //--------------------------------------------------------------------------
670
671 // Asynchronously read into a buffer until the buffer is full, or an error occurs
672 template<class AsyncReadStream, BOOST_BEAST_ASYNC_TPARAM2 ReadHandler>
673 typename net::async_result<ReadHandler, void(error_code, std::size_t)>::return_type
674 async_read(AsyncReadStream& stream, net::mutable_buffer buffer, ReadHandler&& handler)
675 {
676 using handler_type = BOOST_ASIO_HANDLER_TYPE(ReadHandler, void(error_code, std::size_t));
677 using base_type = async_base<handler_type, typename AsyncReadStream::executor_type>;
678
679 struct op : base_type
680 {
681 AsyncReadStream& stream_;
682 net::mutable_buffer buffer_;
683 std::size_t total_bytes_transferred_;
684
685 op(
686 AsyncReadStream& stream,
687 net::mutable_buffer buffer,
688 handler_type& handler)
689 : base_type(std::move(handler), stream.get_executor())
690 , stream_(stream)
691 , buffer_(buffer)
692 , total_bytes_transferred_(0)
693 {
694 (*this)({}, 0, false); // start the operation
695 }
696
697 void operator()(error_code ec, std::size_t bytes_transferred, bool is_continuation = true)
698 {
699 // Adjust the count of bytes and advance our buffer
700 total_bytes_transferred_ += bytes_transferred;
701 buffer_ = buffer_ + bytes_transferred;
702
703 // Keep reading until buffer is full or an error occurs
704 if(! ec && buffer_.size() > 0)
705 return stream_.async_read_some(buffer_, std::move(*this));
706
707 // Call the completion handler with the result. If `is_continuation` is
708 // false, which happens on the first time through this function, then
709 // `net::post` will be used to call the completion handler, otherwise
710 // the completion handler will be invoked directly.
711
712 this->complete(is_continuation, ec, total_bytes_transferred_);
713 }
714 };
715
716 net::async_completion<ReadHandler, void(error_code, std::size_t)> init{handler};
717 op(stream, buffer, init.completion_handler);
718 return init.result.get();
719 }
720
721 // Asynchronously send a message multiple times, once per second
722 template <class AsyncWriteStream, class T, class WriteHandler>
723 auto async_write_messages(
724 AsyncWriteStream& stream,
725 T const& message,
726 std::size_t repeat_count,
727 WriteHandler&& handler) ->
728 typename net::async_result<
729 typename std::decay<WriteHandler>::type,
730 void(error_code)>::return_type
731 {
732 using handler_type = typename net::async_completion<WriteHandler, void(error_code)>::completion_handler_type;
733 using base_type = stable_async_base<handler_type, typename AsyncWriteStream::executor_type>;
734
735 struct op : base_type, boost::asio::coroutine
736 {
737 // This object must have a stable address
738 struct temporary_data
739 {
740 // Although std::string is in theory movable, most implementations
741 // use a "small buffer optimization" which means that we might
742 // be submitting a buffer to the write operation and then
743 // moving the string, invalidating the buffer. To prevent
744 // undefined behavior we store the string object itself at
745 // a stable location.
746 std::string const message;
747
748 net::steady_timer timer;
749
750 temporary_data(std::string message_, net::any_io_executor ex)
751 : message(std::move(message_))
752 , timer(std::move(ex))
753 {
754 }
755 };
756
757 AsyncWriteStream& stream_;
758 std::size_t repeats_;
759 temporary_data& data_;
760
761 op(AsyncWriteStream& stream, std::size_t repeats, std::string message, handler_type& handler)
762 : base_type(std::move(handler), stream.get_executor())
763 , stream_(stream)
764 , repeats_(repeats)
765 , data_(allocate_stable<temporary_data>(*this,
766 std::move(message),
767 stream.get_executor()))
768 {
769 (*this)(); // start the operation
770 }
771
772 // Including this file provides the keywords for macro-based coroutines
773 #include <boost/asio/yield.hpp>
774
775 void operator()(error_code ec = {}, std::size_t = 0)
776 {
777 reenter(*this)
778 {
779 // If repeats starts at 0 then we must complete immediately. But
780 // we can't call the final handler from inside the initiating
781 // function, so we post our intermediate handler first. We use
782 // net::async_write with an empty buffer instead of calling
783 // net::post to avoid an extra function template instantiation, to
784 // keep compile times lower and make the resulting executable smaller.
785 yield net::async_write(stream_, net::const_buffer{}, std::move(*this));
786 while(! ec && repeats_-- > 0)
787 {
788 // Send the string. We construct a `const_buffer` here to guarantee
789 // that we do not create an additional function template instantation
790 // of net::async_write, since we already instantiated it above for
791 // net::const_buffer.
792
793 yield net::async_write(stream_,
794 net::const_buffer(net::buffer(data_.message)), std::move(*this));
795 if(ec)
796 break;
797
798 // Set the timer and wait
799 data_.timer.expires_after(std::chrono::seconds(1));
800 yield data_.timer.async_wait(std::move(*this));
801 }
802 }
803
804 // The base class destroys the temporary data automatically,
805 // before invoking the final completion handler
806 this->complete_now(ec);
807 }
808
809 // Including this file undefines the macros for the coroutines
810 #include <boost/asio/unyield.hpp>
811 };
812
813 net::async_completion<WriteHandler, void(error_code)> completion(handler);
814 std::ostringstream os;
815 os << message;
816 op(stream, repeat_count, os.str(), completion.completion_handler);
817 return completion.result.get();
818 }
819
820 void
821 testJavadocs()
822 {
823 struct handler
824 {
825 void operator()(error_code = {}, std::size_t = 0)
826 {
827 }
828 };
829
830 BEAST_EXPECT((&async_base_test::async_read<test::stream, handler>));
831 BEAST_EXPECT((&async_base_test::async_write_messages<test::stream, std::string, handler>));
832 }
833
834 //--------------------------------------------------------------------------
835
836 void
837 run() override
838 {
839 testBase();
840 testStableBase();
841 testJavadocs();
842 }
843};
844
845BEAST_DEFINE_TESTSUITE(beast,core,async_base);
846
847} // beast
848} // boost
849

source code of boost/libs/beast/test/beast/core/async_base.cpp