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