2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
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)
7 // Official repository: https://github.com/boostorg/beast
10 #ifndef BOOST_BEAST_CORE_ASYNC_BASE_HPP
11 #define BOOST_BEAST_CORE_ASYNC_BASE_HPP
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/asio/associated_allocator.hpp>
18 #include <boost/asio/associated_executor.hpp>
19 #include <boost/asio/bind_executor.hpp>
20 #include <boost/asio/executor_work_guard.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>
32 /** Base class to assist writing composed operations.
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
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:
45 @li Ownership of the final completion handler provided upon construction.
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
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.
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.
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.
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:
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)
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>;
89 AsyncReadStream& stream_;
90 net::mutable_buffer buffer_;
91 std::size_t total_bytes_transferred_;
94 AsyncReadStream& stream,
95 net::mutable_buffer buffer,
96 handler_type& handler)
97 : base_type(std::move(handler), stream.get_executor())
100 , total_bytes_transferred_(0)
102 (*this)({}, 0, false); // start the operation
105 void operator()(error_code ec, std::size_t bytes_transferred, bool is_continuation = true)
107 // Adjust the count of bytes and advance our buffer
108 total_bytes_transferred_ += bytes_transferred;
109 buffer_ = buffer_ + bytes_transferred;
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));
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.
120 this->complete(is_continuation, ec, total_bytes_transferred_);
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();
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:
138 @li The free function @ref allocate_stable may be used to allocate
139 one or more temporary objects associated with the composed operation.
141 @li Memory for stable temporary objects is allocated using the allocator
142 associated with the composed operation.
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.
149 @par Temporary Storage Example
151 The following example demonstrates how a composed operation may store a
158 @tparam Handler The type of the completion handler to store.
159 This type must meet the requirements of <em>CompletionHandler</em>.
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.
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
172 @see stable_async_base
177 class Allocator = std::allocator<void>
180 #if ! BOOST_BEAST_DOXYGEN
181 : private boost::empty_value<Allocator>
185 net::is_executor<Executor1>::value,
186 "Executor type requirements not met");
189 net::executor_work_guard<Executor1> wg1_;
200 @param handler The final completion handler.
201 The type of this object must meet the requirements of <em>CompletionHandler</em>.
202 The implementation takes ownership of the handler by performing a decay-copy.
204 @param ex1 The executor associated with the implied I/O object
205 target of the operation. The implementation shall maintain an
206 executor work guard for the lifetime of the operation, or until
207 the final completion handler is invoked, whichever is shorter.
209 @param alloc The allocator to be associated with objects
210 derived from this class. If `Allocator` is default-constructible,
211 this parameter is optional and may be omitted.
213 #if BOOST_BEAST_DOXYGEN
214 template<class Handler_>
217 Executor1 const& ex1,
218 Allocator const& alloc = Allocator());
222 class = typename std::enable_if<
223 ! std::is_same<typename
224 std::decay<Handler_>::type,
230 Executor1 const& ex1)
231 : h_(std::forward<Handler_>(handler))
236 template<class Handler_>
239 Executor1 const& ex1,
240 Allocator const& alloc)
241 : boost::empty_value<Allocator>(
242 boost::empty_init_t{}, alloc)
243 , h_(std::forward<Handler_>(handler))
250 async_base(async_base&& other) = default;
252 virtual ~async_base() = default;
253 async_base(async_base const&) = delete;
254 async_base& operator=(async_base const&) = delete;
256 /** The type of allocator associated with this object.
258 If a class derived from @ref async_base is a completion
259 handler, then the associated allocator of the derived class will
262 using allocator_type =
263 net::associated_allocator_t<Handler, Allocator>;
265 /** The type of executor associated with this object.
267 If a class derived from @ref async_base is a completion
268 handler, then the associated executor of the derived class will
271 using executor_type =
272 net::associated_executor_t<Handler, Executor1>;
274 /** Returns the allocator associated with this object.
276 If a class derived from @ref async_base is a completion
277 handler, then the object returned from this function will be used
278 as the associated allocator of the derived class.
281 get_allocator() const noexcept
283 return net::get_associated_allocator(h_,
284 boost::empty_value<Allocator>::get());
287 /** Returns the executor associated with this object.
289 If a class derived from @ref async_base is a completion
290 handler, then the object returned from this function will be used
291 as the associated executor of the derived class.
294 get_executor() const noexcept
296 return net::get_associated_executor(
297 h_, wg1_.get_executor());
300 /// Returns the handler associated with this object
302 handler() const noexcept
307 /** Returns ownership of the handler associated with this object
309 This function is used to transfer ownership of the handler to
310 the caller, by move-construction. After the move, the only
311 valid operations on the base object are move construction and
317 return std::move(h_);
320 /** Invoke the final completion handler, maybe using post.
322 This invokes the final completion handler with the specified
323 arguments forwarded. It is undefined to call either of
324 @ref complete or @ref complete_now more than once.
326 Any temporary objects allocated with @ref beast::allocate_stable will
327 be automatically destroyed before the final completion handler
330 @param is_continuation If this value is `false`, then the
331 handler will be submitted to the executor using `net::post`.
332 Otherwise the handler will be invoked as if by calling
335 @param args A list of optional parameters to invoke the handler
336 with. The completion handler must be invocable with the parameter
337 list, or else a compilation error will result.
339 template<class... Args>
341 complete(bool is_continuation, Args&&... args)
343 this->before_invoke_hook();
344 if(! is_continuation)
346 auto const ex = get_executor();
347 net::post(net::bind_executor(
349 beast::bind_front_handler(
351 std::forward<Args>(args)...)));
357 h_(std::forward<Args>(args)...);
361 /** Invoke the final completion handler.
363 This invokes the final completion handler with the specified
364 arguments forwarded. It is undefined to call either of
365 @ref complete or @ref complete_now more than once.
367 Any temporary objects allocated with @ref beast::allocate_stable will
368 be automatically destroyed before the final completion handler
371 @param args A list of optional parameters to invoke the handler
372 with. The completion handler must be invocable with the parameter
373 list, or else a compilation error will result.
375 template<class... Args>
377 complete_now(Args&&... args)
379 this->before_invoke_hook();
381 h_(std::forward<Args>(args)...);
384 #if ! BOOST_BEAST_DOXYGEN
386 get_legacy_handler_pointer() noexcept
388 return std::addressof(h_);
393 //------------------------------------------------------------------------------
395 /** Base class to provide completion handler boilerplate for composed operations.
397 A function object submitted to intermediate initiating functions during
398 a composed operation may derive from this type to inherit all of the
399 boilerplate to forward the executor, allocator, and legacy customization
400 points associated with the completion handler invoked at the end of the
403 The composed operation must be typical; that is, associated with one
404 executor of an I/O object, and invoking a caller-provided completion
405 handler when the operation is finished. Classes derived from
406 @ref async_base will acquire these properties:
408 @li Ownership of the final completion handler provided upon construction.
410 @li If the final handler has an associated allocator, this allocator will
411 be propagated to the composed operation subclass. Otherwise, the
412 associated allocator will be the type specified in the allocator
413 template parameter, or the default of `std::allocator<void>` if the
414 parameter is omitted.
416 @li If the final handler has an associated executor, then it will be used
417 as the executor associated with the composed operation. Otherwise,
418 the specified `Executor1` will be the type of executor associated
419 with the composed operation.
421 @li An instance of `net::executor_work_guard` for the instance of `Executor1`
422 shall be maintained until either the final handler is invoked, or the
423 operation base is destroyed, whichever comes first.
425 @li Calls to the legacy customization points
426 `asio_handler_invoke`,
427 `asio_handler_allocate`,
428 `asio_handler_deallocate`, and
429 `asio_handler_is_continuation`,
430 which use argument-dependent lookup, will be forwarded to the
431 legacy customization points associated with the handler.
433 Data members of composed operations implemented as completion handlers
434 do not have stable addresses, as the composed operation object is move
435 constructed upon each call to an initiating function. For most operations
436 this is not a problem. For complex operations requiring stable temporary
437 storage, the class @ref stable_async_base is provided which offers
438 additional functionality:
440 @li The free function @ref beast::allocate_stable may be used to allocate
441 one or more temporary objects associated with the composed operation.
443 @li Memory for stable temporary objects is allocated using the allocator
444 associated with the composed operation.
446 @li Stable temporary objects are automatically destroyed, and the memory
447 freed using the associated allocator, either before the final completion
448 handler is invoked (a Networking requirement) or when the composed operation
449 is destroyed, whichever occurs first.
453 The following code demonstrates how @ref stable_async_base may be be used to
454 assist authoring an asynchronous initiating function, by providing all of
455 the boilerplate to manage the final completion handler in a way that maintains
456 the allocator and executor associations. Furthermore, the operation shown
457 allocates temporary memory using @ref beast::allocate_stable for the timer and
458 message, whose addresses must not change between intermediate operations:
462 // Asynchronously send a message multiple times, once per second
463 template <class AsyncWriteStream, class T, class WriteHandler>
464 auto async_write_messages(
465 AsyncWriteStream& stream,
467 std::size_t repeat_count,
468 WriteHandler&& handler) ->
469 typename net::async_result<
470 typename std::decay<WriteHandler>::type,
471 void(error_code)>::return_type
473 using handler_type = typename net::async_completion<WriteHandler, void(error_code)>::completion_handler_type;
474 using base_type = stable_async_base<handler_type, typename AsyncWriteStream::executor_type>;
476 struct op : base_type, boost::asio::coroutine
478 // This object must have a stable address
479 struct temporary_data
481 // Although std::string is in theory movable, most implementations
482 // use a "small buffer optimization" which means that we might
483 // be submitting a buffer to the write operation and then
484 // moving the string, invalidating the buffer. To prevent
485 // undefined behavior we store the string object itself at
486 // a stable location.
487 std::string const message;
489 net::steady_timer timer;
491 temporary_data(std::string message_, net::io_context& ctx)
492 : message(std::move(message_))
498 AsyncWriteStream& stream_;
499 std::size_t repeats_;
500 temporary_data& data_;
502 op(AsyncWriteStream& stream, std::size_t repeats, std::string message, handler_type& handler)
503 : base_type(std::move(handler), stream.get_executor())
506 , data_(allocate_stable<temporary_data>(*this, std::move(message), stream.get_executor().context()))
508 (*this)(); // start the operation
511 // Including this file provides the keywords for macro-based coroutines
512 #include <boost/asio/yield.hpp>
514 void operator()(error_code ec = {}, std::size_t = 0)
518 // If repeats starts at 0 then we must complete immediately. But
519 // we can't call the final handler from inside the initiating
520 // function, so we post our intermediate handler first. We use
521 // net::async_write with an empty buffer instead of calling
522 // net::post to avoid an extra function template instantiation, to
523 // keep compile times lower and make the resulting executable smaller.
524 yield net::async_write(stream_, net::const_buffer{}, std::move(*this));
525 while(! ec && repeats_-- > 0)
527 // Send the string. We construct a `const_buffer` here to guarantee
528 // that we do not create an additional function template instantation
529 // of net::async_write, since we already instantiated it above for
530 // net::const_buffer.
532 yield net::async_write(stream_,
533 net::const_buffer(net::buffer(data_.message)), std::move(*this));
537 // Set the timer and wait
538 data_.timer.expires_after(std::chrono::seconds(1));
539 yield data_.timer.async_wait(std::move(*this));
543 // The base class destroys the temporary data automatically,
544 // before invoking the final completion handler
545 this->complete_now(ec);
548 // Including this file undefines the macros for the coroutines
549 #include <boost/asio/unyield.hpp>
552 net::async_completion<WriteHandler, void(error_code)> completion(handler);
553 std::ostringstream os;
555 op(stream, repeat_count, os.str(), completion.completion_handler);
556 return completion.result.get();
561 @tparam Handler The type of the completion handler to store.
562 This type must meet the requirements of <em>CompletionHandler</em>.
564 @tparam Executor1 The type of the executor used when the handler has no
565 associated executor. An instance of this type must be provided upon
566 construction. The implementation will maintain an executor work guard
567 and a copy of this instance.
569 @tparam Allocator The allocator type to use if the handler does not
570 have an associated allocator. If this parameter is omitted, then
571 `std::allocator<void>` will be used. If the specified allocator is
572 not default constructible, an instance of the type must be provided
575 @see allocate_stable, async_base
580 class Allocator = std::allocator<void>
582 class stable_async_base
584 Handler, Executor1, Allocator>
586 detail::stable_base* list_ = nullptr;
589 before_invoke_hook() override
591 detail::stable_base::destroy_list(list_);
597 @param handler The final completion handler.
598 The type of this object must meet the requirements of <em>CompletionHandler</em>.
599 The implementation takes ownership of the handler by performing a decay-copy.
601 @param ex1 The executor associated with the implied I/O object
602 target of the operation. The implementation shall maintain an
603 executor work guard for the lifetime of the operation, or until
604 the final completion handler is invoked, whichever is shorter.
606 @param alloc The allocator to be associated with objects
607 derived from this class. If `Allocator` is default-constructible,
608 this parameter is optional and may be omitted.
610 #if BOOST_BEAST_DOXYGEN
611 template<class Handler>
614 Executor1 const& ex1,
615 Allocator const& alloc = Allocator());
619 class = typename std::enable_if<
620 ! std::is_same<typename
621 std::decay<Handler_>::type,
627 Executor1 const& ex1)
629 Handler, Executor1, Allocator>(
630 std::forward<Handler_>(handler), ex1)
634 template<class Handler_>
637 Executor1 const& ex1,
638 Allocator const& alloc)
640 Handler, Executor1, Allocator>(
641 std::forward<Handler_>(handler), ex1, alloc)
647 stable_async_base(stable_async_base&& other)
648 : async_base<Handler, Executor1, Allocator>(
650 , list_(boost::exchange(other.list_, nullptr))
656 If the completion handler was not invoked, then any
657 state objects allocated with @ref allocate_stable will
662 detail::stable_base::destroy_list(list_);
665 /** Allocate a temporary object to hold operation state.
667 The object will be destroyed just before the completion
668 handler is invoked, or when the operation base is destroyed.
680 Handler_, Executor1_, Allocator_>& base,
684 /** Allocate a temporary object to hold stable asynchronous operation state.
686 The object will be destroyed just before the completion
687 handler is invoked, or when the base is destroyed.
689 @tparam State The type of object to allocate.
691 @param base The helper to allocate from.
693 @param args An optional list of parameters to forward to the
694 constructor of the object being allocated.
696 @see stable_async_base
707 Handler, Executor1, Allocator>& base,
713 #include <boost/beast/core/impl/async_base.hpp>