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 #ifndef BOOST_BEAST_CORE_ASYNC_BASE_HPP 11 #define BOOST_BEAST_CORE_ASYNC_BASE_HPP 12 13 #include <boost/beast/core/detail/config.hpp> 14 #include <boost/beast/core/bind_handler.hpp> 15 #include <boost/beast/core/detail/allocator.hpp> 16 #include <boost/beast/core/detail/async_base.hpp> 17 #include <boost/beast/core/detail/work_guard.hpp> 18 #include <boost/asio/associated_allocator.hpp> 19 #include <boost/asio/associated_executor.hpp> 20 #include <boost/asio/bind_executor.hpp> 21 #include <boost/asio/handler_alloc_hook.hpp> 22 #include <boost/asio/handler_continuation_hook.hpp> 23 #include <boost/asio/handler_invoke_hook.hpp> 24 #include <boost/asio/post.hpp> 25 #include <boost/core/exchange.hpp> 26 #include <boost/core/empty_value.hpp> 27 #include <utility> 28 29 namespace boost { 30 namespace beast { 31 32 /** Base class to assist writing composed operations. 33 34 A function object submitted to intermediate initiating functions during 35 a composed operation may derive from this type to inherit all of the 36 boilerplate to forward the executor, allocator, and legacy customization 37 points associated with the completion handler invoked at the end of the 38 composed operation. 39 40 The composed operation must be typical; that is, associated with one 41 executor of an I/O object, and invoking a caller-provided completion 42 handler when the operation is finished. Classes derived from 43 @ref async_base will acquire these properties: 44 45 @li Ownership of the final completion handler provided upon construction. 46 47 @li If the final handler has an associated allocator, this allocator will 48 be propagated to the composed operation subclass. Otherwise, the 49 associated allocator will be the type specified in the allocator 50 template parameter, or the default of `std::allocator<void>` if the 51 parameter is omitted. 52 53 @li If the final handler has an associated executor, then it will be used 54 as the executor associated with the composed operation. Otherwise, 55 the specified `Executor1` will be the type of executor associated 56 with the composed operation. 57 58 @li An instance of `net::executor_work_guard` for the instance of `Executor1` 59 shall be maintained until either the final handler is invoked, or the 60 operation base is destroyed, whichever comes first. 61 62 @li Calls to the legacy customization points 63 `asio_handler_invoke`, 64 `asio_handler_allocate`, 65 `asio_handler_deallocate`, and 66 `asio_handler_is_continuation`, 67 which use argument-dependent lookup, will be forwarded to the 68 legacy customization points associated with the handler. 69 70 @par Example 71 72 The following code demonstrates how @ref async_base may be be used to 73 assist authoring an asynchronous initiating function, by providing all of 74 the boilerplate to manage the final completion handler in a way that 75 maintains the allocator and executor associations: 76 77 @code 78 79 // Asynchronously read into a buffer until the buffer is full, or an error occurs 80 template<class AsyncReadStream, class ReadHandler> 81 typename net::async_result<ReadHandler, void(error_code, std::size_t)>::return_type 82 async_read(AsyncReadStream& stream, net::mutable_buffer buffer, ReadHandler&& handler) 83 { 84 using handler_type = BOOST_ASIO_HANDLER_TYPE(ReadHandler, void(error_code, std::size_t)); 85 using base_type = async_base<handler_type, typename AsyncReadStream::executor_type>; 86 87 struct op : base_type 88 { 89 AsyncReadStream& stream_; 90 net::mutable_buffer buffer_; 91 std::size_t total_bytes_transferred_; 92 93 op( 94 AsyncReadStream& stream, 95 net::mutable_buffer buffer, 96 handler_type& handler) 97 : base_type(std::move(handler), stream.get_executor()) 98 , stream_(stream) 99 , buffer_(buffer) 100 , total_bytes_transferred_(0) 101 { 102 (*this)({}, 0, false); // start the operation 103 } 104 105 void operator()(error_code ec, std::size_t bytes_transferred, bool is_continuation = true) 106 { 107 // Adjust the count of bytes and advance our buffer 108 total_bytes_transferred_ += bytes_transferred; 109 buffer_ = buffer_ + bytes_transferred; 110 111 // Keep reading until buffer is full or an error occurs 112 if(! ec && buffer_.size() > 0) 113 return stream_.async_read_some(buffer_, std::move(*this)); 114 115 // Call the completion handler with the result. If `is_continuation` is 116 // false, which happens on the first time through this function, then 117 // `net::post` will be used to call the completion handler, otherwise 118 // the completion handler will be invoked directly. 119 120 this->complete(is_continuation, ec, total_bytes_transferred_); 121 } 122 }; 123 124 net::async_completion<ReadHandler, void(error_code, std::size_t)> init{handler}; 125 op(stream, buffer, init.completion_handler); 126 return init.result.get(); 127 } 128 129 @endcode 130 131 Data members of composed operations implemented as completion handlers 132 do not have stable addresses, as the composed operation object is move 133 constructed upon each call to an initiating function. For most operations 134 this is not a problem. For complex operations requiring stable temporary 135 storage, the class @ref stable_async_base is provided which offers 136 additional functionality: 137 138 @li The free function @ref allocate_stable may be used to allocate 139 one or more temporary objects associated with the composed operation. 140 141 @li Memory for stable temporary objects is allocated using the allocator 142 associated with the composed operation. 143 144 @li Stable temporary objects are automatically destroyed, and the memory 145 freed using the associated allocator, either before the final completion 146 handler is invoked (a Networking requirement) or when the composed operation 147 is destroyed, whichever occurs first. 148 149 @par Temporary Storage Example 150 151 The following example demonstrates how a composed operation may store a 152 temporary object. 153 154 @code 155 156 @endcode 157 158 @tparam Handler The type of the completion handler to store. 159 This type must meet the requirements of <em>CompletionHandler</em>. 160 161 @tparam Executor1 The type of the executor used when the handler has no 162 associated executor. An instance of this type must be provided upon 163 construction. The implementation will maintain an executor work guard 164 and a copy of this instance. 165 166 @tparam Allocator The allocator type to use if the handler does not 167 have an associated allocator. If this parameter is omitted, then 168 `std::allocator<void>` will be used. If the specified allocator is 169 not default constructible, an instance of the type must be provided 170 upon construction. 171 172 @see stable_async_base 173 */ 174 template< 175 class Handler, 176 class Executor1, 177 class Allocator = std::allocator<void> 178 > 179 class async_base 180 #if ! BOOST_BEAST_DOXYGEN 181 : private boost::empty_value<Allocator> 182 #endif 183 { 184 static_assert( 185 net::is_executor<Executor1>::value || net::execution::is_executor<Executor1>::value, 186 "Executor type requirements not met"); 187 188 Handler h_; 189 detail::select_work_guard_t<Executor1> wg1_; 190 191 public: 192 /** The type of executor associated with this object. 193 194 If a class derived from @ref async_base is a completion 195 handler, then the associated executor of the derived class will 196 be this type. 197 */ 198 using executor_type = 199 #if BOOST_BEAST_DOXYGEN 200 __implementation_defined__; 201 #else 202 typename 203 net::associated_executor< 204 Handler, 205 typename detail::select_work_guard_t<Executor1>::executor_type 206 >::type; 207 #endif 208 209 private: 210 211 virtual 212 void before_invoke_hook()213 before_invoke_hook() 214 { 215 } 216 217 public: 218 /** Constructor 219 220 @param handler The final completion handler. 221 The type of this object must meet the requirements of <em>CompletionHandler</em>. 222 The implementation takes ownership of the handler by performing a decay-copy. 223 224 @param ex1 The executor associated with the implied I/O object 225 target of the operation. The implementation shall maintain an 226 executor work guard for the lifetime of the operation, or until 227 the final completion handler is invoked, whichever is shorter. 228 229 @param alloc The allocator to be associated with objects 230 derived from this class. If `Allocator` is default-constructible, 231 this parameter is optional and may be omitted. 232 */ 233 #if BOOST_BEAST_DOXYGEN 234 template<class Handler_> 235 async_base( 236 Handler&& handler, 237 Executor1 const& ex1, 238 Allocator const& alloc = Allocator()); 239 #else 240 template< 241 class Handler_, 242 class = typename std::enable_if< 243 ! std::is_same<typename 244 std::decay<Handler_>::type, 245 async_base 246 >::value>::type 247 > 248 async_base( 249 Handler_&& handler, 250 Executor1 const& ex1) 251 : h_(std::forward<Handler_>(handler)) 252 , wg1_(detail::make_work_guard(ex1)) 253 { 254 } 255 256 template<class Handler_> 257 async_base( 258 Handler_&& handler, 259 Executor1 const& ex1, 260 Allocator const& alloc) 261 : boost::empty_value<Allocator>( 262 boost::empty_init_t{}, alloc) 263 , h_(std::forward<Handler_>(handler)) 264 , wg1_(ex1) 265 { 266 } 267 #endif 268 269 /// Move Constructor 270 async_base(async_base&& other) = default; 271 272 virtual ~async_base() = default; 273 async_base(async_base const&) = delete; 274 async_base& operator=(async_base const&) = delete; 275 276 /** The type of allocator associated with this object. 277 278 If a class derived from @ref async_base is a completion 279 handler, then the associated allocator of the derived class will 280 be this type. 281 */ 282 using allocator_type = 283 net::associated_allocator_t<Handler, Allocator>; 284 285 /** Returns the allocator associated with this object. 286 287 If a class derived from @ref async_base is a completion 288 handler, then the object returned from this function will be used 289 as the associated allocator of the derived class. 290 */ 291 allocator_type get_allocator() const292 get_allocator() const noexcept 293 { 294 return net::get_associated_allocator(h_, 295 boost::empty_value<Allocator>::get()); 296 } 297 298 /** Returns the executor associated with this object. 299 300 If a class derived from @ref async_base is a completion 301 handler, then the object returned from this function will be used 302 as the associated executor of the derived class. 303 */ 304 executor_type get_executor() const305 get_executor() const noexcept 306 { 307 return net::get_associated_executor( 308 h_, wg1_.get_executor()); 309 } 310 311 /// Returns the handler associated with this object 312 Handler const& handler() const313 handler() const noexcept 314 { 315 return h_; 316 } 317 318 /** Returns ownership of the handler associated with this object 319 320 This function is used to transfer ownership of the handler to 321 the caller, by move-construction. After the move, the only 322 valid operations on the base object are move construction and 323 destruction. 324 */ 325 Handler release_handler()326 release_handler() 327 { 328 return std::move(h_); 329 } 330 331 /** Invoke the final completion handler, maybe using post. 332 333 This invokes the final completion handler with the specified 334 arguments forwarded. It is undefined to call either of 335 @ref complete or @ref complete_now more than once. 336 337 Any temporary objects allocated with @ref beast::allocate_stable will 338 be automatically destroyed before the final completion handler 339 is invoked. 340 341 @param is_continuation If this value is `false`, then the 342 handler will be submitted to the executor using `net::post`. 343 Otherwise the handler will be invoked as if by calling 344 @ref complete_now. 345 346 @param args A list of optional parameters to invoke the handler 347 with. The completion handler must be invocable with the parameter 348 list, or else a compilation error will result. 349 */ 350 template<class... Args> 351 void complete(bool is_continuation,Args &&...args)352 complete(bool is_continuation, Args&&... args) 353 { 354 this->before_invoke_hook(); 355 if(! is_continuation) 356 { 357 auto const ex = get_executor(); 358 net::post(net::bind_executor( 359 ex, 360 beast::bind_front_handler( 361 std::move(h_), 362 std::forward<Args>(args)...))); 363 wg1_.reset(); 364 } 365 else 366 { 367 wg1_.reset(); 368 h_(std::forward<Args>(args)...); 369 } 370 } 371 372 /** Invoke the final completion handler. 373 374 This invokes the final completion handler with the specified 375 arguments forwarded. It is undefined to call either of 376 @ref complete or @ref complete_now more than once. 377 378 Any temporary objects allocated with @ref beast::allocate_stable will 379 be automatically destroyed before the final completion handler 380 is invoked. 381 382 @param args A list of optional parameters to invoke the handler 383 with. The completion handler must be invocable with the parameter 384 list, or else a compilation error will result. 385 */ 386 template<class... Args> 387 void complete_now(Args &&...args)388 complete_now(Args&&... args) 389 { 390 this->before_invoke_hook(); 391 wg1_.reset(); 392 h_(std::forward<Args>(args)...); 393 } 394 395 #if ! BOOST_BEAST_DOXYGEN 396 Handler* get_legacy_handler_pointer()397 get_legacy_handler_pointer() noexcept 398 { 399 return std::addressof(h_); 400 } 401 #endif 402 }; 403 404 //------------------------------------------------------------------------------ 405 406 /** Base class to provide completion handler boilerplate for composed operations. 407 408 A function object submitted to intermediate initiating functions during 409 a composed operation may derive from this type to inherit all of the 410 boilerplate to forward the executor, allocator, and legacy customization 411 points associated with the completion handler invoked at the end of the 412 composed operation. 413 414 The composed operation must be typical; that is, associated with one 415 executor of an I/O object, and invoking a caller-provided completion 416 handler when the operation is finished. Classes derived from 417 @ref async_base will acquire these properties: 418 419 @li Ownership of the final completion handler provided upon construction. 420 421 @li If the final handler has an associated allocator, this allocator will 422 be propagated to the composed operation subclass. Otherwise, the 423 associated allocator will be the type specified in the allocator 424 template parameter, or the default of `std::allocator<void>` if the 425 parameter is omitted. 426 427 @li If the final handler has an associated executor, then it will be used 428 as the executor associated with the composed operation. Otherwise, 429 the specified `Executor1` will be the type of executor associated 430 with the composed operation. 431 432 @li An instance of `net::executor_work_guard` for the instance of `Executor1` 433 shall be maintained until either the final handler is invoked, or the 434 operation base is destroyed, whichever comes first. 435 436 @li Calls to the legacy customization points 437 `asio_handler_invoke`, 438 `asio_handler_allocate`, 439 `asio_handler_deallocate`, and 440 `asio_handler_is_continuation`, 441 which use argument-dependent lookup, will be forwarded to the 442 legacy customization points associated with the handler. 443 444 Data members of composed operations implemented as completion handlers 445 do not have stable addresses, as the composed operation object is move 446 constructed upon each call to an initiating function. For most operations 447 this is not a problem. For complex operations requiring stable temporary 448 storage, the class @ref stable_async_base is provided which offers 449 additional functionality: 450 451 @li The free function @ref beast::allocate_stable may be used to allocate 452 one or more temporary objects associated with the composed operation. 453 454 @li Memory for stable temporary objects is allocated using the allocator 455 associated with the composed operation. 456 457 @li Stable temporary objects are automatically destroyed, and the memory 458 freed using the associated allocator, either before the final completion 459 handler is invoked (a Networking requirement) or when the composed operation 460 is destroyed, whichever occurs first. 461 462 @par Example 463 464 The following code demonstrates how @ref stable_async_base may be be used to 465 assist authoring an asynchronous initiating function, by providing all of 466 the boilerplate to manage the final completion handler in a way that maintains 467 the allocator and executor associations. Furthermore, the operation shown 468 allocates temporary memory using @ref beast::allocate_stable for the timer and 469 message, whose addresses must not change between intermediate operations: 470 471 @code 472 473 // Asynchronously send a message multiple times, once per second 474 template <class AsyncWriteStream, class T, class WriteHandler> 475 auto async_write_messages( 476 AsyncWriteStream& stream, 477 T const& message, 478 std::size_t repeat_count, 479 WriteHandler&& handler) -> 480 typename net::async_result< 481 typename std::decay<WriteHandler>::type, 482 void(error_code)>::return_type 483 { 484 using handler_type = typename net::async_completion<WriteHandler, void(error_code)>::completion_handler_type; 485 using base_type = stable_async_base<handler_type, typename AsyncWriteStream::executor_type>; 486 487 struct op : base_type, boost::asio::coroutine 488 { 489 // This object must have a stable address 490 struct temporary_data 491 { 492 // Although std::string is in theory movable, most implementations 493 // use a "small buffer optimization" which means that we might 494 // be submitting a buffer to the write operation and then 495 // moving the string, invalidating the buffer. To prevent 496 // undefined behavior we store the string object itself at 497 // a stable location. 498 std::string const message; 499 500 net::steady_timer timer; 501 502 temporary_data(std::string message_, net::io_context& ctx) 503 : message(std::move(message_)) 504 , timer(ctx) 505 { 506 } 507 }; 508 509 AsyncWriteStream& stream_; 510 std::size_t repeats_; 511 temporary_data& data_; 512 513 op(AsyncWriteStream& stream, std::size_t repeats, std::string message, handler_type& handler) 514 : base_type(std::move(handler), stream.get_executor()) 515 , stream_(stream) 516 , repeats_(repeats) 517 , data_(allocate_stable<temporary_data>(*this, std::move(message), stream.get_executor().context())) 518 { 519 (*this)(); // start the operation 520 } 521 522 // Including this file provides the keywords for macro-based coroutines 523 #include <boost/asio/yield.hpp> 524 525 void operator()(error_code ec = {}, std::size_t = 0) 526 { 527 reenter(*this) 528 { 529 // If repeats starts at 0 then we must complete immediately. But 530 // we can't call the final handler from inside the initiating 531 // function, so we post our intermediate handler first. We use 532 // net::async_write with an empty buffer instead of calling 533 // net::post to avoid an extra function template instantiation, to 534 // keep compile times lower and make the resulting executable smaller. 535 yield net::async_write(stream_, net::const_buffer{}, std::move(*this)); 536 while(! ec && repeats_-- > 0) 537 { 538 // Send the string. We construct a `const_buffer` here to guarantee 539 // that we do not create an additional function template instantation 540 // of net::async_write, since we already instantiated it above for 541 // net::const_buffer. 542 543 yield net::async_write(stream_, 544 net::const_buffer(net::buffer(data_.message)), std::move(*this)); 545 if(ec) 546 break; 547 548 // Set the timer and wait 549 data_.timer.expires_after(std::chrono::seconds(1)); 550 yield data_.timer.async_wait(std::move(*this)); 551 } 552 } 553 554 // The base class destroys the temporary data automatically, 555 // before invoking the final completion handler 556 this->complete_now(ec); 557 } 558 559 // Including this file undefines the macros for the coroutines 560 #include <boost/asio/unyield.hpp> 561 }; 562 563 net::async_completion<WriteHandler, void(error_code)> completion(handler); 564 std::ostringstream os; 565 os << message; 566 op(stream, repeat_count, os.str(), completion.completion_handler); 567 return completion.result.get(); 568 } 569 570 @endcode 571 572 @tparam Handler The type of the completion handler to store. 573 This type must meet the requirements of <em>CompletionHandler</em>. 574 575 @tparam Executor1 The type of the executor used when the handler has no 576 associated executor. An instance of this type must be provided upon 577 construction. The implementation will maintain an executor work guard 578 and a copy of this instance. 579 580 @tparam Allocator The allocator type to use if the handler does not 581 have an associated allocator. If this parameter is omitted, then 582 `std::allocator<void>` will be used. If the specified allocator is 583 not default constructible, an instance of the type must be provided 584 upon construction. 585 586 @see allocate_stable, async_base 587 */ 588 template< 589 class Handler, 590 class Executor1, 591 class Allocator = std::allocator<void> 592 > 593 class stable_async_base 594 : public async_base< 595 Handler, Executor1, Allocator> 596 { 597 detail::stable_base* list_ = nullptr; 598 599 void before_invoke_hook()600 before_invoke_hook() override 601 { 602 detail::stable_base::destroy_list(list_); 603 } 604 605 public: 606 /** Constructor 607 608 @param handler The final completion handler. 609 The type of this object must meet the requirements of <em>CompletionHandler</em>. 610 The implementation takes ownership of the handler by performing a decay-copy. 611 612 @param ex1 The executor associated with the implied I/O object 613 target of the operation. The implementation shall maintain an 614 executor work guard for the lifetime of the operation, or until 615 the final completion handler is invoked, whichever is shorter. 616 617 @param alloc The allocator to be associated with objects 618 derived from this class. If `Allocator` is default-constructible, 619 this parameter is optional and may be omitted. 620 */ 621 #if BOOST_BEAST_DOXYGEN 622 template<class Handler> 623 stable_async_base( 624 Handler&& handler, 625 Executor1 const& ex1, 626 Allocator const& alloc = Allocator()); 627 #else 628 template< 629 class Handler_, 630 class = typename std::enable_if< 631 ! std::is_same<typename 632 std::decay<Handler_>::type, 633 stable_async_base 634 >::value>::type 635 > 636 stable_async_base( 637 Handler_&& handler, 638 Executor1 const& ex1) 639 : async_base< 640 Handler, Executor1, Allocator>( 641 std::forward<Handler_>(handler), ex1) 642 { 643 } 644 645 template<class Handler_> 646 stable_async_base( 647 Handler_&& handler, 648 Executor1 const& ex1, 649 Allocator const& alloc) 650 : async_base< 651 Handler, Executor1, Allocator>( 652 std::forward<Handler_>(handler), ex1, alloc) 653 { 654 } 655 #endif 656 657 /// Move Constructor stable_async_base(stable_async_base && other)658 stable_async_base(stable_async_base&& other) 659 : async_base<Handler, Executor1, Allocator>( 660 std::move(other)) 661 , list_(boost::exchange(other.list_, nullptr)) 662 { 663 } 664 665 /** Destructor 666 667 If the completion handler was not invoked, then any 668 state objects allocated with @ref allocate_stable will 669 be destroyed here. 670 */ ~stable_async_base()671 ~stable_async_base() 672 { 673 detail::stable_base::destroy_list(list_); 674 } 675 676 /** Allocate a temporary object to hold operation state. 677 678 The object will be destroyed just before the completion 679 handler is invoked, or when the operation base is destroyed. 680 */ 681 template< 682 class State, 683 class Handler_, 684 class Executor1_, 685 class Allocator_, 686 class... Args> 687 friend 688 State& 689 allocate_stable( 690 stable_async_base< 691 Handler_, Executor1_, Allocator_>& base, 692 Args&&... args); 693 }; 694 695 /** Allocate a temporary object to hold stable asynchronous operation state. 696 697 The object will be destroyed just before the completion 698 handler is invoked, or when the base is destroyed. 699 700 @tparam State The type of object to allocate. 701 702 @param base The helper to allocate from. 703 704 @param args An optional list of parameters to forward to the 705 constructor of the object being allocated. 706 707 @see stable_async_base 708 */ 709 template< 710 class State, 711 class Handler, 712 class Executor1, 713 class Allocator, 714 class... Args> 715 State& 716 allocate_stable( 717 stable_async_base< 718 Handler, Executor1, Allocator>& base, 719 Args&&... args); 720 721 } // beast 722 } // boost 723 724 #include <boost/beast/core/impl/async_base.hpp> 725 726 #endif 727