123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785 |
- //
- // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
- //
- // Distributed under the Boost Software License, Version 1.0. (See accompanying
- // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
- //
- // Official repository: https://github.com/boostorg/beast
- //
- #ifndef BOOST_BEAST_CORE_ASYNC_BASE_HPP
- #define BOOST_BEAST_CORE_ASYNC_BASE_HPP
- #include <boost/beast/core/detail/config.hpp>
- #include <boost/beast/core/detail/allocator.hpp>
- #include <boost/beast/core/detail/async_base.hpp>
- #include <boost/beast/core/detail/filtering_cancellation_slot.hpp>
- #include <boost/beast/core/detail/work_guard.hpp>
- #include <boost/asio/associated_allocator.hpp>
- #include <boost/asio/associated_cancellation_slot.hpp>
- #include <boost/asio/associated_executor.hpp>
- #include <boost/asio/associated_immediate_executor.hpp>
- #include <boost/asio/bind_executor.hpp>
- #include <boost/asio/handler_continuation_hook.hpp>
- #include <boost/asio/dispatch.hpp>
- #include <boost/asio/post.hpp>
- #include <boost/asio/prepend.hpp>
- #include <boost/core/exchange.hpp>
- #include <boost/core/empty_value.hpp>
- #include <utility>
- namespace boost {
- namespace beast {
- /** Base class to assist writing composed operations.
- A function object submitted to intermediate initiating functions during
- a composed operation may derive from this type to inherit all of the
- boilerplate to forward the executor, allocator, and legacy customization
- points associated with the completion handler invoked at the end of the
- composed operation.
- The composed operation must be typical; that is, associated with one
- executor of an I/O object, and invoking a caller-provided completion
- handler when the operation is finished. Classes derived from
- @ref async_base will acquire these properties:
- @li Ownership of the final completion handler provided upon construction.
- @li If the final handler has an associated allocator, this allocator will
- be propagated to the composed operation subclass. Otherwise, the
- associated allocator will be the type specified in the allocator
- template parameter, or the default of `std::allocator<void>` if the
- parameter is omitted.
- @li If the final handler has an associated executor, then it will be used
- as the executor associated with the composed operation. Otherwise,
- the specified `Executor1` will be the type of executor associated
- with the composed operation.
- @li An instance of `net::executor_work_guard` for the instance of `Executor1`
- shall be maintained until either the final handler is invoked, or the
- operation base is destroyed, whichever comes first.
- @li Calls to the legacy customization point `asio_handler_is_continuation`
- which use argument-dependent lookup, will be forwarded to the
- legacy customization points associated with the handler.
- @par Example
- The following code demonstrates how @ref async_base may be be used to
- assist authoring an asynchronous initiating function, by providing all of
- the boilerplate to manage the final completion handler in a way that
- maintains the allocator and executor associations:
- @code
- // Asynchronously read into a buffer until the buffer is full, or an error occurs
- template<class AsyncReadStream, class ReadHandler>
- typename net::async_result<ReadHandler, void(error_code, std::size_t)>::return_type
- async_read(AsyncReadStream& stream, net::mutable_buffer buffer, ReadHandler&& handler)
- {
- using handler_type = BOOST_ASIO_HANDLER_TYPE(ReadHandler, void(error_code, std::size_t));
- using base_type = async_base<handler_type, typename AsyncReadStream::executor_type>;
- struct op : base_type
- {
- AsyncReadStream& stream_;
- net::mutable_buffer buffer_;
- std::size_t total_bytes_transferred_;
- op(
- AsyncReadStream& stream,
- net::mutable_buffer buffer,
- handler_type& handler)
- : base_type(std::move(handler), stream.get_executor())
- , stream_(stream)
- , buffer_(buffer)
- , total_bytes_transferred_(0)
- {
- (*this)({}, 0, false); // start the operation
- }
- void operator()(error_code ec, std::size_t bytes_transferred, bool is_continuation = true)
- {
- // Adjust the count of bytes and advance our buffer
- total_bytes_transferred_ += bytes_transferred;
- buffer_ = buffer_ + bytes_transferred;
- // Keep reading until buffer is full or an error occurs
- if(! ec && buffer_.size() > 0)
- return stream_.async_read_some(buffer_, std::move(*this));
- // Call the completion handler with the result. If `is_continuation` is
- // false, which happens on the first time through this function, then
- // `net::post` will be used to call the completion handler, otherwise
- // the completion handler will be invoked directly.
- this->complete(is_continuation, ec, total_bytes_transferred_);
- }
- };
- net::async_completion<ReadHandler, void(error_code, std::size_t)> init{handler};
- op(stream, buffer, init.completion_handler);
- return init.result.get();
- }
- @endcode
- Data members of composed operations implemented as completion handlers
- do not have stable addresses, as the composed operation object is move
- constructed upon each call to an initiating function. For most operations
- this is not a problem. For complex operations requiring stable temporary
- storage, the class @ref stable_async_base is provided which offers
- additional functionality:
- @li The free function @ref allocate_stable may be used to allocate
- one or more temporary objects associated with the composed operation.
- @li Memory for stable temporary objects is allocated using the allocator
- associated with the composed operation.
- @li Stable temporary objects are automatically destroyed, and the memory
- freed using the associated allocator, either before the final completion
- handler is invoked (a Networking requirement) or when the composed operation
- is destroyed, whichever occurs first.
- @par Temporary Storage Example
- The following example demonstrates how a composed operation may store a
- temporary object.
- @code
- @endcode
- @tparam Handler The type of the completion handler to store.
- This type must meet the requirements of <em>CompletionHandler</em>.
- @tparam Executor1 The type of the executor used when the handler has no
- associated executor. An instance of this type must be provided upon
- construction. The implementation will maintain an executor work guard
- and a copy of this instance.
- @tparam Allocator The allocator type to use if the handler does not
- have an associated allocator. If this parameter is omitted, then
- `std::allocator<void>` will be used. If the specified allocator is
- not default constructible, an instance of the type must be provided
- upon construction.
- @see stable_async_base
- */
- template<
- class Handler,
- class Executor1,
- class Allocator = std::allocator<void>
- >
- class async_base
- #if ! BOOST_BEAST_DOXYGEN
- : private boost::empty_value<Allocator>
- #endif
- {
- static_assert(
- net::is_executor<Executor1>::value || net::execution::is_executor<Executor1>::value,
- "Executor type requirements not met");
- Handler h_;
- detail::select_work_guard_t<Executor1> wg1_;
- net::cancellation_type act_{net::cancellation_type::terminal};
- public:
- /** The type of executor associated with this object.
- If a class derived from @ref boost::beast::async_base is a completion
- handler, then the associated executor of the derived class will
- be this type.
- */
- using executor_type =
- #if BOOST_BEAST_DOXYGEN
- __implementation_defined__;
- #else
- typename
- net::associated_executor<
- Handler,
- typename detail::select_work_guard_t<Executor1>::executor_type
- >::type;
- #endif
- /** The type of the immediate executor associated with this object.
- If a class derived from @ref boost::beast::async_base is a completion
- handler, then the associated immediage executor of the derived class will
- be this type.
- */
- using immediate_executor_type =
- #if BOOST_BEAST_DOXYGEN
- __implementation_defined__;
- #else
- typename
- net::associated_immediate_executor<
- Handler,
- typename detail::select_work_guard_t<Executor1>::executor_type
- >::type;
- #endif
- private:
- virtual
- void
- before_invoke_hook()
- {
- }
- public:
- /** Constructor
- @param handler The final completion handler.
- The type of this object must meet the requirements of <em>CompletionHandler</em>.
- The implementation takes ownership of the handler by performing a decay-copy.
- @param ex1 The executor associated with the implied I/O object
- target of the operation. The implementation shall maintain an
- executor work guard for the lifetime of the operation, or until
- the final completion handler is invoked, whichever is shorter.
- @param alloc The allocator to be associated with objects
- derived from this class. If `Allocator` is default-constructible,
- this parameter is optional and may be omitted.
- */
- #if BOOST_BEAST_DOXYGEN
- template<class Handler_>
- async_base(
- Handler&& handler,
- Executor1 const& ex1,
- Allocator const& alloc = Allocator());
- #else
- template<
- class Handler_,
- class = typename std::enable_if<
- ! std::is_same<typename
- std::decay<Handler_>::type,
- async_base
- >::value>::type
- >
- async_base(
- Handler_&& handler,
- Executor1 const& ex1)
- : h_(std::forward<Handler_>(handler))
- , wg1_(detail::make_work_guard(ex1))
- {
- }
- template<class Handler_>
- async_base(
- Handler_&& handler,
- Executor1 const& ex1,
- Allocator const& alloc)
- : boost::empty_value<Allocator>(
- boost::empty_init_t{}, alloc)
- , h_(std::forward<Handler_>(handler))
- , wg1_(ex1)
- {
- }
- #endif
- /// Move Constructor
- async_base(async_base&& other) = default;
- virtual ~async_base() = default;
- async_base(async_base const&) = delete;
- async_base& operator=(async_base const&) = delete;
- /** The type of allocator associated with this object.
- If a class derived from @ref boost::beast::async_base is a completion
- handler, then the associated allocator of the derived class will
- be this type.
- */
- using allocator_type =
- net::associated_allocator_t<Handler, Allocator>;
- /** Returns the allocator associated with this object.
- If a class derived from @ref boost::beast::async_base is a completion
- handler, then the object returned from this function will be used
- as the associated allocator of the derived class.
- */
- allocator_type
- get_allocator() const noexcept
- {
- return net::get_associated_allocator(h_,
- boost::empty_value<Allocator>::get());
- }
- /** Returns the executor associated with this object.
- If a class derived from @ref boost::beast::async_base is a completion
- handler, then the object returned from this function will be used
- as the associated executor of the derived class.
- */
- executor_type
- get_executor() const noexcept
- {
- return net::get_associated_executor(
- h_, wg1_.get_executor());
- }
- /** Returns the immediate executor associated with this handler.
- If the handler has none it returns asios default immediate
- executor based on the executor of the object.
- If a class derived from @ref boost::beast::async_base is a completion
- handler, then the object returned from this function will be used
- as the associated immediate executor of the derived class.
- */
- immediate_executor_type
- get_immediate_executor() const noexcept
- {
- return net::get_associated_immediate_executor(
- h_, wg1_.get_executor());
- }
- /** The type of cancellation_slot associated with this object.
- If a class derived from @ref async_base is a completion
- handler, then the associated cancellation_slot of the
- derived class will be this type.
- The default type is a filtering cancellation slot,
- that only allows terminal cancellation.
- */
- using cancellation_slot_type =
- beast::detail::filtering_cancellation_slot<net::associated_cancellation_slot_t<Handler>>;
- /** Returns the cancellation_slot associated with this object.
- If a class derived from @ref async_base is a completion
- handler, then the object returned from this function will be used
- as the associated cancellation_slot of the derived class.
- */
- cancellation_slot_type
- get_cancellation_slot() const noexcept
- {
- return cancellation_slot_type(act_, net::get_associated_cancellation_slot(h_,
- net::cancellation_slot()));
- }
- /// Set the allowed cancellation types, default is `terminal`.
- void set_allowed_cancellation(
- net::cancellation_type allowed_cancellation_types = net::cancellation_type::terminal)
- {
- act_ = allowed_cancellation_types;
- }
- /// Returns the handler associated with this object
- Handler const&
- handler() const noexcept
- {
- return h_;
- }
- /** Returns ownership of the handler associated with this object
- This function is used to transfer ownership of the handler to
- the caller, by move-construction. After the move, the only
- valid operations on the base object are move construction and
- destruction.
- */
- Handler
- release_handler()
- {
- return std::move(h_);
- }
- /** Invoke the final completion handler, maybe using post.
- This invokes the final completion handler with the specified
- arguments forwarded. It is undefined to call either of
- @ref boost::beast::async_base::complete or
- @ref boost::beast::async_base::complete_now more than once.
- Any temporary objects allocated with @ref boost::beast::allocate_stable will
- be automatically destroyed before the final completion handler
- is invoked.
- @param is_continuation If this value is `false`, then the
- handler will be submitted to the to the immediate executor using
- `net::dispatch`. If the handler has no immediate executor,
- this will submit to the executor via `net::post`.
- Otherwise the handler will be invoked as if by calling
- @ref boost::beast::async_base::complete_now.
- @param args A list of optional parameters to invoke the handler
- with. The completion handler must be invocable with the parameter
- list, or else a compilation error will result.
- */
- template<class... Args>
- void
- complete(bool is_continuation, Args&&... args)
- {
- this->before_invoke_hook();
- if(! is_continuation)
- {
- auto const ex = this->get_immediate_executor();
- net::dispatch(
- ex,
- net::prepend(std::move(h_), std::forward<Args>(args)...));
- wg1_.reset();
- }
- else
- {
- wg1_.reset();
- h_(std::forward<Args>(args)...);
- }
- }
- /** Invoke the final completion handler.
- This invokes the final completion handler with the specified
- arguments forwarded. It is undefined to call either of
- @ref boost::beast::async_base::complete or @ref boost::beast::async_base::complete_now more than once.
- Any temporary objects allocated with @ref boost::beast::allocate_stable will
- be automatically destroyed before the final completion handler
- is invoked.
- @param args A list of optional parameters to invoke the handler
- with. The completion handler must be invocable with the parameter
- list, or else a compilation error will result.
- */
- template<class... Args>
- void
- complete_now(Args&&... args)
- {
- this->before_invoke_hook();
- wg1_.reset();
- h_(std::forward<Args>(args)...);
- }
- #if ! BOOST_BEAST_DOXYGEN
- Handler*
- get_legacy_handler_pointer() noexcept
- {
- return std::addressof(h_);
- }
- #endif
- };
- //------------------------------------------------------------------------------
- /** Base class to provide completion handler boilerplate for composed operations.
- A function object submitted to intermediate initiating functions during
- a composed operation may derive from this type to inherit all of the
- boilerplate to forward the executor, allocator, and legacy customization
- points associated with the completion handler invoked at the end of the
- composed operation.
- The composed operation must be typical; that is, associated with one
- executor of an I/O object, and invoking a caller-provided completion
- handler when the operation is finished. Classes derived from
- @ref async_base will acquire these properties:
- @li Ownership of the final completion handler provided upon construction.
- @li If the final handler has an associated allocator, this allocator will
- be propagated to the composed operation subclass. Otherwise, the
- associated allocator will be the type specified in the allocator
- template parameter, or the default of `std::allocator<void>` if the
- parameter is omitted.
- @li If the final handler has an associated executor, then it will be used
- as the executor associated with the composed operation. Otherwise,
- the specified `Executor1` will be the type of executor associated
- with the composed operation.
- @li An instance of `net::executor_work_guard` for the instance of `Executor1`
- shall be maintained until either the final handler is invoked, or the
- operation base is destroyed, whichever comes first.
- Data members of composed operations implemented as completion handlers
- do not have stable addresses, as the composed operation object is move
- constructed upon each call to an initiating function. For most operations
- this is not a problem. For complex operations requiring stable temporary
- storage, the class @ref stable_async_base is provided which offers
- additional functionality:
- @li The free function @ref beast::allocate_stable may be used to allocate
- one or more temporary objects associated with the composed operation.
- @li Memory for stable temporary objects is allocated using the allocator
- associated with the composed operation.
- @li Stable temporary objects are automatically destroyed, and the memory
- freed using the associated allocator, either before the final completion
- handler is invoked (a Networking requirement) or when the composed operation
- is destroyed, whichever occurs first.
- @par Example
- The following code demonstrates how @ref stable_async_base may be be used to
- assist authoring an asynchronous initiating function, by providing all of
- the boilerplate to manage the final completion handler in a way that maintains
- the allocator and executor associations. Furthermore, the operation shown
- allocates temporary memory using @ref beast::allocate_stable for the timer and
- message, whose addresses must not change between intermediate operations:
- @code
- // Asynchronously send a message multiple times, once per second
- template <class AsyncWriteStream, class T, class WriteHandler>
- auto async_write_messages(
- AsyncWriteStream& stream,
- T const& message,
- std::size_t repeat_count,
- WriteHandler&& handler) ->
- typename net::async_result<
- typename std::decay<WriteHandler>::type,
- void(error_code)>::return_type
- {
- using handler_type = typename net::async_completion<WriteHandler, void(error_code)>::completion_handler_type;
- using base_type = stable_async_base<handler_type, typename AsyncWriteStream::executor_type>;
- struct op : base_type, boost::asio::coroutine
- {
- // This object must have a stable address
- struct temporary_data
- {
- // Although std::string is in theory movable, most implementations
- // use a "small buffer optimization" which means that we might
- // be submitting a buffer to the write operation and then
- // moving the string, invalidating the buffer. To prevent
- // undefined behavior we store the string object itself at
- // a stable location.
- std::string const message;
- net::steady_timer timer;
- temporary_data(std::string message_, net::io_context& ctx)
- : message(std::move(message_))
- , timer(ctx)
- {
- }
- };
- AsyncWriteStream& stream_;
- std::size_t repeats_;
- temporary_data& data_;
- op(AsyncWriteStream& stream, std::size_t repeats, std::string message, handler_type& handler)
- : base_type(std::move(handler), stream.get_executor())
- , stream_(stream)
- , repeats_(repeats)
- , data_(allocate_stable<temporary_data>(*this, std::move(message), stream.get_executor().context()))
- {
- (*this)(); // start the operation
- }
- // Including this file provides the keywords for macro-based coroutines
- #include <boost/asio/yield.hpp>
- void operator()(error_code ec = {}, std::size_t = 0)
- {
- reenter(*this)
- {
- // If repeats starts at 0 then we must complete immediately. But
- // we can't call the final handler from inside the initiating
- // function, so we post our intermediate handler first. We use
- // net::async_write with an empty buffer instead of calling
- // net::post to avoid an extra function template instantiation, to
- // keep compile times lower and make the resulting executable smaller.
- yield net::async_write(stream_, net::const_buffer{}, std::move(*this));
- while(! ec && repeats_-- > 0)
- {
- // Send the string. We construct a `const_buffer` here to guarantee
- // that we do not create an additional function template instantation
- // of net::async_write, since we already instantiated it above for
- // net::const_buffer.
- yield net::async_write(stream_,
- net::const_buffer(net::buffer(data_.message)), std::move(*this));
- if(ec)
- break;
- // Set the timer and wait
- data_.timer.expires_after(std::chrono::seconds(1));
- yield data_.timer.async_wait(std::move(*this));
- }
- }
- // The base class destroys the temporary data automatically,
- // before invoking the final completion handler
- this->complete_now(ec);
- }
- // Including this file undefines the macros for the coroutines
- #include <boost/asio/unyield.hpp>
- };
- net::async_completion<WriteHandler, void(error_code)> completion(handler);
- std::ostringstream os;
- os << message;
- op(stream, repeat_count, os.str(), completion.completion_handler);
- return completion.result.get();
- }
- @endcode
- @tparam Handler The type of the completion handler to store.
- This type must meet the requirements of <em>CompletionHandler</em>.
- @tparam Executor1 The type of the executor used when the handler has no
- associated executor. An instance of this type must be provided upon
- construction. The implementation will maintain an executor work guard
- and a copy of this instance.
- @tparam Allocator The allocator type to use if the handler does not
- have an associated allocator. If this parameter is omitted, then
- `std::allocator<void>` will be used. If the specified allocator is
- not default constructible, an instance of the type must be provided
- upon construction.
- @see allocate_stable, async_base
- */
- template<
- class Handler,
- class Executor1,
- class Allocator = std::allocator<void>
- >
- class stable_async_base
- : public async_base<
- Handler, Executor1, Allocator>
- {
- detail::stable_base* list_ = nullptr;
- void
- before_invoke_hook() override
- {
- detail::stable_base::destroy_list(list_);
- }
- public:
- /** Constructor
- @param handler The final completion handler.
- The type of this object must meet the requirements of <em>CompletionHandler</em>.
- The implementation takes ownership of the handler by performing a decay-copy.
- @param ex1 The executor associated with the implied I/O object
- target of the operation. The implementation shall maintain an
- executor work guard for the lifetime of the operation, or until
- the final completion handler is invoked, whichever is shorter.
- @param alloc The allocator to be associated with objects
- derived from this class. If `Allocator` is default-constructible,
- this parameter is optional and may be omitted.
- */
- #if BOOST_BEAST_DOXYGEN
- template<class Handler>
- stable_async_base(
- Handler&& handler,
- Executor1 const& ex1,
- Allocator const& alloc = Allocator());
- #else
- template<
- class Handler_,
- class = typename std::enable_if<
- ! std::is_same<typename
- std::decay<Handler_>::type,
- stable_async_base
- >::value>::type
- >
- stable_async_base(
- Handler_&& handler,
- Executor1 const& ex1)
- : async_base<
- Handler, Executor1, Allocator>(
- std::forward<Handler_>(handler), ex1)
- {
- }
- template<class Handler_>
- stable_async_base(
- Handler_&& handler,
- Executor1 const& ex1,
- Allocator const& alloc)
- : async_base<
- Handler, Executor1, Allocator>(
- std::forward<Handler_>(handler), ex1, alloc)
- {
- }
- #endif
- /// Move Constructor
- stable_async_base(stable_async_base&& other)
- : async_base<Handler, Executor1, Allocator>(
- std::move(other))
- , list_(boost::exchange(other.list_, nullptr))
- {
- }
- /** Destructor
- If the completion handler was not invoked, then any
- state objects allocated with @ref allocate_stable will
- be destroyed here.
- */
- ~stable_async_base()
- {
- detail::stable_base::destroy_list(list_);
- }
- /** Allocate a temporary object to hold operation state.
- The object will be destroyed just before the completion
- handler is invoked, or when the operation base is destroyed.
- */
- template<
- class State,
- class Handler_,
- class Executor1_,
- class Allocator_,
- class... Args>
- friend
- State&
- allocate_stable(
- stable_async_base<
- Handler_, Executor1_, Allocator_>& base,
- Args&&... args);
- };
- /** Allocate a temporary object to hold stable asynchronous operation state.
- The object will be destroyed just before the completion
- handler is invoked, or when the base is destroyed.
- @tparam State The type of object to allocate.
- @param base The helper to allocate from.
- @param args An optional list of parameters to forward to the
- constructor of the object being allocated.
- @see stable_async_base
- */
- template<
- class State,
- class Handler,
- class Executor1,
- class Allocator,
- class... Args>
- State&
- allocate_stable(
- stable_async_base<
- Handler, Executor1, Allocator>& base,
- Args&&... args);
- } // beast
- } // boost
- #include <boost/beast/core/impl/async_base.hpp>
- #endif
|