diff --git a/CHANGELOG.md b/CHANGELOG.md index b48a6745..d00f0da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Version 202 * Tidy up basic_stream_socket docs * Refactor async_op_base * Use async_op_base +* async_op_base is a public interface -------------------------------------------------------------------------------- diff --git a/doc/qbk/03_core/5_composed.qbk b/doc/qbk/03_core/5_composed.qbk index b24e4581..eca3836f 100644 --- a/doc/qbk/03_core/5_composed.qbk +++ b/doc/qbk/03_core/5_composed.qbk @@ -38,6 +38,27 @@ composed operations: [table Asynchronous Helpers [[Name][Description]] +[[ + [link beast.ref.boost__beast__async_op_base `async_op_base`] + [link beast.ref.boost__beast__stable_async_op_base `stable_async_op_base`] +][ + This class is designed to be used as a base class when authoriing + composed asynchronous operations expressed as an intermediate + completion handler. This eliminates the need for the extensive + boilerplate to propgate the associated executor, associated + allocator, and legacy completion handler hooks. +]] +[[ + [link beast.ref.boost__beast__allocate_stable `allocate_stable`] +][ + For composed operation algorithms which need stable storage for + temporary objects, this function may be used. Memory for the + stable storage is allocated using the allocator associated with + the final completion handler. The implementation automatically + destroys the temporary object before the final completion handler + is invoked, or when the intermediate completion handler is + destroyed. +]] [[ [link beast.ref.boost__beast__bind_handler `bind_handler`] ][ diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 1f2e1f7d..67078606 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -177,6 +177,7 @@ Classes + async_op_base basic_flat_buffer basic_multi_buffer basic_stream_socket @@ -206,6 +207,7 @@ static_buffer static_buffer_base static_string + stable_async_op_base stream_socket string_param string_view @@ -220,6 +222,7 @@ Functions + allocate_stable async_connect bind_back_handler bind_front_handler diff --git a/include/boost/beast/_experimental/core/impl/flat_stream.hpp b/include/boost/beast/_experimental/core/impl/flat_stream.hpp index 90656f6c..b83b9266 100644 --- a/include/boost/beast/_experimental/core/impl/flat_stream.hpp +++ b/include/boost/beast/_experimental/core/impl/flat_stream.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST_CORE_IMPL_FLAT_STREAM_HPP #include -#include +#include #include #include #include @@ -24,7 +24,7 @@ namespace beast { template template class flat_stream::write_op - : public detail::async_op_base> , public net::coroutine @@ -59,7 +59,7 @@ public: flat_stream& s, ConstBufferSequence const& b, Handler_&& h) - : detail::async_op_base>( s.get_executor(), std::forward(h)) diff --git a/include/boost/beast/_experimental/http/impl/icy_stream.hpp b/include/boost/beast/_experimental/http/impl/icy_stream.hpp index 5d314684..25e024b2 100644 --- a/include/boost/beast/_experimental/http/impl/icy_stream.hpp +++ b/include/boost/beast/_experimental/http/impl/icy_stream.hpp @@ -11,11 +11,11 @@ #define BOOST_BEAST_CORE_IMPL_ICY_STREAM_HPP #include +#include #include #include #include #include -#include #include #include #include @@ -121,7 +121,7 @@ public: template template class icy_stream::read_op - : public beast::detail::stable_async_op_base> , public net::coroutine { @@ -151,10 +151,10 @@ public: Handler_&& h, icy_stream& s, MutableBufferSequence const& b) - : beast::detail::stable_async_op_base>( s.get_executor(), std::forward(h)) - , d_(beast::detail::allocate_stable(*this, s, b)) + , d_(beast::allocate_stable(*this, s, b)) { (*this)({}, 0); } diff --git a/include/boost/beast/core/async_op_base.hpp b/include/boost/beast/core/async_op_base.hpp new file mode 100644 index 00000000..391f662d --- /dev/null +++ b/include/boost/beast/core/async_op_base.hpp @@ -0,0 +1,760 @@ +// +// Copyright (c) 2018 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_OP_BASE_HPP +#define BOOST_BEAST_CORE_ASYNC_OP_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +/** 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_op_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` 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 points + `asio_handler_invoke`, + `asio_handler_allocate`, + `asio_handler_deallocate`, and + `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_op_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 + BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void (error_code, std::size_t)) + 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_op_base; + + struct read_op : base_type + { + AsyncReadStream& stream_; + net::mutable_buffer buffer_; + std::size_t total_bytes_transferred_; + + read_op( + AsyncReadStream& stream, + net::mutable_buffer buffer, + handler_type& handler) + : base_type(stream.get_executor(), std::move(handler)) + , 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)); + + // If this is first invocation, we have to post to the executor. Otherwise the + // handler would be invoked before the call to async_read returns, which is disallowed. + if(! is_continuation) + { + // Issue a zero-sized read so our handler runs "as-if" posted using net::post(). + // This technique is used to reduce the number of function template instantiations. + return stream_.async_read_some(net::mutable_buffer(buffer_.data(), 0), std::move(*this)); + } + + // Call the completion handler with the result + this->invoke(ec, total_bytes_transferred_); + } + }; + + net::async_completion init{handler}; + read_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_op_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 CompletionHandler. + + @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` will be used. If the specified allocator is + not default constructible, an instance of the type must be provided + upon construction. + + @see @ref stable_async_op_base +*/ +template< + class Handler, + class Executor1, + class Allocator = std::allocator +> +class async_op_base +#if ! BOOST_BEAST_DOXYGEN + : private boost::empty_value +#endif +{ + static_assert( + net::is_executor::value, + "Executor requirements not met"); + + Handler h_; + net::executor_work_guard wg_; + + virtual + void + before_invoke_hook() + { + } + +public: + /// Move Constructor + async_op_base(async_op_base&& other) = default; + + /** The type of allocator associated with this object. + + If a class derived from @ref async_op_base is a completion + handler, then the associated allocator of the derived class will + be this type. + */ + using allocator_type = + net::associated_allocator_t; + + /** The type of executor associated with this object. + + If a class derived from @ref async_op_base is a completion + handler, then the associated executor of the derived class will + be this type. + */ + using executor_type = + net::associated_executor_t; + + /** Returns the allocator associated with this object. + + If a class derived from @ref async_op_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::get()); + } + + /** Returns the executor associated with this object. + + If a class derived from @ref async_op_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_, wg_.get_executor()); + } + + /// 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_); + } + +protected: + /** Constructor + + @param handler The final completion handler. The type of this + object must meet the requirements of CompletionHandler. + + @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 + async_op_base( + Executor1 const& ex1, + Handler&& handler, + Allocator const& alloc = Allocator()); +#else + template< + class Handler_, + class = typename std::enable_if< + ! std::is_same::type, + async_op_base + >::value>::type + > + async_op_base( + Executor1 const& ex1, + Handler_&& handler) + : h_(std::forward(handler)) + , wg_(ex1) + { + } + + template + async_op_base( + Executor1 const& ex1, + Handler_&& handler, + Allocator const& alloc) + : boost::empty_value(alloc) + , h_(std::forward(handler)) + , wg_(ex1) + { + } +#endif + + /** Invoke the final completion handler. + + This invokes the final completion handler with the specified + arguments forwarded. It is undefined to call this member + function more than once. + + Any temporary objects allocated with @ref allocate_stable will + be automatically destroyed before the final completion handler + is invoked. + */ + template + void + invoke(Args&&... args) + { + this->before_invoke_hook(); + wg_.reset(); + h_(std::forward(args)...); + } + +#if ! BOOST_BEAST_DOXYGEN +public: + template< + class Handler_, + class Executor1_, + class Allocator_, + class Function> + friend + void asio_handler_invoke( + Function&& f, + async_op_base< + Handler_, Executor1_, Allocator_>* p); + + friend + void* asio_handler_allocate( + std::size_t size, async_op_base* p) + { + using net::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(p->h_)); + } + + friend + void asio_handler_deallocate( + void* mem, std::size_t size, + async_op_base* p) + { + using net::asio_handler_deallocate; + asio_handler_deallocate(mem, size, + std::addressof(p->h_)); + } + + friend + bool asio_handler_is_continuation( + async_op_base* p) + { + using net::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(p->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_op_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` 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 points + `asio_handler_invoke`, + `asio_handler_allocate`, + `asio_handler_deallocate`, and + `asio_handler_is_continuation`, + which use argument-dependent lookup, will be forwarded to the + legacy customization points associated with the handler. + + 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_op_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 Example + + The following code demonstrates how @ref stable_async_op_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 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 + auto async_write_messages( + AsyncWriteStream& stream, + T const& message, + std::size_t repeat_count, + WriteHandler&& handler) -> + typename net::async_result< + typename std::decay::type, + void(error_code)>::return_type + { + using handler_type = typename net::async_completion::completion_handler_type; + using base_type = stable_async_op_base; + + struct op : base_type + { + // This object must have a stable address + struct temporary_data + { + 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_; + enum { starting, waiting, writing } state_; + + op(AsyncWriteStream& stream, std::size_t repeats, std::string message, handler_type& handler) + : base_type(stream.get_executor(), std::move(handler)) + , stream_(stream) + , repeats_(repeats) + , state_(starting) + , data_(allocate_stable(*this, std::move(message), stream.get_executor().context())) + { + (*this)(); // start the operation + } + + void operator()(error_code ec = {}, std::size_t = 0) + { + if (!ec) + { + switch (state_) + { + case starting: + // 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. + if(repeats_ == 0) + return net::post(std::move(*this)); + + case writing: + if (repeats_ > 0) + { + --repeats_; + state_ = waiting; + data_.timer.expires_after(std::chrono::seconds(1)); + + // Composed operation not yet complete. + return data_.timer.async_wait(std::move(*this)); + } + + // Composed operation complete, continue below. + break; + + case waiting: + // Composed operation not yet complete. + state_ = writing; + return net::async_write(stream_, net::buffer(data_.message), std::move(*this)); + } + } + + // The base class destroys the temporary data automatically, before invoking the final completion handler + this->invoke(ec); + } + }; + + net::async_completion 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 CompletionHandler. + + @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` will be used. If the specified allocator is + not default constructible, an instance of the type must be provided + upon construction. + + @see @ref allocate_stable, @ref async_op_base +*/ +template< + class Handler, + class Executor1, + class Allocator = std::allocator +> +class stable_async_op_base + : public async_op_base< + Handler, Executor1, Allocator> +{ + detail::stable_base* list_ = nullptr; + + void + before_invoke_hook() override + { + detail::stable_base::destroy_list(list_); + } + +public: + /// Move Constructor + stable_async_op_base(stable_async_op_base&& other) + : async_op_base( + std::move(other)) + , list_(boost::exchange(other.list_, nullptr)) + { + } + +protected: + /** Constructor + + @param handler The final completion handler. The type of this + object must meet the requirements of CompletionHandler. + + @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 + stable_async_op_base( + Executor1 const& ex1, + Handler&& handler, + Allocator const& alloc = Allocator()); +#else + template< + class Handler_, + class = typename std::enable_if< + ! std::is_same::type, + stable_async_op_base + >::value>::type + > + stable_async_op_base( + Executor1 const& ex1, + Handler_&& handler) + : async_op_base< + Handler, Executor1, Allocator>( + ex1, std::forward(handler)) + { + } + + template + stable_async_op_base( + Executor1 const& ex1, + Handler_&& handler, + Allocator const& alloc) + : async_op_base< + Handler, Executor1, Allocator>( + ex1, std::forward(handler)) + { + } +#endif + + /** Destructor + + If the completion handler was not invoked, then any + state objects allocated with @ref allocate_stable will + be destroyed here. + */ + ~stable_async_op_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_op_base< + Handler_, Executor1_, Allocator_>& base, + Args&&... args); +}; + +#if ! BOOST_BEAST_DOXYGEN +template< + class Handler, + class Executor1, + class Allocator, + class Function> +void asio_handler_invoke( + Function&& f, + async_op_base< + Handler, Executor1, Allocator>* p) +{ + using net::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(p->h_)); +} +#endif + +/** 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. + + @see @ref stable_async_op_base +*/ +template< + class State, + class Handler, + class Executor1, + class Allocator, + class... Args> +State& +allocate_stable( + stable_async_op_base< + Handler, Executor1, Allocator>& base, + Args&&... args) +{ + struct state; + + using allocator_type = typename stable_async_op_base< + Handler, Executor1, Allocator>::allocator_type; + + using A = typename detail::allocator_traits< + allocator_type>::template rebind_alloc; + + struct state final + : detail::stable_base + , boost::empty_value + { + State value; + + explicit + state( + allocator_type const& alloc, + detail::stable_base*& list, + Args&&... args) + : detail::stable_base(list) + , boost::empty_value( + boost::empty_init_t{}, alloc) + , value{std::forward(args)...} + { + } + + void destroy() override + { + A a(this->get()); + detail::allocator_traits::destroy(a, this); + detail::allocator_traits::deallocate(a, this, 1); + } + }; + + struct deleter + { + allocator_type alloc; + state* ptr; + + ~deleter() + { + if(ptr) + { + A a(alloc); + detail::allocator_traits::deallocate(a, ptr, 1); + } + } + }; + + A a(base.get_allocator()); + deleter d{base.get_allocator(), nullptr}; + d.ptr = detail::allocator_traits::allocate(a, 1); + detail::allocator_traits::construct(a, d.ptr, + d.alloc, + base.list_, + std::forward(args)...); + return boost::exchange(d.ptr, nullptr)->value; +} + +} // beast +} // boost + +#endif diff --git a/include/boost/beast/core/detail/async_op_base.hpp b/include/boost/beast/core/detail/async_op_base.hpp index 1aeeda2a..f35e9c76 100644 --- a/include/boost/beast/core/detail/async_op_base.hpp +++ b/include/boost/beast/core/detail/async_op_base.hpp @@ -10,511 +10,36 @@ #ifndef BOOST_BEAST_CORE_DETAIL_ASYNC_OP_BASE_HPP #define BOOST_BEAST_CORE_DETAIL_ASYNC_OP_BASE_HPP -#include -#include -#include -#include -#include -#include -#include #include -#include -#include namespace boost { namespace beast { namespace detail { -//------------------------------------------------------------------------------ - -/** 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 the executor - of a particular I/O object, and invoking a caller-provided completion handler - when the operation is finished. Specifically, classes derived from - @ref async_op_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` 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 points - `asio_handler_invoke`, - `asio_handler_allocate`, - `asio_handler_deallocate`, and - `asio_handler_is_continuation`, - which use argument-dependent lookup, will be forwarded to the - legacy customization points associated with the handler. - - 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_op_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 CompletionHandler. - - @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` will be used. If the specified allocator is - not default constructible, an instance of the type must be provided - upon construction. - - @see @ref allocate_stable -*/ -template< - class Handler, - class Executor1, - class Allocator = std::allocator -> -class async_op_base -#if ! BOOST_BEAST_DOXYGEN - : private boost::empty_value -#endif +struct stable_base { - static_assert( - net::is_executor::value, - "Executor requirements not met"); - - Handler h_; - net::executor_work_guard wg_; - - virtual + static void - before_invoke_hook() + destroy_list(stable_base*& list) { - } - -public: - /// Move Constructor - async_op_base(async_op_base&& other) = default; - - /** The type of allocator associated with this object. - - If a class derived from @ref async_op_base is a completion - handler, then the associated allocator of the derived class will - be this type. - */ - using allocator_type = - net::associated_allocator_t; - - /** The type of executor associated with this object. - - If a class derived from @ref async_op_base is a completion - handler, then the associated executor of the derived class will - be this type. - */ - using executor_type = - net::associated_executor_t; - - /** Returns the allocator associated with this object. - - If a class derived from @ref async_op_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::get()); - } - - /** Returns the executor associated with this object. - - If a class derived from @ref async_op_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_, wg_.get_executor()); - } - - /// 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_); + while(list) + { + auto next = list->next_; + list->destroy(); + list = next; + } } protected: - /** Constructor - - @param target The target of the operation. This object must - have class type, with a member function named `get_executor` - publicly accessible whose return type meets the requirements - of Executor. - - @param handler The final completion handler. The type of this - object must meet the requirements of CompletionHandler. - - @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 - async_op_base( - Executor1 const& ex1, - Handler&& handler, - Allocator const& alloc = Allocator()); -#else - template< - class Handler_, - class = typename std::enable_if< - ! std::is_same::type, - async_op_base - >::value>::type - > - async_op_base( - Executor1 const& ex1, - Handler_&& handler) - : h_(std::forward(handler)) - , wg_(ex1) + stable_base* next_; + virtual void destroy() = 0; + virtual ~stable_base() = default; + explicit stable_base(stable_base*& list) + : next_(boost::exchange(list, this)) { } - - template - async_op_base( - Executor1 const& ex1, - Handler_&& handler, - Allocator const& alloc) - : boost::empty_value(alloc) - , h_(std::forward(handler)) - , wg_(ex1) - { - } -#endif - - /** Invoke the final completion handler. - - This invokes the final completion handler with the specified - arguments forwarded. It is undefined to call this member - function more than once. - */ - template - void - invoke(Args&&... args) - { - this->before_invoke_hook(); - wg_.reset(); - h_(std::forward(args)...); - } - -#if ! BOOST_BEAST_DOXYGEN -public: - template< - class Handler_, - class Executor1_, - class Allocator_, - class Function> - friend - void asio_handler_invoke( - Function&& f, - async_op_base< - Handler_, Executor1_, Allocator_>* p); - - friend - void* asio_handler_allocate( - std::size_t size, async_op_base* p) - { - using net::asio_handler_allocate; - return asio_handler_allocate( - size, std::addressof(p->h_)); - } - - friend - void asio_handler_deallocate( - void* mem, std::size_t size, - async_op_base* p) - { - using net::asio_handler_deallocate; - asio_handler_deallocate(mem, size, - std::addressof(p->h_)); - } - - friend - bool asio_handler_is_continuation( - async_op_base* p) - { - using net::asio_handler_is_continuation; - return asio_handler_is_continuation( - std::addressof(p->h_)); - } -#endif }; -#if ! BOOST_BEAST_DOXYGEN -template< - class Handler, - class Executor1, - class Allocator, - class Function> -void asio_handler_invoke( - Function&& f, - async_op_base< - Handler, Executor1, Allocator>* p) -{ - using net::asio_handler_invoke; - asio_handler_invoke( - f, std::addressof(p->h_)); -} -#endif - -//------------------------------------------------------------------------------ - -// namspace detail { - -class stable_async_op_detail -{ -protected: - struct stable_base - { - static - void - destroy_list(stable_base*& list) - { - while(list) - { - auto next = list->next_; - list->destroy(); - list = next; - } - } - - protected: - stable_base* next_; - virtual void destroy() = 0; - virtual ~stable_base() = default; - explicit stable_base(stable_base*& list) - : next_(boost::exchange(list, this)) - { - } - }; -}; - -// } detail - -template< - class Handler, - class Executor1, - class Allocator = std::allocator -> -class stable_async_op_base - : public async_op_base< - Handler, Executor1, Allocator> -#if ! BOOST_BEAST_DOXYGEN - , private stable_async_op_detail -#endif -{ - stable_base* list_ = nullptr; - - void - before_invoke_hook() override - { - stable_base::destroy_list(list_); - } - -public: - /// Move Constructor - stable_async_op_base(stable_async_op_base&& other) - : async_op_base( - std::move(other)) - , list_(boost::exchange(other.list_, nullptr)) - { - } - -protected: - /** Constructor - - @param target The target of the operation. This object must - have class type, with a member function named `get_executor` - publicly accessible whose return type meets the requirements - of Executor. - - @param handler The final completion handler. The type of this - object must meet the requirements of CompletionHandler. - - @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 - stable_async_op_base( - Executor1 const& ex1, - Handler&& handler, - Allocator const& alloc = Allocator()); -#else - template< - class Handler_, - class = typename std::enable_if< - ! std::is_same::type, - stable_async_op_base - >::value>::type - > - stable_async_op_base( - Executor1 const& ex1, - Handler_&& handler) - : async_op_base< - Handler, Executor1, Allocator>( - ex1, std::forward(handler)) - { - } - - template - stable_async_op_base( - Executor1 const& ex1, - Handler_&& handler, - Allocator const& alloc) - : async_op_base< - Handler, Executor1, Allocator>( - ex1, std::forward(handler)) - { - } -#endif - - /** Destructor - - If the completion handler was not invoked, then any - state objects allocated with @ref allocate_stable will - be destroyed here. - */ - ~stable_async_op_base() - { - 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_op_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. -*/ -template< - class State, - class Handler, - class Executor1, - class Allocator, - class... Args> -State& -allocate_stable( - stable_async_op_base< - Handler, Executor1, Allocator>& base, - Args&&... args) -{ - using base_type = stable_async_op_base< - Handler, Executor1, Allocator>; - - using stable_base = - typename base_type::stable_base; - - struct state final - : stable_base - { - State value; - - explicit - state( - stable_base*& list, - Args&&... args) - : stable_base(list) - , value(std::forward(args)...) - { - } - - void destroy() override - { - delete this; - } - }; - - return (::new state(base.list_, - std::forward(args)...))->value; -} - } // detail } // beast } // boost diff --git a/include/boost/beast/core/detail/impl/read.hpp b/include/boost/beast/core/detail/impl/read.hpp index 037d45d6..a35fc4a8 100644 --- a/include/boost/beast/core/detail/impl/read.hpp +++ b/include/boost/beast/core/detail/impl/read.hpp @@ -11,9 +11,9 @@ #define BOOST_BEAST_DETAIL_IMPL_READ_HPP #include +#include #include #include -#include #include #include #include diff --git a/include/boost/beast/core/handler_ptr.hpp b/include/boost/beast/core/handler_ptr.hpp index d758e060..417501c0 100644 --- a/include/boost/beast/core/handler_ptr.hpp +++ b/include/boost/beast/core/handler_ptr.hpp @@ -10,8 +10,8 @@ #ifndef BOOST_BEAST_HANDLER_PTR_HPP #define BOOST_BEAST_HANDLER_PTR_HPP -#include #include +#include #include #include #include diff --git a/include/boost/beast/core/impl/basic_stream_socket.hpp b/include/boost/beast/core/impl/basic_stream_socket.hpp index 4ddf6d85..96da2c3c 100644 --- a/include/boost/beast/core/impl/basic_stream_socket.hpp +++ b/include/boost/beast/core/impl/basic_stream_socket.hpp @@ -10,8 +10,8 @@ #ifndef BOOST_BEAST_CORE_IMPL_BASIC_STREAM_SOCKET_HPP #define BOOST_BEAST_CORE_IMPL_BASIC_STREAM_SOCKET_HPP +#include #include -#include #include #include #include @@ -92,7 +92,7 @@ struct basic_stream_socket< template template class basic_stream_socket::read_op - : public detail::async_op_base + : public async_op_base , public boost::asio::coroutine { typename basic_stream_socket< @@ -106,7 +106,7 @@ public: basic_stream_socket& s, Buffers const& b, Handler_&& h) - : detail::async_op_base( + : async_op_base( s.get_executor(), std::forward(h)) , impl_(*s.impl_) , pg_(impl_.read_pending) @@ -209,7 +209,7 @@ public: template template class basic_stream_socket::write_op - : public detail::async_op_base + : public async_op_base , public boost::asio::coroutine { typename basic_stream_socket< @@ -223,7 +223,7 @@ public: basic_stream_socket& s, Buffers const& b, Handler_&& h) - : detail::async_op_base( + : async_op_base( s.get_executor(), std::forward(h)) , impl_(*s.impl_) , pg_(impl_.write_pending) @@ -328,7 +328,7 @@ namespace detail { template< class Protocol, class Executor, class Handler> class stream_socket_connect_op - : public detail::async_op_base + : public async_op_base { using stream_type = beast::basic_stream_socket; @@ -349,7 +349,7 @@ public: Endpoints const& eps, Condition cond, Handler_&& h) - : detail::async_op_base( + : async_op_base( s.get_executor(), std::forward(h)) , impl_(*s.impl_) , pg0_(impl_.read_pending) @@ -376,7 +376,7 @@ public: Iterator begin, Iterator end, Condition cond, Handler_&& h) - : detail::async_op_base( + : async_op_base( s.get_executor(), std::forward(h)) , impl_(*s.impl_) , pg0_(impl_.read_pending) diff --git a/include/boost/beast/core/impl/buffered_read_stream.hpp b/include/boost/beast/core/impl/buffered_read_stream.hpp index 7f9e3fb9..320b3856 100644 --- a/include/boost/beast/core/impl/buffered_read_stream.hpp +++ b/include/boost/beast/core/impl/buffered_read_stream.hpp @@ -10,11 +10,11 @@ #ifndef BOOST_BEAST_IMPL_BUFFERED_READ_STREAM_HPP #define BOOST_BEAST_IMPL_BUFFERED_READ_STREAM_HPP +#include #include #include #include #include -#include #include #include #include @@ -26,7 +26,7 @@ template template class buffered_read_stream< Stream, DynamicBuffer>::read_some_op - : public detail::async_op_base< + : public async_op_base< Handler, detail::get_executor_type> { buffered_read_stream& s_; @@ -42,7 +42,7 @@ public: Handler_&& h, buffered_read_stream& s, MutableBufferSequence const& b) - : detail::async_op_base< + : async_op_base< Handler, detail::get_executor_type>( s.get_executor(), std::forward(h)) , s_(s) diff --git a/include/boost/beast/http/impl/file_body_win32.ipp b/include/boost/beast/http/impl/file_body_win32.ipp index 2f6c9325..a23bad68 100644 --- a/include/boost/beast/http/impl/file_body_win32.ipp +++ b/include/boost/beast/http/impl/file_body_win32.ipp @@ -12,10 +12,10 @@ #if BOOST_BEAST_USE_WIN32_FILE +#include #include #include #include -#include #include #include #include @@ -333,7 +333,7 @@ template< class Protocol, class Handler, bool isRequest, class Fields> class write_some_win32_op - : public beast::detail::async_op_base< + : public beast::async_op_base< Handler, typename net::basic_stream_socket< Protocol>::executor_type> { @@ -350,7 +350,7 @@ public: net::basic_stream_socket& s, serializer,Fields>& sr) - : beast::detail::async_op_base< + : beast::async_op_base< Handler, typename net::basic_stream_socket< Protocol>::executor_type>( s.get_executor(), diff --git a/include/boost/beast/http/impl/read.ipp b/include/boost/beast/http/impl/read.ipp index 3103a84f..272a7c40 100644 --- a/include/boost/beast/http/impl/read.ipp +++ b/include/boost/beast/http/impl/read.ipp @@ -14,8 +14,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -150,7 +149,7 @@ template< bool isRequest, class Body, class Allocator, class Handler> class read_msg_op - : public beast::detail::stable_async_op_base< + : public beast::stable_async_op_base< Handler, beast::detail::get_executor_type> , public net::coroutine { @@ -185,10 +184,10 @@ public: DynamicBuffer& b, message_type& m, Handler_&& h) - : beast::detail::stable_async_op_base< + : beast::stable_async_op_base< Handler, beast::detail::get_executor_type>( s.get_executor(), std::forward(h)) - , d_(beast::detail::allocate_stable( + , d_(beast::allocate_stable( *this, s, m)) { http::async_read(d_.s, b, d_.p, std::move(*this)); diff --git a/include/boost/beast/http/impl/write.ipp b/include/boost/beast/http/impl/write.ipp index ac188bd9..adcf00d3 100644 --- a/include/boost/beast/http/impl/write.ipp +++ b/include/boost/beast/http/impl/write.ipp @@ -11,12 +11,12 @@ #define BOOST_BEAST_HTTP_IMPL_WRITE_IPP #include +#include #include #include #include #include #include -#include #include #include #include @@ -34,7 +34,7 @@ template< class Stream, class Handler, bool isRequest, class Body, class Fields> class write_some_op - : public beast::detail::async_op_base< + : public beast::async_op_base< Handler, beast::detail::get_executor_type> { Stream& s_; @@ -72,7 +72,7 @@ public: Handler_&& h, Stream& s, serializer& sr) - : beast::detail::async_op_base< + : beast::async_op_base< Handler, beast::detail::get_executor_type>( s.get_executor(), std::forward(h)) , s_(s) @@ -154,7 +154,7 @@ template< class Stream, class Handler, class Predicate, bool isRequest, class Body, class Fields> class write_op - : public beast::detail::async_op_base< + : public beast::async_op_base< Handler, beast::detail::get_executor_type> , public net::coroutine { @@ -168,7 +168,7 @@ public: Handler_&& h, Stream& s, serializer& sr) - : beast::detail::async_op_base< + : beast::async_op_base< Handler, beast::detail::get_executor_type>( s.get_executor(), std::forward(h)) , s_(s) @@ -215,7 +215,7 @@ template< class Stream, class Handler, bool isRequest, class Body, class Fields> class write_msg_op - : public beast::detail::stable_async_op_base< + : public beast::stable_async_op_base< Handler, beast::detail::get_executor_type> { Stream& s_; @@ -229,11 +229,11 @@ public: Stream& s, Handler_&& h, Args&&... args) - : beast::detail::stable_async_op_base< + : beast::stable_async_op_base< Handler, beast::detail::get_executor_type>( s.get_executor(), std::forward(h)) , s_(s) - , sr_(beast::detail::allocate_stable< + , sr_(beast::allocate_stable< serializer>( *this, std::forward(args)...)) { diff --git a/include/boost/beast/websocket/impl/accept.ipp b/include/boost/beast/websocket/impl/accept.ipp index 257a2340..b98168bc 100644 --- a/include/boost/beast/websocket/impl/accept.ipp +++ b/include/boost/beast/websocket/impl/accept.ipp @@ -16,8 +16,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -36,7 +36,7 @@ namespace websocket { template template class stream::response_op - : public beast::detail::stable_async_op_base< + : public beast::stable_async_op_base< Handler, beast::detail::get_executor_type> , public net::coroutine { @@ -68,10 +68,10 @@ public: Handler_&& h, stream& ws, Args&&... args) - : beast::detail::stable_async_op_base< + : beast::stable_async_op_base< Handler, beast::detail::get_executor_type>( ws.get_executor(), std::forward(h)) - , d_(beast::detail::allocate_stable( + , d_(beast::allocate_stable( *this, ws, std::forward(args)...)) { } @@ -107,7 +107,7 @@ public: template template class stream::accept_op - : public beast::detail::stable_async_op_base< + : public beast::stable_async_op_base< Handler, beast::detail::get_executor_type> , public net::coroutine { @@ -136,10 +136,10 @@ public: Handler_&& h, stream& ws, Args&&... args) - : beast::detail::stable_async_op_base< + : beast::stable_async_op_base< Handler, beast::detail::get_executor_type>( ws.get_executor(), std::forward(h)) - , d_(beast::detail::allocate_stable( + , d_(beast::allocate_stable( *this, ws, std::forward(args)...)) { } diff --git a/include/boost/beast/websocket/impl/close.ipp b/include/boost/beast/websocket/impl/close.ipp index 59dcf421..4066106d 100644 --- a/include/boost/beast/websocket/impl/close.ipp +++ b/include/boost/beast/websocket/impl/close.ipp @@ -11,9 +11,9 @@ #define BOOST_BEAST_WEBSOCKET_IMPL_CLOSE_IPP #include +#include #include #include -#include #include #include #include @@ -35,7 +35,7 @@ namespace websocket { template template class stream::close_op - : public beast::detail::stable_async_op_base< + : public beast::stable_async_op_base< Handler, beast::detail::get_executor_type> , public net::coroutine { @@ -67,10 +67,10 @@ public: Handler_&& h, stream& ws, close_reason const& cr) - : beast::detail::stable_async_op_base< + : beast::stable_async_op_base< Handler, beast::detail::get_executor_type>( ws.get_executor(), std::forward(h)) - , d_(beast::detail::allocate_stable( + , d_(beast::allocate_stable( *this, ws, cr)) { } diff --git a/include/boost/beast/websocket/impl/handshake.ipp b/include/boost/beast/websocket/impl/handshake.ipp index 3b50c426..5d1764ad 100644 --- a/include/boost/beast/websocket/impl/handshake.ipp +++ b/include/boost/beast/websocket/impl/handshake.ipp @@ -15,8 +15,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -34,7 +34,7 @@ namespace websocket { template template class stream::handshake_op - : public beast::detail::stable_async_op_base> , public net::coroutine { @@ -71,10 +71,10 @@ public: handshake_op( Handler_&& h, stream& ws, Args&&... args) - : beast::detail::stable_async_op_base>( ws.get_executor(), std::forward(h)) - , d_(beast::detail::allocate_stable( + , d_(beast::allocate_stable( *this, ws, std::forward(args)...)) { } diff --git a/include/boost/beast/websocket/impl/ping.ipp b/include/boost/beast/websocket/impl/ping.ipp index dfb2a6a4..6c575ac8 100644 --- a/include/boost/beast/websocket/impl/ping.ipp +++ b/include/boost/beast/websocket/impl/ping.ipp @@ -10,10 +10,10 @@ #ifndef BOOST_BEAST_WEBSOCKET_IMPL_PING_IPP #define BOOST_BEAST_WEBSOCKET_IMPL_PING_IPP +#include #include #include #include -#include #include #include #include @@ -33,7 +33,7 @@ namespace websocket { template template class stream::ping_op - : public beast::detail::stable_async_op_base< + : public beast::stable_async_op_base< Handler, beast::detail::get_executor_type> , public net::coroutine { @@ -66,10 +66,10 @@ public: stream& ws, detail::opcode op, ping_data const& payload) - : beast::detail::stable_async_op_base< + : beast::stable_async_op_base< Handler, beast::detail::get_executor_type>( ws.get_executor(), std::forward(h)) - , d_(beast::detail::allocate_stable( + , d_(beast::allocate_stable( *this, ws, op, payload)) { } diff --git a/include/boost/beast/websocket/impl/read.ipp b/include/boost/beast/websocket/impl/read.ipp index bbb67a78..b5f4f39e 100644 --- a/include/boost/beast/websocket/impl/read.ipp +++ b/include/boost/beast/websocket/impl/read.ipp @@ -11,12 +11,12 @@ #define BOOST_BEAST_WEBSOCKET_IMPL_READ_IPP #include +#include #include #include #include #include #include -#include #include #include #include @@ -77,7 +77,7 @@ template< class MutableBufferSequence, class Handler> class stream::read_some_op - : public beast::detail::async_op_base< + : public beast::async_op_base< Handler, beast::detail::get_executor_type> , public net::coroutine { @@ -98,7 +98,7 @@ public: Handler_&& h, stream& ws, MutableBufferSequence const& bs) - : beast::detail::async_op_base< + : beast::async_op_base< Handler, beast::detail::get_executor_type>( ws.get_executor(), std::forward(h)) , ws_(ws) @@ -672,7 +672,7 @@ template< class DynamicBuffer, class Handler> class stream::read_op - : public beast::detail::async_op_base< + : public beast::async_op_base< Handler, beast::detail::get_executor_type> , public net::coroutine { @@ -690,7 +690,7 @@ public: DynamicBuffer& b, std::size_t limit, bool some) - : beast::detail::async_op_base< + : beast::async_op_base< Handler, beast::detail::get_executor_type>( ws.get_executor(), std::forward(h)) , ws_(ws) diff --git a/include/boost/beast/websocket/impl/teardown.ipp b/include/boost/beast/websocket/impl/teardown.ipp index 8422aed3..fe8ab645 100644 --- a/include/boost/beast/websocket/impl/teardown.ipp +++ b/include/boost/beast/websocket/impl/teardown.ipp @@ -10,9 +10,9 @@ #ifndef BOOST_BEAST_WEBSOCKET_IMPL_TEARDOWN_IPP #define BOOST_BEAST_WEBSOCKET_IMPL_TEARDOWN_IPP +#include #include #include -#include #include #include #include @@ -26,7 +26,7 @@ namespace detail { template class teardown_tcp_op - : public beast::detail::async_op_base< + : public beast::async_op_base< Handler, beast::detail::get_executor_type< net::ip::tcp::socket>> , public net::coroutine @@ -43,7 +43,7 @@ public: Handler_&& h, socket_type& s, role_type role) - : beast::detail::async_op_base< + : beast::async_op_base< Handler, beast::detail::get_executor_type< net::ip::tcp::socket>>(s.get_executor(), std::forward(h)) diff --git a/include/boost/beast/websocket/impl/write.ipp b/include/boost/beast/websocket/impl/write.ipp index 67a85353..caf5c95b 100644 --- a/include/boost/beast/websocket/impl/write.ipp +++ b/include/boost/beast/websocket/impl/write.ipp @@ -10,6 +10,7 @@ #ifndef BOOST_BEAST_WEBSOCKET_IMPL_WRITE_IPP #define BOOST_BEAST_WEBSOCKET_IMPL_WRITE_IPP +#include #include #include #include @@ -17,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -136,7 +136,7 @@ do_context_takeover_write(role_type role) template template class stream::write_some_op - : public beast::detail::async_op_base< + : public beast::async_op_base< Handler, beast::detail::get_executor_type> , public net::coroutine { @@ -161,7 +161,7 @@ public: stream& ws, bool fin, Buffers const& bs) - : beast::detail::async_op_base< + : beast::async_op_base< Handler, beast::detail::get_executor_type>( ws.get_executor(), std::forward(h)) , ws_(ws) diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt index 0bedd480..6fd539cd 100644 --- a/test/beast/core/CMakeLists.txt +++ b/test/beast/core/CMakeLists.txt @@ -16,7 +16,6 @@ add_executable (tests-beast-core ${EXTRAS_FILES} ${TEST_MAIN} Jamfile - _detail_async_op_base.cpp _detail_base64.cpp _detail_buffer.cpp _detail_clamp.cpp @@ -25,6 +24,7 @@ add_executable (tests-beast-core _detail_tuple.cpp _detail_variant.cpp _detail_varint.cpp + async_op_base.cpp buffer_test.hpp file_test.hpp basic_stream_socket.cpp diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile index 89bbca5c..e2e76f43 100644 --- a/test/beast/core/Jamfile +++ b/test/beast/core/Jamfile @@ -8,7 +8,6 @@ # local SOURCES = - _detail_async_op_base.cpp _detail_base64.cpp _detail_buffer.cpp _detail_clamp.cpp @@ -17,6 +16,7 @@ local SOURCES = _detail_tuple.cpp _detail_variant.cpp _detail_varint.cpp + async_op_base.cpp basic_stream_socket.cpp bind_handler.cpp buffer_traits.cpp diff --git a/test/beast/core/_detail_async_op_base.cpp b/test/beast/core/async_op_base.cpp similarity index 61% rename from test/beast/core/_detail_async_op_base.cpp rename to test/beast/core/async_op_base.cpp index d39cae1f..81086d51 100644 --- a/test/beast/core/_detail_async_op_base.cpp +++ b/test/beast/core/async_op_base.cpp @@ -8,23 +8,24 @@ // // Test that header file is self-contained. -#include +#include #include -#include +#include #include +#include #include -#include +#include #include +#include #include //------------------------------------------------------------------------------ namespace boost { namespace beast { -namespace detail { -//namespace { +namespace { struct ex1_type { @@ -158,9 +159,8 @@ asio_handler_is_continuation( return false; } -//} // (anon) +} // (anon) -} // detail } // beast } // boost @@ -169,18 +169,18 @@ namespace asio { template struct associated_allocator< - boost::beast::detail::handler< - boost::beast::detail::no_ex, - boost::beast::detail::intrusive_alloc>, + boost::beast::handler< + boost::beast::no_ex, + boost::beast::intrusive_alloc>, Allocator> { using type = - boost::beast::detail::intrusive_alloc::allocator_type; + boost::beast::intrusive_alloc::allocator_type; static type get( - boost::beast::detail::handler< - boost::beast::detail::no_ex, - boost::beast::detail::intrusive_alloc> const& h, + boost::beast::handler< + boost::beast::no_ex, + boost::beast::intrusive_alloc> const& h, Allocator const& a = Allocator()) noexcept { return type{}; @@ -189,18 +189,18 @@ struct associated_allocator< template struct associated_executor< - boost::beast::detail::handler< - boost::beast::detail::intrusive_ex, - boost::beast::detail::no_alloc>, + boost::beast::handler< + boost::beast::intrusive_ex, + boost::beast::no_alloc>, Executor> { using type = - boost::beast::detail::intrusive_ex::executor_type; + boost::beast::intrusive_ex::executor_type; static type get( - boost::beast::detail::handler< - boost::beast::detail::intrusive_ex, - boost::beast::detail::no_alloc> const& h, + boost::beast::handler< + boost::beast::intrusive_ex, + boost::beast::no_alloc> const& h, Executor const& a = Executor()) noexcept { return type{}; @@ -209,12 +209,12 @@ struct associated_executor< template struct associated_allocator< - boost::beast::detail::legacy_handler, Allocator> + boost::beast::legacy_handler, Allocator> { using type = std::allocator; static type get( - boost::beast::detail::legacy_handler const& h, + boost::beast::legacy_handler const& h, Allocator const& a = Allocator()) noexcept { return type{}; @@ -223,13 +223,13 @@ struct associated_allocator< template struct associated_executor< - boost::beast::detail::legacy_handler, Executor> + boost::beast::legacy_handler, Executor> { using type = typename - boost::beast::detail::legacy_handler::executor; + boost::beast::legacy_handler::executor; static type get( - boost::beast::detail::legacy_handler const&, + boost::beast::legacy_handler const&, Executor const& = Executor()) noexcept { return type{}; @@ -243,7 +243,6 @@ struct associated_executor< namespace boost { namespace beast { -namespace detail { class async_op_base_test : public beast::unit_test::suite { @@ -510,45 +509,162 @@ public: //-------------------------------------------------------------------------- -#if 0 - // VFALCO TODO: This will become the example in the javadoc - - template< - class AsyncReadStream, - class MutableBufferSequence, - class ReadHandler> - BOOST_ASIO_INITFN_RESULT_TYPE( - ReadHandler, void (error_code, std::size_t)) - async_read( - AsyncReadStream& stream, - MutableBufferSequence const& buffers, - ReadHandler&& handler) + // Asynchronously read into a buffer until the buffer is full, or an error occurs + template + BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void (error_code, std::size_t)) + async_read(AsyncReadStream& stream, net::mutable_buffer buffer, ReadHandler&& handler) { - net::async_completion init{handler}; + using handler_type = BOOST_ASIO_HANDLER_TYPE(ReadHandler, void(error_code, std::size_t)); + using base_type = async_op_base; - using base = async_op_base< - BOOST_ASIO_HANDLER_TYPE(ReadHandler, void(error_code, std::size_t)), - get_executor_type>; - - struct read_op : base + struct op : base_type { AsyncReadStream& stream_; - buffers_suffix buffers_; + net::mutable_buffer buffer_; + std::size_t total_bytes_transferred_; - void operator()(error_code ec, std::size_t bytes_transferred) + op( + AsyncReadStream& stream, + net::mutable_buffer buffer, + handler_type& handler) + : base_type(stream.get_executor(), std::move(handler)) + , 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)); + + // If this is first invocation, we have to post to the executor. Otherwise the + // handler would be invoked before the call to async_read returns, which is disallowed. + if(! is_continuation) + { + // Issue a zero-sized read so our handler runs "as-if" posted using net::post(). + // This technique is used to reduce the number of function template instantiations. + return stream_.async_read_some(net::mutable_buffer(buffer_.data(), 0), std::move(*this)); + } + + // Call the completion handler with the result + this->invoke(ec, total_bytes_transferred_); } }; - read_op(stream, buffers, std::forward(handler)); + net::async_completion init{handler}; + op(stream, buffer, init.completion_handler); return init.result.get(); } -#endif + + // Asynchronously send a message multiple times, once per second + template + auto async_write_messages( + AsyncWriteStream& stream, + T const& message, + std::size_t repeat_count, + WriteHandler&& handler) -> + typename net::async_result< + typename std::decay::type, + void(error_code)>::return_type + { + using handler_type = typename net::async_completion::completion_handler_type; + using base_type = stable_async_op_base; + + struct op : base_type + { + // This object must have a stable address + struct temporary_data + { + std::string const message; + net::steady_timer timer; + + temporary_data(std::string message_, net::io_context& ctx) + : message(std::move(message_)) + , timer(ctx) + { + } + }; + + enum { starting, waiting, writing } state_; + AsyncWriteStream& stream_; + std::size_t repeats_; + temporary_data& data_; + + op(AsyncWriteStream& stream, std::size_t repeats, std::string message, handler_type& handler) + : base_type(stream.get_executor(), std::move(handler)) + , state_(starting) + , stream_(stream) + , repeats_(repeats) + , data_(allocate_stable(*this, std::move(message), stream.get_executor().context())) + { + (*this)(); // start the operation + } + + void operator()(error_code ec = {}, std::size_t = 0) + { + if (!ec) + { + switch (state_) + { + case starting: + // 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. + if(repeats_ == 0) + return net::post(std::move(*this)); + + case writing: + if (repeats_ > 0) + { + --repeats_; + state_ = waiting; + data_.timer.expires_after(std::chrono::seconds(1)); + + // Composed operation not yet complete. + return data_.timer.async_wait(std::move(*this)); + } + + // Composed operation complete, continue below. + break; + + case waiting: + // Composed operation not yet complete. + state_ = writing; + return net::async_write(stream_, net::buffer(data_.message), std::move(*this)); + } + } + + // The base class destroys the temporary data automatically, before invoking the final completion handler + this->invoke(ec); + } + }; + + net::async_completion completion(handler); + std::ostringstream os; + os << message; + op(stream, repeat_count, os.str(), completion.completion_handler); + return completion.result.get(); + } void testJavadocs() { + struct handler + { + void operator()(error_code = {}, std::size_t = 0) + { + } + }; + + BEAST_EXPECT((&async_op_base_test::async_read)); + BEAST_EXPECT((&async_op_base_test::async_write_messages)); } //-------------------------------------------------------------------------- @@ -564,6 +680,5 @@ public: BEAST_DEFINE_TESTSUITE(beast,core,async_op_base); -} // detail } // beast } // boost