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/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>
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 || net::execution::is_executor<Executor1>::value,
186 "Executor type requirements not met");
189 detail::select_work_guard_t<Executor1> wg1_;
192 /** The type of executor associated with this object.
194 If a class derived from @ref async_base is a completion
195 handler, then the associated executor of the derived class will
198 using executor_type =
199 #if BOOST_BEAST_DOXYGEN
200 __implementation_defined__;
203 net::associated_executor<
205 typename detail::select_work_guard_t<Executor1>::executor_type
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.
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.
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.
233 #if BOOST_BEAST_DOXYGEN
234 template<class Handler_>
237 Executor1 const& ex1,
238 Allocator const& alloc = Allocator());
242 class = typename std::enable_if<
243 ! std::is_same<typename
244 std::decay<Handler_>::type,
250 Executor1 const& ex1)
251 : h_(std::forward<Handler_>(handler))
252 , wg1_(detail::make_work_guard(ex1))
256 template<class 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))
270 async_base(async_base&& other) = default;
272 virtual ~async_base() = default;
273 async_base(async_base const&) = delete;
274 async_base& operator=(async_base const&) = delete;
276 /** The type of allocator associated with this object.
278 If a class derived from @ref async_base is a completion
279 handler, then the associated allocator of the derived class will
282 using allocator_type =
283 net::associated_allocator_t<Handler, Allocator>;
285 /** Returns the allocator associated with this object.
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.
292 get_allocator() const noexcept
294 return net::get_associated_allocator(h_,
295 boost::empty_value<Allocator>::get());
298 /** Returns the executor associated with this object.
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.
305 get_executor() const noexcept
307 return net::get_associated_executor(
308 h_, wg1_.get_executor());
311 /// Returns the handler associated with this object
313 handler() const noexcept
318 /** Returns ownership of the handler associated with this object
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
328 return std::move(h_);
331 /** Invoke the final completion handler, maybe using post.
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.
337 Any temporary objects allocated with @ref beast::allocate_stable will
338 be automatically destroyed before the final completion handler
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
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.
350 template<class... Args>
352 complete(bool is_continuation, Args&&... args)
354 this->before_invoke_hook();
355 if(! is_continuation)
357 auto const ex = get_executor();
358 net::post(net::bind_executor(
360 beast::bind_front_handler(
362 std::forward<Args>(args)...)));
368 h_(std::forward<Args>(args)...);
372 /** Invoke the final completion handler.
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.
378 Any temporary objects allocated with @ref beast::allocate_stable will
379 be automatically destroyed before the final completion handler
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.
386 template<class... Args>
388 complete_now(Args&&... args)
390 this->before_invoke_hook();
392 h_(std::forward<Args>(args)...);
395 #if ! BOOST_BEAST_DOXYGEN
397 get_legacy_handler_pointer() noexcept
399 return std::addressof(h_);
404 //------------------------------------------------------------------------------
406 /** Base class to provide completion handler boilerplate for composed operations.
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
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:
419 @li Ownership of the final completion handler provided upon construction.
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.
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.
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.
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.
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:
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.
454 @li Memory for stable temporary objects is allocated using the allocator
455 associated with the composed operation.
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.
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:
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,
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
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>;
487 struct op : base_type, boost::asio::coroutine
489 // This object must have a stable address
490 struct temporary_data
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;
500 net::steady_timer timer;
502 temporary_data(std::string message_, net::io_context& ctx)
503 : message(std::move(message_))
509 AsyncWriteStream& stream_;
510 std::size_t repeats_;
511 temporary_data& data_;
513 op(AsyncWriteStream& stream, std::size_t repeats, std::string message, handler_type& handler)
514 : base_type(std::move(handler), stream.get_executor())
517 , data_(allocate_stable<temporary_data>(*this, std::move(message), stream.get_executor().context()))
519 (*this)(); // start the operation
522 // Including this file provides the keywords for macro-based coroutines
523 #include <boost/asio/yield.hpp>
525 void operator()(error_code ec = {}, std::size_t = 0)
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)
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.
543 yield net::async_write(stream_,
544 net::const_buffer(net::buffer(data_.message)), std::move(*this));
548 // Set the timer and wait
549 data_.timer.expires_after(std::chrono::seconds(1));
550 yield data_.timer.async_wait(std::move(*this));
554 // The base class destroys the temporary data automatically,
555 // before invoking the final completion handler
556 this->complete_now(ec);
559 // Including this file undefines the macros for the coroutines
560 #include <boost/asio/unyield.hpp>
563 net::async_completion<WriteHandler, void(error_code)> completion(handler);
564 std::ostringstream os;
566 op(stream, repeat_count, os.str(), completion.completion_handler);
567 return completion.result.get();
572 @tparam Handler The type of the completion handler to store.
573 This type must meet the requirements of <em>CompletionHandler</em>.
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.
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
586 @see allocate_stable, async_base
591 class Allocator = std::allocator<void>
593 class stable_async_base
595 Handler, Executor1, Allocator>
597 detail::stable_base* list_ = nullptr;
600 before_invoke_hook() override
602 detail::stable_base::destroy_list(list_);
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.
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.
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.
621 #if BOOST_BEAST_DOXYGEN
622 template<class Handler>
625 Executor1 const& ex1,
626 Allocator const& alloc = Allocator());
630 class = typename std::enable_if<
631 ! std::is_same<typename
632 std::decay<Handler_>::type,
638 Executor1 const& ex1)
640 Handler, Executor1, Allocator>(
641 std::forward<Handler_>(handler), ex1)
645 template<class Handler_>
648 Executor1 const& ex1,
649 Allocator const& alloc)
651 Handler, Executor1, Allocator>(
652 std::forward<Handler_>(handler), ex1, alloc)
658 stable_async_base(stable_async_base&& other)
659 : async_base<Handler, Executor1, Allocator>(
661 , list_(boost::exchange(other.list_, nullptr))
667 If the completion handler was not invoked, then any
668 state objects allocated with @ref allocate_stable will
673 detail::stable_base::destroy_list(list_);
676 /** Allocate a temporary object to hold operation state.
678 The object will be destroyed just before the completion
679 handler is invoked, or when the operation base is destroyed.
691 Handler_, Executor1_, Allocator_>& base,
695 /** Allocate a temporary object to hold stable asynchronous operation state.
697 The object will be destroyed just before the completion
698 handler is invoked, or when the base is destroyed.
700 @tparam State The type of object to allocate.
702 @param base The helper to allocate from.
704 @param args An optional list of parameters to forward to the
705 constructor of the object being allocated.
707 @see stable_async_base
718 Handler, Executor1, Allocator>& base,
724 #include <boost/beast/core/impl/async_base.hpp>