Refactor async_op_base:

* Renamed to async_op_base (was operation_base)
* Executor1 is explicit
* Add stable_async_op_base refinement
This commit is contained in:
Vinnie Falco
2019-01-05 18:51:47 -08:00
parent 522ca9e78b
commit 9e44ae7be5
15 changed files with 1528 additions and 1461 deletions

View File

@ -3,6 +3,7 @@ Version 202
* Use cxxstd instead of cxxflags
* Update coverage badge images
* Tidy up basic_stream_socket docs
* Refactor async_op_base
--------------------------------------------------------------------------------

View File

@ -272,7 +272,7 @@ public:
net::io_context::executor_type;
/// Return the executor associated with the object.
net::io_context::executor_type
executor_type
get_executor() noexcept
{
return in_->ioc.get_executor();

View File

@ -19,6 +19,7 @@
#include <boost/asio/connect.hpp>
#include <boost/asio/executor.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/config/workaround.hpp>
#include <boost/optional.hpp>
#include <chrono>
#include <memory>
@ -172,6 +173,11 @@ class basic_stream_socket
static std::size_t constexpr no_limit =
(std::numeric_limits<std::size_t>::max)();
// friend class template declaration in a class template is ignored
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88672
#if BOOST_WORKAROUND(BOOST_GCC, > 0)
public:
#endif
struct impl_type
: std::enable_shared_from_this<impl_type>
{
@ -205,7 +211,17 @@ class basic_stream_socket
void close(); // cancel everything
void maybe_kick(); // kick the rate timer if needed
void on_timer(); // rate timer completion
using executor_type = Executor;
Executor
get_executor() const noexcept
{
return ex;
}
};
#if BOOST_WORKAROUND(BOOST_GCC, > 0)
private:
#endif
// We use shared ownership for the state so it can
// outlive the destruction of the stream_socket object,
@ -230,6 +246,9 @@ class basic_stream_socket
template<class, class>
friend class basic_stream_socket;
struct read_timeout_handler;
struct write_timeout_handler;
public:
/// The type of the next layer.
using next_layer_type = net::basic_stream_socket<Protocol>;

View File

@ -0,0 +1,509 @@
//
// 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_DETAIL_ASYNC_OP_BASE_HPP
#define BOOST_BEAST_CORE_DETAIL_ASYNC_OP_BASE_HPP
#include <boost/beast/core/detail/config.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/asio/handler_alloc_hook.hpp>
#include <boost/asio/handler_continuation_hook.hpp>
#include <boost/asio/handler_invoke_hook.hpp>
#include <boost/core/exchange.hpp>
#include <boost/core/empty_value.hpp>
#include <utility>
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<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 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 <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 @ref allocate_stable
*/
template<
class Handler,
class Executor1,
class Allocator = std::allocator<void>
>
class async_op_base
#if ! BOOST_BEAST_DOXYGEN
: private boost::empty_value<Allocator>
#endif
{
static_assert(
net::is_executor<Executor1>::value,
"Executor requirements not met");
Handler h_;
net::executor_work_guard<Executor1> 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<Handler, Allocator>;
/** 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<Handler, Executor1>;
/** 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<Allocator>::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_;
}
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 <em>Executor</em>.
@param handler The final completion handler. The type of this
object must meet the requirements of <em>CompletionHandler</em>.
@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_op_base(
Executor1 const& ex1,
Handler&& handler,
Allocator const& alloc = Allocator());
#else
template<
class Handler_,
class = typename std::enable_if<
! std::is_same<typename
std::decay<Handler_>::type,
async_op_base
>::value>::type
>
async_op_base(
Executor1 const& ex1,
Handler_&& handler)
: h_(std::forward<Handler_>(handler))
, wg_(ex1)
{
}
template<class Handler_>
async_op_base(
Executor1 const& ex1,
Handler_&& handler,
Allocator const& alloc)
: boost::empty_value<Allocator>(alloc)
, h_(std::forward<Handler_>(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<class... Args>
void
invoke(Args&&... args)
{
this->before_invoke_hook();
wg_.reset();
h_(std::forward<Args>(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<void>
>
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<Handler, Executor1, Allocator>(
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 <em>Executor</em>.
@param handler The final completion handler. The type of this
object must meet the requirements of <em>CompletionHandler</em>.
@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_op_base(
Executor1 const& ex1,
Handler&& handler,
Allocator const& alloc = Allocator());
#else
template<
class Handler_,
class = typename std::enable_if<
! std::is_same<typename
std::decay<Handler_>::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_>(handler))
{
}
template<class Handler_>
stable_async_op_base(
Executor1 const& ex1,
Handler_&& handler,
Allocator const& alloc)
: async_op_base<
Handler, Executor1, Allocator>(
ex1, std::forward<Handler_>(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>(args)...)
{
}
void destroy() override
{
delete this;
}
};
return (::new state(base.list_,
std::forward<Args>(args)...))->value;
}
} // detail
} // beast
} // boost
#endif

View File

@ -0,0 +1,35 @@
//
// Copyright (c) 2016-2017 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_DETAIL_GET_EXECUTOR_TYPE
#define BOOST_BEAST_DETAIL_GET_EXECUTOR_TYPE
#include <boost/config/workaround.hpp>
#include <utility>
namespace boost {
namespace beast {
namespace detail {
// Workaround for ICE on gcc 4.8
#if BOOST_WORKAROUND(BOOST_GCC, < 40900)
template<class T>
using get_executor_type =
typename std::decay<T>::type::executor_type;
#else
template<class T>
using get_executor_type =
decltype(std::declval<T&>().get_executor());
#endif
} // detail
} // beast
} // boost
#endif

View File

@ -10,11 +10,16 @@
#ifndef BOOST_BEAST_DETAIL_IMPL_READ_HPP
#define BOOST_BEAST_DETAIL_IMPL_READ_HPP
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/flat_static_buffer.hpp>
#include <boost/beast/core/detail/get_executor_type.hpp>
#include <boost/beast/core/detail/async_op_base.hpp>
#include <boost/asio/basic_stream_socket.hpp>
#include <boost/asio/handler_alloc_hook.hpp>
#include <boost/asio/handler_continuation_hook.hpp>
#include <boost/asio/handler_invoke_hook.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/throw_exception.hpp>
// REMOVE
#define BOOST_BEAST_ENABLE_NON_BLOCKING
namespace boost {
namespace beast {
@ -32,31 +37,31 @@ template<
class DynamicBuffer,
class Condition,
class Handler>
class read_op : public net::coroutine
class read_op
: public net::coroutine
, public async_op_base<
Handler, get_executor_type<Stream>>
{
Stream& s_;
net::executor_work_guard<decltype(
std::declval<Stream&>().get_executor())> wg_;
DynamicBuffer& b_;
Condition cond_;
Handler h_;
std::size_t total_ = 0;
public:
read_op(read_op&&) = default;
read_op(read_op const&) = delete;
template<class DeducedHandler>
template<class Handler_>
read_op(
Stream& s,
DynamicBuffer& b,
Condition cond,
DeducedHandler&& h)
: s_(s)
, wg_(s_.get_executor())
Handler_&& h)
: async_op_base<
Handler, get_executor_type<Stream>>(
s.get_executor(), std::forward<Handler_>(h))
, s_(s)
, b_(b)
, cond_(cond)
, h_(std::forward<DeducedHandler>(h))
{
(*this)({}, 0, false);
}
@ -65,100 +70,43 @@ public:
operator()(
error_code ec,
std::size_t bytes_transferred,
bool cont = true);
//
using allocator_type =
net::associated_allocator_t<Handler>;
using executor_type = net::associated_executor_t<
Handler, decltype(std::declval<Stream&>().get_executor())>;
allocator_type
get_allocator() const noexcept
bool cont = true)
{
return net::get_associated_allocator(h_);
}
executor_type
get_executor() const noexcept
{
return net::get_associated_executor(
h_, s_.get_executor());
}
friend
void* asio_handler_allocate(
std::size_t size, read_op* op)
{
using net::asio_handler_allocate;
return asio_handler_allocate(
size, std::addressof(op->h_));
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, read_op* op)
{
using net::asio_handler_deallocate;
asio_handler_deallocate(
p, size, std::addressof(op->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, read_op* op)
{
using net::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(op->h_));
}
};
template<
class Stream, class DynamicBuffer,
class Condition, class Handler>
void
read_op<Stream, DynamicBuffer, Condition, Handler>::
operator()(
error_code ec,
std::size_t bytes_transferred,
bool cont)
{
std::size_t max_size;
std::size_t max_prepare;
BOOST_ASIO_CORO_REENTER(*this)
{
max_size = cond_(ec, total_, b_);
max_prepare = std::min<std::size_t>(
std::max<std::size_t>(
512, b_.capacity() - b_.size()),
std::min<std::size_t>(
max_size, b_.max_size() - b_.size()));
while(max_prepare > 0)
std::size_t max_size;
std::size_t max_prepare;
BOOST_ASIO_CORO_REENTER(*this)
{
BOOST_ASIO_CORO_YIELD
s_.async_read_some(
b_.prepare(max_prepare), std::move(*this));
b_.commit(bytes_transferred);
total_ += bytes_transferred;
max_size = cond_(ec, total_, b_);
max_prepare = std::min<std::size_t>(
std::max<std::size_t>(
512, b_.capacity() - b_.size()),
std::min<std::size_t>(
max_size, b_.max_size() - b_.size()));
while(max_prepare > 0)
{
BOOST_ASIO_CORO_YIELD
s_.async_read_some(
b_.prepare(max_prepare), std::move(*this));
b_.commit(bytes_transferred);
total_ += bytes_transferred;
max_size = cond_(ec, total_, b_);
max_prepare = std::min<std::size_t>(
std::max<std::size_t>(
512, b_.capacity() - b_.size()),
std::min<std::size_t>(
max_size, b_.max_size() - b_.size()));
}
if(! cont)
{
BOOST_ASIO_CORO_YIELD
net::post(s_.get_executor(),
beast::bind_front_handler(
std::move(*this), ec, total_));
}
this->invoke(ec, total_);
}
if(! cont)
{
BOOST_ASIO_CORO_YIELD
net::post(s_.get_executor(),
beast::bind_front_handler(
std::move(*this), ec, total_));
}
h_(ec, total_);
}
}
};
//------------------------------------------------------------------------------
@ -170,14 +118,14 @@ template<
class DynamicBuffer,
class Condition,
class Handler>
class read_non_blocking_op : public net::coroutine
class read_non_blocking_op
: public net::coroutine
, public async_op_base<Handler,
get_executor_type<net::basic_stream_socket<Protocol>>>
{
net::basic_stream_socket<Protocol>& s_;
net::executor_work_guard<decltype(
s_.get_executor())> wg_;
DynamicBuffer& b_;
Condition cond_;
Handler h_;
std::size_t limit_;
std::size_t total_ = 0;
@ -185,143 +133,90 @@ public:
read_non_blocking_op(read_non_blocking_op&&) = default;
read_non_blocking_op(read_non_blocking_op const&) = delete;
template<class DeducedHandler>
template<class Handler_>
read_non_blocking_op(
net::basic_stream_socket<Protocol>& s,
DynamicBuffer& b,
Condition cond,
DeducedHandler&& h)
: s_(s)
, wg_(s_.get_executor())
Handler_&& h)
: async_op_base<Handler, get_executor_type<
net::basic_stream_socket<Protocol>>>(
s.get_executor(), std::forward<Handler_>(h))
, s_(s)
, b_(b)
, cond_(cond)
, h_(std::forward<DeducedHandler>(h))
{
(*this)({}, false);
}
void
operator()(error_code ec, bool cont = true);
//
using allocator_type =
net::associated_allocator_t<Handler>;
using executor_type = net::associated_executor_t<
Handler, decltype(s_.get_executor())>;
allocator_type
get_allocator() const noexcept
operator()(error_code ec, bool cont = true)
{
return net::get_associated_allocator(h_);
std::size_t n;
std::size_t bytes_transferred;
BOOST_ASIO_CORO_REENTER(*this)
{
limit_ = cond_(ec, total_, b_);
for(;;)
{
n = detail::min<std::size_t>(
limit_, b_.max_size() - b_.size());
if(n == 0)
break;
BOOST_ASIO_CORO_YIELD
s_.async_wait(
net::socket_base::wait_read, std::move(*this));
if(b_.size() <= default_max_stack_buffer)
{
flat_static_buffer<
default_max_stack_buffer> sb;
bytes_transferred = net::buffer_copy(
sb.prepare(b_.size()), b_.data());
sb.commit(bytes_transferred);
b_.consume(bytes_transferred);
//detail::shrink_to_fit(b_);
n = detail::min<std::size_t>(
limit_,
sb.capacity() - sb.size(),
b_.max_size() - sb.size());
BOOST_ASSERT(n > 0);
bytes_transferred =
s_.read_some(sb.prepare(n), ec);
sb.commit(bytes_transferred);
total_ += bytes_transferred;
limit_ = cond_(ec, total_, sb);
b_.commit(net::buffer_copy(
b_.prepare(sb.size()), sb.data()));
}
else
{
n = detail::min<std::size_t>(
limit_,
s_.available(),
b_.max_size() - b_.size(),
std::max<std::size_t>(
512, b_.capacity() - b_.size()));
BOOST_ASSERT(n > 0);
bytes_transferred = s_.read_some(
b_.prepare(n), ec);
b_.commit(bytes_transferred);
total_ += bytes_transferred;
limit_ = cond_(ec, total_, b_);
}
}
if(! cont)
{
BOOST_ASIO_CORO_YIELD
net::post(s_.get_executor(),
beast::bind_front_handler(
std::move(*this), ec, total_));
}
this->invoke(ec, total_);
}
}
executor_type
get_executor() const noexcept
{
return net::get_associated_executor(
h_, s_.get_executor());
}
friend
void* asio_handler_allocate(
std::size_t size, read_non_blocking_op* op)
{
using net::asio_handler_allocate;
return asio_handler_allocate(
size, std::addressof(op->h_));
}
friend
void asio_handler_deallocate(void* p,
std::size_t size, read_non_blocking_op* op)
{
using net::asio_handler_deallocate;
asio_handler_deallocate(
p, size, std::addressof(op->h_));
}
template<class Function>
friend
void asio_handler_invoke(
Function&& f, read_non_blocking_op* op)
{
using net::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(op->h_));
}
};
template<
class Protocol, class DynamicBuffer,
class Condition, class Handler>
void
read_non_blocking_op<
Protocol, DynamicBuffer, Condition, Handler>::
operator()(error_code ec, bool cont)
{
std::size_t n;
std::size_t bytes_transferred;
BOOST_ASIO_CORO_REENTER(*this)
{
limit_ = cond_(ec, total_, b_);
for(;;)
{
n = detail::min<std::size_t>(
limit_, b_.max_size() - b_.size());
if(n == 0)
break;
BOOST_ASIO_CORO_YIELD
s_.async_wait(
net::socket_base::wait_read, std::move(*this));
if(b_.size() <= default_max_stack_buffer)
{
flat_static_buffer<
default_max_stack_buffer> sb;
bytes_transferred = net::buffer_copy(
sb.prepare(b_.size()), b_.data());
sb.commit(bytes_transferred);
b_.consume(bytes_transferred);
//detail::shrink_to_fit(b_);
n = detail::min<std::size_t>(
limit_,
sb.capacity() - sb.size(),
b_.max_size() - sb.size());
BOOST_ASSERT(n > 0);
bytes_transferred =
s_.read_some(sb.prepare(n), ec);
sb.commit(bytes_transferred);
total_ += bytes_transferred;
limit_ = cond_(ec, total_, sb);
b_.commit(net::buffer_copy(
b_.prepare(sb.size()), sb.data()));
}
else
{
n = detail::min<std::size_t>(
limit_,
s_.available(),
b_.max_size() - b_.size(),
std::max<std::size_t>(
512, b_.capacity() - b_.size()));
BOOST_ASSERT(n > 0);
bytes_transferred = s_.read_some(
b_.prepare(n), ec);
b_.commit(bytes_transferred);
total_ += bytes_transferred;
limit_ = cond_(ec, total_, b_);
}
}
if(! cont)
{
BOOST_ASIO_CORO_YIELD
net::post(s_.get_executor(),
beast::bind_front_handler(
std::move(*this), ec, total_));
}
h_(ec, total_);
}
}
#endif
//------------------------------------------------------------------------------

View File

@ -1,241 +0,0 @@
//
// 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_DETAIL_OPERATION_BASE_HPP
#define BOOST_BEAST_CORE_DETAIL_OPERATION_BASE_HPP
#include <boost/beast/core/detail/config.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/handler_alloc_hook.hpp>
#include <boost/asio/handler_continuation_hook.hpp>
#include <boost/asio/handler_invoke_hook.hpp>
#include <boost/asio/system_executor.hpp>
#include <boost/core/empty_value.hpp>
#include <utility>
namespace boost {
namespace beast {
namespace detail {
/** Base class which stores a handler and forwards handler associations.
This mix-in assists bind wrappers, intermediate handlers, composed
operations, and similar types which store a handler, by acting as
a base class which holds the handler. Any networking customizations
on the handler will be propagated to the derived class. Specifically:
@li Any allocator associated with the handler will be propagated to
this object. Otherwise, the allocator associated with this
object will be a default allocator which the caller may specify
through a template parameter and constructor parameter.
@li Any executor associated with the handler will be propagated to
this object. Otherwise, the executor associated with this
object will be a default executor which the caller may specify
through a template parameter and constructor parameter.
@li 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 declaration produces a class which wraps a
handler and inherits all of the networking customizations
associated with that handler:
@code
template <class Handler>
struct wrapped_handler : operation_base<
Handler, net::associated_executor_t<Handler>>
{
template <class Handler_>
explicit wrapped_handler (Handler_&& handler)
: operation_base<Handler, net::associated_executor_t <Handler>>(
std::forward<Handler_>(handler), net::get_associated_executor(handler))
{
}
template <class... Args>
void operator()(Args&&... args)
{
this->handler_(std::forward<Args>(args)...);
}
};
@endcode
@tparam Handler The type of the completion handler to store.
This type must meet the requirements of <em>CompletionHandler</em>.
@tparam Executor The executor type to use if the handler does not
have an associated executor.
@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.
*/
template<
class Handler,
class Executor,
class Allocator = std::allocator<void>
>
class operation_base
#if ! BOOST_BEAST_DOXYGEN
: private boost::empty_value<
net::associated_allocator_t<
Handler, Allocator>, 0>
, private boost::empty_value<
net::associated_executor_t<
Handler, Executor>, 1>
#endif
{
public:
/** The type of allocator associated with this object.
If a class derived from @ref operation_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>;
/** The type of executor associated with this object.
If a class derived from @ref operation_base is a completion
handler, then the associated executor of the derived class will
be this type.
*/
using executor_type =
net::associated_executor_t<
Handler, Executor>;
/** Returns the allocator associated with this object.
If a class derived from @ref operation_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 boost::empty_value<
allocator_type, 0>::get();
}
/** Returns the allocator associated with this object.
If a class derived from @ref operation_base is a completion
handler, then the object returned from this function will be used
as the associated allocator of the derived class.
*/
executor_type
get_executor() const noexcept
{
return boost::empty_value<
executor_type, 1>::get();
}
protected:
Handler handler_;
template<
class DeducedHandler
#if BOOST_BEAST_DOXYGEN
,class = typename std::enable_if<
! std::is_same<typename
std::decay<Handler_>::type,
operation_base
>::value>::type
#endif
>
operation_base(
DeducedHandler&& handler,
executor_type ex = executor_type{},
allocator_type alloc = allocator_type{})
: boost::empty_value<allocator_type, 0>(
boost::empty_init_t{}, alloc)
, boost::empty_value<executor_type, 1>(
boost::empty_init_t{}, ex)
, handler_(std::forward<DeducedHandler>(handler))
{
}
public:
#if ! BOOST_BEAST_DOXYGEN
template<
class Handler_,
class Executor_,
class Allocator_,
class Function>
friend
void asio_handler_invoke(
Function&& f,
operation_base<
Handler_, Executor_, Allocator_>* p);
friend
void* asio_handler_allocate(
std::size_t size, operation_base* p)
{
using net::asio_handler_allocate;
return asio_handler_allocate(
size, std::addressof(p->handler_));
}
friend
void asio_handler_deallocate(
void* mem, std::size_t size,
operation_base* p)
{
using net::asio_handler_deallocate;
asio_handler_deallocate(mem, size,
std::addressof(p->handler_));
}
friend
bool asio_handler_is_continuation(
operation_base* p)
{
using net::asio_handler_is_continuation;
return asio_handler_is_continuation(
std::addressof(p->handler_));
}
#endif
};
#if ! BOOST_BEAST_DOXYGEN
template<
class Handler,
class Executor,
class Allocator,
class Function>
void asio_handler_invoke(
Function&& f,
operation_base<
Handler, Executor, Allocator>* p)
{
using net::asio_handler_invoke;
asio_handler_invoke(
f, std::addressof(p->handler_));
}
#endif
} // detail
} // beast
} // boost
#endif

View File

@ -11,7 +11,10 @@
#define BOOST_BEAST_DETAIL_READ_HPP
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/detail/stream_algorithm.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/type_traits.hpp>
#include <boost/asio/async_result.hpp>
#include <cstdlib>
namespace boost {
namespace beast {

View File

@ -11,10 +11,9 @@
#define BOOST_BEAST_CORE_IMPL_BASIC_STREAM_SOCKET_HPP
#include <boost/beast/core/buffers_prefix.hpp>
#include <boost/beast/core/detail/operation_base.hpp>
#include <boost/beast/core/detail/async_op_base.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/assert.hpp>
#include <boost/core/exchange.hpp>
#include <cstdlib>
@ -24,6 +23,64 @@
namespace boost {
namespace beast {
template<class Protocol, class Executor>
struct basic_stream_socket<
Protocol, Executor>::read_timeout_handler
{
std::shared_ptr<impl_type> impl;
void
operator()(error_code ec)
{
// timer canceled
if(ec == net::error::operation_aborted)
return;
BOOST_ASSERT(! ec);
if(! impl->read_closed)
{
// timeout
impl->close();
impl->read_closed = true;
}
else
{
// late completion
impl->read_closed = false;
}
}
};
template<class Protocol, class Executor>
struct basic_stream_socket<
Protocol, Executor>::write_timeout_handler
{
std::shared_ptr<impl_type> impl;
void
operator()(error_code ec)
{
// timer canceled
if(ec == net::error::operation_aborted)
return;
BOOST_ASSERT(! ec);
if(! impl->write_closed)
{
// timeout
impl->close();
impl->write_closed = true;
}
else
{
// late completion
impl->write_closed = false;
}
}
};
/*
The algorithm for implementing the timeout depends
on the executor providing ordered execution guarantee.
@ -35,87 +92,53 @@ namespace beast {
template<class Protocol, class Executor>
template<class Buffers, class Handler>
class basic_stream_socket<Protocol, Executor>::read_op
: public detail::operation_base<
Handler, Executor>
: public detail::async_op_base<Handler, Executor>
, public boost::asio::coroutine
{
basic_stream_socket& s_;
net::executor_work_guard<Executor> wg0_;
net::executor_work_guard<executor_type> wg1_;
typename basic_stream_socket<
Protocol, Executor>::impl_type& impl_;
pending_guard pg_;
Buffers b_;
struct timeout_handler
{
std::shared_ptr<impl_type> impl;
void
operator()(error_code ec)
{
// timer canceled
if(ec == net::error::operation_aborted)
return;
BOOST_ASSERT(! ec);
if(! impl->read_closed)
{
// timeout
impl->close();
impl->read_closed = true;
}
else
{
// late completion
impl->read_closed = false;
}
}
};
public:
template<class Handler_>
read_op(
basic_stream_socket& s,
Buffers const& b,
Handler_&& h)
: detail::operation_base<
Handler, Executor>(
std::forward<Handler_>(h),
net::get_associated_executor(
h, s.get_executor()))
, s_(s)
, wg0_(s_.get_executor())
, wg1_(net::get_associated_executor(
this->handler_, s_.get_executor()))
, pg_(s_.impl_->read_pending)
: detail::async_op_base<Handler, Executor>(
s.get_executor(), std::forward<Handler_>(h))
, impl_(*s.impl_)
, pg_(impl_.read_pending)
, b_(b)
{
(*this)();
(*this)({});
}
void
operator()(
error_code ec = {},
error_code ec,
std::size_t bytes_transferred = 0)
{
BOOST_ASIO_CORO_REENTER(*this)
{
// must come first
// VFALCO TODO what about the handler's allocator?
s_.impl_->read_timer.async_wait(
impl_.read_timer.async_wait(
net::bind_executor(
this->get_executor(),
timeout_handler{s_.impl_}));
read_timeout_handler{
impl_.shared_from_this()}));
s_.impl_->maybe_kick();
impl_.maybe_kick();
// check if the balance is zero
if(s_.impl_->read_remain == 0)
if(impl_.read_remain == 0)
{
// wait until the next time slice
++s_.impl_->waiting;
++impl_.waiting;
BOOST_ASIO_CORO_YIELD
s_.impl_->rate_timer.async_wait(std::move(*this));
impl_.rate_timer.async_wait(std::move(*this));
if(ec)
{
@ -126,46 +149,46 @@ public:
}
// must call this
s_.impl_->on_timer();
BOOST_ASSERT(s_.impl_->read_remain > 0);
impl_.on_timer();
BOOST_ASSERT(impl_.read_remain > 0);
}
// we always use buffers_prefix,
// to reduce template instantiations.
BOOST_ASSERT(s_.impl_->read_remain > 0);
BOOST_ASSERT(impl_.read_remain > 0);
BOOST_ASIO_CORO_YIELD
s_.impl_->socket.async_read_some(
impl_.socket.async_read_some(
beast::buffers_prefix(
s_.impl_->read_remain, b_),
impl_.read_remain, b_),
std::move(*this));
if(s_.impl_->read_remain != no_limit)
if(impl_.read_remain != no_limit)
{
// adjust balance
BOOST_ASSERT(
bytes_transferred <= s_.impl_->read_remain);
s_.impl_->read_remain -= bytes_transferred;
bytes_transferred <= impl_.read_remain);
impl_.read_remain -= bytes_transferred;
}
{
// try cancelling timer
auto const n =
s_.impl_->read_timer.cancel();
impl_.read_timer.cancel();
if(s_.impl_->read_closed)
if(impl_.read_closed)
{
// timeout handler already invoked
BOOST_ASSERT(n == 0);
ec = beast::error::timeout;
s_.impl_->read_closed = false;
impl_.read_closed = false;
}
else if(n == 0)
{
// timeout handler already queued
ec = beast::error::timeout;
s_.impl_->close();
s_.impl_->read_closed = true;
impl_.close();
impl_.read_closed = true;
}
else
{
@ -176,7 +199,7 @@ public:
upcall:
pg_.reset();
this->handler_(ec, bytes_transferred);
this->invoke(ec, bytes_transferred);
}
}
};
@ -186,59 +209,24 @@ public:
template<class Protocol, class Executor>
template<class Buffers, class Handler>
class basic_stream_socket<Protocol, Executor>::write_op
: public detail::operation_base<
Handler, Executor>
: public detail::async_op_base<Handler, Executor>
, public boost::asio::coroutine
{
basic_stream_socket& s_;
net::executor_work_guard<Executor> wg0_;
net::executor_work_guard<executor_type> wg1_;
typename basic_stream_socket<
Protocol, Executor>::impl_type& impl_;
pending_guard pg_;
Buffers b_;
struct timeout_handler
{
std::shared_ptr<impl_type> impl;
void
operator()(error_code ec)
{
// timer canceled
if(ec == net::error::operation_aborted)
return;
BOOST_ASSERT(! ec);
if(! impl->write_closed)
{
// timeout
impl->close();
impl->write_closed = true;
}
else
{
// late completion
impl->write_closed = false;
}
}
};
public:
template<class Handler_>
write_op(
basic_stream_socket& s,
Buffers const& b,
Handler_&& h)
: detail::operation_base<
Handler, Executor>(
std::forward<Handler_>(h),
net::get_associated_executor(
h, s.get_executor()))
, s_(s)
, wg0_(s_.get_executor())
, wg1_(net::get_associated_executor(
this->handler_, s_.get_executor()))
, pg_(s_.impl_->write_pending)
: detail::async_op_base<Handler, Executor>(
s.get_executor(), std::forward<Handler_>(h))
, impl_(*s.impl_)
, pg_(impl_.write_pending)
, b_(b)
{
(*this)();
@ -253,20 +241,21 @@ public:
{
// must come first
// VFALCO TODO what about the handler's allocator?
s_.impl_->write_timer.async_wait(
impl_.write_timer.async_wait(
net::bind_executor(
this->get_executor(),
timeout_handler{s_.impl_}));
write_timeout_handler{
impl_.shared_from_this()}));
s_.impl_->maybe_kick();
impl_.maybe_kick();
// check if the balance is zero
if(s_.impl_->write_remain == 0)
if(impl_.write_remain == 0)
{
// wait until the next time slice
++s_.impl_->waiting;
++impl_.waiting;
BOOST_ASIO_CORO_YIELD
s_.impl_->rate_timer.async_wait(std::move(*this));
impl_.rate_timer.async_wait(std::move(*this));
if(ec)
{
@ -277,46 +266,46 @@ public:
}
// must call this
s_.impl_->on_timer();
BOOST_ASSERT(s_.impl_->write_remain > 0);
impl_.on_timer();
BOOST_ASSERT(impl_.write_remain > 0);
}
// we always use buffers_prefix,
// to reduce template instantiations.
BOOST_ASSERT(s_.impl_->write_remain > 0);
BOOST_ASSERT(impl_.write_remain > 0);
BOOST_ASIO_CORO_YIELD
s_.impl_->socket.async_write_some(
impl_.socket.async_write_some(
beast::buffers_prefix(
s_.impl_->write_remain, b_),
impl_.write_remain, b_),
std::move(*this));
if(s_.impl_->write_remain != no_limit)
if(impl_.write_remain != no_limit)
{
// adjust balance
BOOST_ASSERT(
bytes_transferred <= s_.impl_->write_remain);
s_.impl_->write_remain -= bytes_transferred;
bytes_transferred <= impl_.write_remain);
impl_.write_remain -= bytes_transferred;
}
{
// try cancelling timer
auto const n =
s_.impl_->write_timer.cancel();
impl_.write_timer.cancel();
if(s_.impl_->write_closed)
if(impl_.write_closed)
{
// timeout handler already invoked
BOOST_ASSERT(n == 0);
ec = beast::error::timeout;
s_.impl_->write_closed = false;
impl_.write_closed = false;
}
else if(n == 0)
{
// timeout handler already queued
ec = beast::error::timeout;
s_.impl_->close();
s_.impl_->write_closed = true;
impl_.close();
impl_.write_closed = true;
}
else
{
@ -327,7 +316,7 @@ public:
upcall:
pg_.reset();
this->handler_(ec, bytes_transferred);
this->invoke(ec, bytes_transferred);
}
}
};
@ -339,46 +328,18 @@ namespace detail {
template<
class Protocol, class Executor, class Handler>
class stream_socket_connect_op
: public detail::operation_base<
Handler, Executor>
: public detail::async_op_base<Handler, Executor>
{
using stream_type =
beast::basic_stream_socket<Protocol, Executor>;
stream_type& s_;
net::executor_work_guard<Executor> wg0_;
net::executor_work_guard<typename
stream_type::executor_type> wg1_;
using timeout_handler =
typename stream_type::write_timeout_handler;
typename basic_stream_socket<
Protocol, Executor>::impl_type& impl_;
typename stream_type::pending_guard pg0_;
typename stream_type::pending_guard pg1_;
struct timeout_handler
{
std::shared_ptr<typename
stream_type::impl_type> impl;
void
operator()(error_code ec)
{
// timer canceled
if(ec == net::error::operation_aborted)
return;
BOOST_ASSERT(! ec);
if(! impl->write_closed)
{
// timeout
impl->close();
impl->write_closed = true;
}
else
{
// late completion
impl->write_closed = false;
}
}
};
public:
template<
class Endpoints, class Condition,
@ -388,26 +349,21 @@ public:
Endpoints const& eps,
Condition cond,
Handler_&& h)
: detail::operation_base<
Handler, Executor>(
std::forward<Handler_>(h),
net::get_associated_executor(
h, s.get_executor()))
, s_(s)
, wg0_(s_.get_executor())
, wg1_(net::get_associated_executor(
this->handler_, s_.get_executor()))
, pg0_(s_.impl_->read_pending)
, pg1_(s_.impl_->write_pending)
: detail::async_op_base<Handler, Executor>(
s.get_executor(), std::forward<Handler_>(h))
, impl_(*s.impl_)
, pg0_(impl_.read_pending)
, pg1_(impl_.write_pending)
{
// must come first
// VFALCO TODO what about the handler's allocator?
s_.impl_->write_timer.async_wait(
impl_.write_timer.async_wait(
net::bind_executor(
this->get_executor(),
timeout_handler{s_.impl_}));
timeout_handler{
impl_.shared_from_this()}));
net::async_connect(s_.impl_->socket,
net::async_connect(impl_.socket,
eps, cond, std::move(*this));
// *this is now moved-from
}
@ -420,25 +376,20 @@ public:
Iterator begin, Iterator end,
Condition cond,
Handler_&& h)
: detail::operation_base<
Handler, Executor>(
std::forward<Handler_>(h),
net::get_associated_executor(
h, s.get_executor()))
, s_(s)
, wg0_(s_.get_executor())
, wg1_(net::get_associated_executor(
this->handler_, s_.get_executor()))
, pg0_(s_.impl_->read_pending)
, pg1_(s_.impl_->write_pending)
: detail::async_op_base<Handler, Executor>(
s.get_executor(), std::forward<Handler_>(h))
, impl_(*s.impl_)
, pg0_(impl_.read_pending)
, pg1_(impl_.write_pending)
{
// must come first
s_.impl_->write_timer.async_wait(
impl_.write_timer.async_wait(
net::bind_executor(
this->get_executor(),
timeout_handler{s_.impl_}));
timeout_handler{
impl_.shared_from_this()}));
net::async_connect(s_.impl_->socket,
net::async_connect(impl_.socket,
begin, end, cond, std::move(*this));
// *this is now moved-from
}
@ -449,22 +400,22 @@ public:
{
// try to cancel the timer
auto const n =
s_.impl_->write_timer.cancel();
impl_.write_timer.cancel();
if(s_.impl_->write_closed)
if(impl_.write_closed)
{
// timeout handler already invoked
BOOST_ASSERT(n == 0);
ec = beast::error::timeout;
s_.impl_->write_closed = false;
impl_.write_closed = false;
}
else if(n == 0)
{
// timeout handler already queued
ec = beast::error::timeout;
s_.impl_->close();
s_.impl_->write_closed = true;
impl_.close();
impl_.write_closed = true;
}
else
{
@ -474,7 +425,7 @@ public:
pg0_.reset();
pg1_.reset();
this->handler_(ec, std::forward<Arg>(arg));
this->invoke(ec, std::forward<Arg>(arg));
}
};

View File

@ -69,15 +69,9 @@ public:
reference
operator*() const
{
#if 0
if(it_ == b_->begin_)
return value_type{*it_} + b_->skip_;
return value_type{*it_};
#else
return it_ == b_->begin_
? value_type{*it_} + b_->skip_
: *it_;
#endif
}
pointer

View File

@ -13,16 +13,11 @@
#include <boost/beast/http/type_traits.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/buffers_range.hpp>
#include <boost/beast/core/handler_ptr.hpp>
#include <boost/beast/core/ostream.hpp>
#include <boost/beast/core/type_traits.hpp>
#include <boost/beast/core/detail/config.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/beast/core/detail/get_executor_type.hpp>
#include <boost/beast/core/detail/async_op_base.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/asio/handler_continuation_hook.hpp>
#include <boost/asio/handler_invoke_hook.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/write.hpp>
#include <boost/optional.hpp>
@ -39,12 +34,11 @@ template<
class Stream, class Handler,
bool isRequest, class Body, class Fields>
class write_some_op
: public beast::detail::async_op_base<
Handler, beast::detail::get_executor_type<Stream>>
{
Stream& s_;
net::executor_work_guard<decltype(
std::declval<Stream&>().get_executor())> wg_;
serializer<isRequest,Body, Fields>& sr_;
Handler h_;
class lambda
{
@ -61,122 +55,73 @@ class write_some_op
template<class ConstBufferSequence>
void
operator()(error_code& ec,
operator()(
error_code& ec,
ConstBufferSequence const& buffers)
{
invoked = true;
ec.assign(0, ec.category());
return op_.s_.async_write_some(
ec = {};
op_.s_.async_write_some(
buffers, std::move(op_));
}
};
public:
write_some_op(write_some_op&&) = default;
write_some_op(write_some_op const&) = delete;
template<class DeducedHandler>
write_some_op(DeducedHandler&& h, Stream& s,
serializer<isRequest, Body, Fields>& sr)
: s_(s)
, wg_(s_.get_executor())
template<class Handler_>
write_some_op(
Handler_&& h,
Stream& s,
serializer<isRequest, Body, Fields>& sr)
: beast::detail::async_op_base<
Handler, beast::detail::get_executor_type<Stream>>(
s.get_executor(), std::forward<Handler_>(h))
, s_(s)
, sr_(sr)
, h_(std::forward<DeducedHandler>(h))
{
}
using allocator_type =
net::associated_allocator_t<Handler>;
allocator_type
get_allocator() const noexcept
{
return (net::get_associated_allocator)(h_);
}
using executor_type = net::associated_executor_t<
Handler, decltype(std::declval<Stream&>().get_executor())>;
executor_type
get_executor() const noexcept
{
return (net::get_associated_executor)(
h_, s_.get_executor());
(*this)();
}
void
operator()();
operator()()
{
error_code ec;
if(! sr_.is_done())
{
lambda f{*this};
sr_.next(ec, f);
if(ec)
{
BOOST_ASSERT(! f.invoked);
return net::post(
s_.get_executor(),
beast::bind_front_handler(
std::move(*this), ec, 0));
}
if(f.invoked)
{
// *this is now moved-from,
return;
}
// What else could it be?
BOOST_ASSERT(sr_.is_done());
}
return net::post(
s_.get_executor(),
beast::bind_front_handler(
std::move(*this), ec, 0));
}
void
operator()(
error_code ec,
std::size_t bytes_transferred);
friend
bool asio_handler_is_continuation(write_some_op* op)
std::size_t bytes_transferred)
{
using net::asio_handler_is_continuation;
return asio_handler_is_continuation(
std::addressof(op->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_some_op* op)
{
using net::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(op->h_));
if(! ec)
sr_.consume(bytes_transferred);
this->invoke(ec, bytes_transferred);
}
};
template<
class Stream, class Handler,
bool isRequest, class Body, class Fields>
void
write_some_op<
Stream, Handler, isRequest, Body, Fields>::
operator()()
{
error_code ec;
if(! sr_.is_done())
{
lambda f{*this};
sr_.next(ec, f);
if(ec)
{
BOOST_ASSERT(! f.invoked);
return net::post(
s_.get_executor(),
beast::bind_front_handler(std::move(*this), ec, 0));
}
if(f.invoked)
{
// *this has been moved from,
// cannot access members here.
return;
}
// What else could it be?
BOOST_ASSERT(sr_.is_done());
}
return net::post(
s_.get_executor(),
beast::bind_front_handler(std::move(*this), ec, 0));
}
template<
class Stream, class Handler,
bool isRequest, class Body, class Fields>
void
write_some_op<
Stream, Handler, isRequest, Body, Fields>::
operator()(
error_code ec, std::size_t bytes_transferred)
{
if(! ec)
sr_.consume(bytes_transferred);
h_(ec, bytes_transferred);
}
//------------------------------------------------------------------------------
struct serializer_is_header_done
@ -208,220 +153,107 @@ struct serializer_is_done
template<
class Stream, class Handler, class Predicate,
bool isRequest, class Body, class Fields>
class write_op : public net::coroutine
class write_op
: public beast::detail::async_op_base<
Handler, beast::detail::get_executor_type<Stream>>
, public net::coroutine
{
Stream& s_;
net::executor_work_guard<decltype(
std::declval<Stream&>().get_executor())> wg_;
serializer<isRequest, Body, Fields>& sr_;
std::size_t bytes_transferred_ = 0;
Handler h_;
bool cont_;
public:
write_op(write_op&&) = default;
write_op(write_op const&) = delete;
template<class DeducedHandler>
write_op(DeducedHandler&& h, Stream& s,
serializer<isRequest, Body, Fields>& sr)
: s_(s)
, wg_(s_.get_executor())
template<class Handler_>
write_op(
Handler_&& h,
Stream& s,
serializer<isRequest, Body, Fields>& sr)
: beast::detail::async_op_base<
Handler, beast::detail::get_executor_type<Stream>>(
s.get_executor(), std::forward<Handler_>(h))
, s_(s)
, sr_(sr)
, h_(std::forward<DeducedHandler>(h))
, cont_([&]
{
using net::asio_handler_is_continuation;
return asio_handler_is_continuation(
std::addressof(h_));
}())
{
}
using allocator_type =
net::associated_allocator_t<Handler>;
allocator_type
get_allocator() const noexcept
{
return (net::get_associated_allocator)(h_);
}
using executor_type = net::associated_executor_t<
Handler, decltype(std::declval<Stream&>().get_executor())>;
executor_type
get_executor() const noexcept
{
return (net::get_associated_executor)(
h_, s_.get_executor());
(*this)();
}
void
operator()(
error_code ec = {},
std::size_t bytes_transferred = 0);
friend
bool asio_handler_is_continuation(write_op* op)
std::size_t bytes_transferred = 0)
{
return op->cont_;
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_op* op)
{
using net::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(op->h_));
BOOST_ASIO_CORO_REENTER(*this)
{
if(Predicate{}(sr_))
{
BOOST_ASIO_CORO_YIELD
net::post(
s_.get_executor(),
std::move(*this));
goto upcall;
}
for(;;)
{
BOOST_ASIO_CORO_YIELD
beast::http::async_write_some(
s_, sr_, std::move(*this));
bytes_transferred_ += bytes_transferred;
if(ec)
goto upcall;
if(Predicate{}(sr_))
break;
}
upcall:
this->invoke(ec, bytes_transferred_);
}
}
};
template<
class Stream, class Handler, class Predicate,
bool isRequest, class Body, class Fields>
void
write_op<Stream, Handler, Predicate,
isRequest, Body, Fields>::
operator()(
error_code ec,
std::size_t bytes_transferred)
{
BOOST_ASIO_CORO_REENTER(*this)
{
if(Predicate{}(sr_))
{
BOOST_ASIO_CORO_YIELD
net::post(
s_.get_executor(),
std::move(*this));
goto upcall;
}
for(;;)
{
BOOST_ASIO_CORO_YIELD
beast::http::async_write_some(
s_, sr_, std::move(*this));
bytes_transferred_ += bytes_transferred;
if(ec)
goto upcall;
if(Predicate{}(sr_))
break;
cont_ = true;
}
upcall:
h_(ec, bytes_transferred_);
}
}
//------------------------------------------------------------------------------
template<class Stream, class Handler,
template<
class Stream, class Handler,
bool isRequest, class Body, class Fields>
class write_msg_op
: public beast::detail::stable_async_op_base<
Handler, beast::detail::get_executor_type<Stream>>
{
struct data
{
Stream& s;
net::executor_work_guard<decltype(
std::declval<Stream&>().get_executor())> wg;
serializer<isRequest, Body, Fields> sr;
data(Handler const&, Stream& s_, message<
isRequest, Body, Fields>& m_)
: s(s_)
, wg(s.get_executor())
, sr(m_)
{
}
data(Handler const&, Stream& s_, message<
isRequest, Body, Fields> const& m_)
: s(s_)
, wg(s.get_executor())
, sr(m_)
{
}
};
handler_ptr<data, Handler> d_;
Stream& s_;
serializer<isRequest, Body, Fields>& sr_;
public:
write_msg_op(write_msg_op&&) = default;
write_msg_op(write_msg_op const&) = delete;
template<class DeducedHandler, class... Args>
write_msg_op(DeducedHandler&& h, Stream& s, Args&&... args)
: d_(std::forward<DeducedHandler>(h),
s, std::forward<Args>(args)...)
template<
class Handler_,
class... Args>
write_msg_op(
Stream& s,
Handler_&& h,
Args&&... args)
: beast::detail::stable_async_op_base<
Handler, beast::detail::get_executor_type<Stream>>(
s.get_executor(), std::forward<Handler_>(h))
, s_(s)
, sr_(beast::detail::allocate_stable<
serializer<isRequest, Body, Fields>>(
*this, std::forward<Args>(args)...))
{
}
using allocator_type =
net::associated_allocator_t<Handler>;
allocator_type
get_allocator() const noexcept
{
return (net::get_associated_allocator)(d_.handler());
}
using executor_type = net::associated_executor_t<
Handler, decltype(std::declval<Stream&>().get_executor())>;
executor_type
get_executor() const noexcept
{
return (net::get_associated_executor)(
d_.handler(), d_->s.get_executor());
(*this)();
}
void
operator()();
operator()()
{
async_write(s_, sr_, std::move(*this));
}
void
operator()(
error_code ec, std::size_t bytes_transferred);
friend
bool asio_handler_is_continuation(write_msg_op* op)
error_code ec, std::size_t bytes_transferred)
{
using net::asio_handler_is_continuation;
return asio_handler_is_continuation(
std::addressof(op->d_.handler()));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_msg_op* op)
{
using net::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(op->d_.handler()));
this->invoke(ec, bytes_transferred);
}
};
template<class Stream, class Handler,
bool isRequest, class Body, class Fields>
void
write_msg_op<
Stream, Handler, isRequest, Body, Fields>::
operator()()
{
auto& d = *d_;
return async_write(d.s, d.sr, std::move(*this));
}
template<class Stream, class Handler,
bool isRequest, class Body, class Fields>
void
write_msg_op<
Stream, Handler, isRequest, Body, Fields>::
operator()(error_code ec, std::size_t bytes_transferred)
{
auto wg = std::move(d_->wg);
d_.invoke(ec, bytes_transferred);
}
//------------------------------------------------------------------------------
template<class Stream>
@ -516,8 +348,8 @@ async_write_some_impl(
AsyncWriteStream,
BOOST_ASIO_HANDLER_TYPE(WriteHandler,
void(error_code, std::size_t)),
isRequest, Body, Fields>{
std::move(init.completion_handler), stream, sr}();
isRequest, Body, Fields>(
std::move(init.completion_handler), stream, sr);
return init.result.get();
}
@ -675,7 +507,7 @@ async_write_header(
void(error_code, std::size_t)),
detail::serializer_is_header_done,
isRequest, Body, Fields>{
std::move(init.completion_handler), stream, sr}();
std::move(init.completion_handler), stream, sr};
return init.result.get();
}
@ -751,7 +583,7 @@ async_write(
void(error_code, std::size_t)),
detail::serializer_is_done,
isRequest, Body, Fields>{
std::move(init.completion_handler), stream, sr}();
std::move(init.completion_handler), stream, sr};
return init.result.get();
}
@ -873,8 +705,8 @@ async_write(
AsyncWriteStream,
BOOST_ASIO_HANDLER_TYPE(WriteHandler,
void(error_code, std::size_t)),
isRequest, Body, Fields>{
std::move(init.completion_handler), stream, msg}();
isRequest, Body, Fields>(stream,
std::move(init.completion_handler), msg);
return init.result.get();
}
@ -904,8 +736,8 @@ async_write(
AsyncWriteStream,
BOOST_ASIO_HANDLER_TYPE(WriteHandler,
void(error_code, std::size_t)),
isRequest, Body, Fields>{
std::move(init.completion_handler), stream, msg}();
isRequest, Body, Fields>(stream,
std::move(init.completion_handler), msg);
return init.result.get();
}

View File

@ -16,10 +16,10 @@ add_executable (tests-beast-core
${EXTRAS_FILES}
${TEST_MAIN}
Jamfile
_detail_async_op_base.cpp
_detail_base64.cpp
_detail_buffer.cpp
_detail_clamp.cpp
_detail_operation_base.cpp
_detail_read.cpp
_detail_sha1.cpp
_detail_tuple.cpp

View File

@ -8,10 +8,10 @@
#
local SOURCES =
_detail_async_op_base.cpp
_detail_base64.cpp
_detail_buffer.cpp
_detail_clamp.cpp
_detail_operation_base.cpp
_detail_read.cpp
_detail_sha1.cpp
_detail_tuple.cpp

View File

@ -0,0 +1,569 @@
//
// Copyright (c) 2016-2017 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
//
// Test that header file is self-contained.
#include <boost/beast/core/detail/async_op_base.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/beast/core/buffers_suffix.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/system_executor.hpp>
#include <boost/core/ignore_unused.hpp>
//------------------------------------------------------------------------------
namespace boost {
namespace beast {
namespace detail {
//namespace {
struct ex1_type
{
void* context() { return nullptr; }
void on_work_started() {}
void on_work_finished() {}
template<class F> void dispatch(F&&) {}
template<class F> void post(F&&) {}
template<class F> void defer(F&&) {}
};
struct no_alloc
{
};
struct nested_alloc
{
struct allocator_type
{
};
};
struct intrusive_alloc
{
struct allocator_type
{
};
};
struct no_ex
{
using executor_type = net::system_executor;
};
struct nested_ex
{
struct executor_type
{
};
};
struct intrusive_ex
{
struct executor_type
{
};
};
template<class E, class A>
struct handler;
template<>
struct handler<no_ex, no_alloc>
{
};
template<>
struct handler<no_ex, nested_alloc>
: nested_alloc
{
};
template<>
struct handler<no_ex, intrusive_alloc>
{
};
template<>
struct handler<nested_ex, no_alloc>
: nested_ex
{
};
template<>
struct handler<intrusive_ex, no_alloc>
{
};
struct legacy_handler
{
bool invoked = false;
struct executor
{
void* context() { return nullptr; }
void on_work_started() {}
void on_work_finished() {}
template<class F> void dispatch(F&&) {}
template<class F> void post(F&&) {}
template<class F> void defer(F&&) {}
};
executor
get_executor() const noexcept
{
return {};
};
};
template<class Function>
void
asio_handler_invoke(
Function&& f,
legacy_handler* p)
{
p->invoked = true;
f();
}
void*
asio_handler_allocate(
std::size_t,
legacy_handler* p)
{
p->invoked = true;
return nullptr;
}
void
asio_handler_deallocate(
void*, std::size_t,
legacy_handler* p)
{
p->invoked = true;
}
bool
asio_handler_is_continuation(
legacy_handler* p)
{
p->invoked = true;
return false;
}
//} // (anon)
} // detail
} // beast
} // boost
namespace boost {
namespace asio {
template<class Allocator>
struct associated_allocator<
boost::beast::detail::handler<
boost::beast::detail::no_ex,
boost::beast::detail::intrusive_alloc>,
Allocator>
{
using type =
boost::beast::detail::intrusive_alloc::allocator_type;
static type get(
boost::beast::detail::handler<
boost::beast::detail::no_ex,
boost::beast::detail::intrusive_alloc> const& h,
Allocator const& a = Allocator()) noexcept
{
return type{};
}
};
template<class Executor>
struct associated_executor<
boost::beast::detail::handler<
boost::beast::detail::intrusive_ex,
boost::beast::detail::no_alloc>,
Executor>
{
using type =
boost::beast::detail::intrusive_ex::executor_type;
static type get(
boost::beast::detail::handler<
boost::beast::detail::intrusive_ex,
boost::beast::detail::no_alloc> const& h,
Executor const& a = Executor()) noexcept
{
return type{};
}
};
template<class Allocator>
struct associated_allocator<
boost::beast::detail::legacy_handler, Allocator>
{
using type = std::allocator<int>;
static type get(
boost::beast::detail::legacy_handler const& h,
Allocator const& a = Allocator()) noexcept
{
return type{};
}
};
template<class Executor>
struct associated_executor<
boost::beast::detail::legacy_handler, Executor>
{
using type = typename
boost::beast::detail::legacy_handler::executor;
static type get(
boost::beast::detail::legacy_handler const&,
Executor const& = Executor()) noexcept
{
return type{};
}
};
} // asio
} // boost
//------------------------------------------------------------------------------
namespace boost {
namespace beast {
namespace detail {
class async_op_base_test : public beast::unit_test::suite
{
public:
// no associated allocator
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<void>,
net::associated_allocator_t<
async_op_base<
handler<no_ex, no_alloc>,
net::io_context::executor_type>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<int>,
net::associated_allocator_t<
async_op_base<
handler<no_ex, no_alloc>,
net::io_context::executor_type,
std::allocator<int>>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<void>,
net::associated_allocator_t<
async_op_base<
handler<no_ex, no_alloc>,
net::io_context::executor_type>,
std::allocator<int> // ignored
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<int>,
net::associated_allocator_t<
async_op_base<
handler<no_ex, no_alloc>,
net::io_context::executor_type,
std::allocator<int>>,
std::allocator<double> // ignored
>>::value);
// nested associated allocator
BOOST_STATIC_ASSERT(
std::is_same<
nested_alloc::allocator_type,
net::associated_allocator_t<
async_op_base<
handler<no_ex, nested_alloc>,
net::io_context::executor_type>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
nested_alloc::allocator_type,
net::associated_allocator_t<
async_op_base<
handler<no_ex, nested_alloc>,
net::io_context::executor_type,
std::allocator<int>> // ignored
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
nested_alloc::allocator_type,
net::associated_allocator_t<
async_op_base<
handler<no_ex, nested_alloc>,
net::io_context::executor_type>,
std::allocator<int> // ignored
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
nested_alloc::allocator_type,
net::associated_allocator_t<
async_op_base<
handler<no_ex, nested_alloc>,
net::io_context::executor_type,
std::allocator<int>>, // ignored
std::allocator<int> // ignored
>>::value);
// intrusive associated allocator
BOOST_STATIC_ASSERT(
std::is_same<
intrusive_alloc::allocator_type,
net::associated_allocator_t<
async_op_base<
handler<no_ex, intrusive_alloc>,
net::io_context::executor_type>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
intrusive_alloc::allocator_type,
net::associated_allocator_t<
async_op_base<
handler<no_ex, intrusive_alloc>,
net::io_context::executor_type,
std::allocator<int>> // ignored
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
intrusive_alloc::allocator_type,
net::associated_allocator_t<
async_op_base<
handler<no_ex, intrusive_alloc>,
net::io_context::executor_type>,
std::allocator<int> // ignored
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
intrusive_alloc::allocator_type,
net::associated_allocator_t<
async_op_base<
handler<no_ex, intrusive_alloc>,
net::io_context::executor_type,
std::allocator<int>>, // ignored
std::allocator<int> // ignored
>>::value);
// no associated executor
BOOST_STATIC_ASSERT(
std::is_same<
ex1_type,
net::associated_executor_t<
async_op_base<
handler<no_ex, no_alloc>,
ex1_type>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
ex1_type,
net::associated_executor_t<
async_op_base<
handler<no_ex, no_alloc>,
ex1_type>,
net::system_executor // ignored
>>::value);
// nested associated executor
BOOST_STATIC_ASSERT(
std::is_same<
nested_ex::executor_type,
net::associated_executor_t<
async_op_base<
handler<nested_ex, no_alloc>,
ex1_type>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
nested_ex::executor_type,
net::associated_executor_t<
async_op_base<
handler<nested_ex, no_alloc>,
ex1_type>,
net::system_executor // ignored
>>::value);
// intrusive associated executor
BOOST_STATIC_ASSERT(
std::is_same<
intrusive_ex::executor_type,
net::associated_executor_t<
async_op_base<
handler<intrusive_ex, no_alloc>,
ex1_type>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
intrusive_ex::executor_type,
net::associated_executor_t<
async_op_base<
handler<intrusive_ex, no_alloc>,
ex1_type>,
net::system_executor // ignored
>>::value);
struct test_op : async_op_base<
legacy_handler, ex1_type>
{
test_op()
: async_op_base<
legacy_handler,
ex1_type>(
ex1_type{}, legacy_handler{})
{
}
bool invoked() const noexcept
{
return this->handler().invoked;
}
};
void
testLegacyHooks()
{
// asio_handler_invoke
{
test_op h;
BEAST_EXPECT(! h.invoked());
bool invoked = false;
using net::asio_handler_invoke;
asio_handler_invoke(
[&invoked]
{
invoked =true;
}, &h);
BEAST_EXPECT(invoked);
BEAST_EXPECT(h.invoked());
}
// asio_handler_allocate
{
test_op h;
BEAST_EXPECT(! h.invoked());
using net::asio_handler_allocate;
asio_handler_allocate(0, &h);
BEAST_EXPECT(h.invoked());
}
// asio_handler_deallocate
{
test_op h;
BEAST_EXPECT(! h.invoked());
using net::asio_handler_deallocate;
asio_handler_deallocate(nullptr, 0, &h);
BEAST_EXPECT(h.invoked());
}
// asio_handler_deallocate
{
test_op h;
BEAST_EXPECT(! h.invoked());
using net::asio_handler_is_continuation;
asio_handler_is_continuation(&h);
BEAST_EXPECT(h.invoked());
}
}
void
testSpecialMembers()
{
test_op h1;
test_op h2(std::move(h1));
}
//--------------------------------------------------------------------------
#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)
{
net::async_completion<CompletionToken, void(error_code, std::size_t)> init{handler};
using base = async_op_base<
BOOST_ASIO_HANDLER_TYPE(ReadHandler, void(error_code, std::size_t)),
get_executor_type<AsyncReadStream>>;
struct read_op : base
{
AsyncReadStream& stream_;
buffers_suffix<MutableBufferSequence> buffers_;
void operator()(error_code ec, std::size_t bytes_transferred)
{
}
};
read_op(stream, buffers, std::forward<ReadHandler>(handler));
return init.result.get();
}
#endif
void
testJavadocs()
{
}
//--------------------------------------------------------------------------
void
run() override
{
testLegacyHooks();
testSpecialMembers();
testJavadocs();
}
};
BEAST_DEFINE_TESTSUITE(beast,core,async_op_base);
} // detail
} // beast
} // boost

View File

@ -1,500 +0,0 @@
//
// Copyright (c) 2016-2017 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
//
// Test that header file is self-contained.
#include <boost/beast/core/detail/operation_base.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/core/ignore_unused.hpp>
//------------------------------------------------------------------------------
namespace boost {
namespace beast {
namespace detail {
namespace {
struct specialized_handler
{
bool invoked = false;
struct executor
{
void* context() { return nullptr; }
void on_work_started() {}
void on_work_finished() {}
template<class F> void dispatch(F&&) {}
template<class F> void post(F&&) {}
template<class F> void defer(F&&) {}
};
};
template<class Function>
static
void
asio_handler_invoke(
Function&& f,
specialized_handler* p)
{
p->invoked = true;
f();
}
static
void*
asio_handler_allocate(
std::size_t,
specialized_handler* p)
{
p->invoked = true;
return nullptr;
}
static
void
asio_handler_deallocate(
void*, std::size_t,
specialized_handler* p)
{
p->invoked = true;
}
static bool
asio_handler_is_continuation(
specialized_handler* p)
{
p->invoked = true;
return false;
}
} // <anonymous>
} // detail
} // beast
} // boost
//------------------------------------------------------------------------------
namespace boost {
namespace asio {
template<class Allocator>
struct associated_allocator<
boost::beast::detail::specialized_handler, Allocator>
{
using type = std::allocator<int>;
static type get(
boost::beast::detail::specialized_handler const& h,
Allocator const& a = Allocator()) noexcept
{
return type{};
}
};
template<class Executor>
struct associated_executor<
boost::beast::detail::specialized_handler, Executor>
{
using type = typename
boost::beast::detail::specialized_handler::executor;
static type get(
boost::beast::detail::specialized_handler const& h,
Executor const& ex = Executor()) noexcept
{
return type{};
}
};
} // asio
} // boost
//------------------------------------------------------------------------------
namespace boost {
namespace beast {
namespace detail {
class operation_base_test : public beast::unit_test::suite
{
public:
using default_alloc = std::allocator<void>;
using default_exec = net::system_executor;
struct U {};
struct V {};
template<class E>
struct executor
{
void* context() { return nullptr; }
void on_work_started() {}
void on_work_finished() {}
template<class F> void dispatch(F&&) {}
template<class F> void post(F&&) {}
template<class F> void defer(F&&) {}
};
struct none
{
void operator()() const
{
}
};
struct with_alloc
{
using allocator_type = std::allocator<U>;
};
struct with_exec
{
using executor_type = executor<U>;
};
struct move_only
{
move_only() = default;
move_only(move_only&&) = default;
move_only(move_only const&) = delete;
void operator()() const{};
};
template<
class H,
class E = default_exec,
class A = default_alloc>
using tested_base =
operation_base<H, E, A>;
struct movable_handler : tested_base<move_only>
{
movable_handler()
: tested_base<move_only>(move_only{})
{
}
};
struct test_handler :
tested_base<specialized_handler>
{
test_handler()
: tested_base<
specialized_handler>(
specialized_handler{})
{
}
bool invoked() const noexcept
{
return this->handler_.invoked;
}
};
// no nested allocator type
BOOST_STATIC_ASSERT(
std::is_same<
default_alloc,
net::associated_allocator_t<
tested_base<none>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
default_alloc,
decltype(net::get_associated_allocator(
std::declval<tested_base<none>>()
))>::value);
BOOST_STATIC_ASSERT(
std::is_same<
default_alloc,
decltype(net::get_associated_allocator(
std::declval<tested_base<none>>()
))>::value);
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<U>,
decltype(net::get_associated_allocator(std::declval<
tested_base<none, default_exec, std::allocator<U>>>()
))>::value);
// shouldn't work due to net.ts limitations
BOOST_STATIC_ASSERT(
! std::is_same<
std::allocator<U>,
decltype(net::get_associated_allocator(
std::declval<tested_base<none>>(),
std::declval<std::allocator<U>>()
))>::value);
BOOST_STATIC_ASSERT(
std::is_same<
default_alloc,
decltype(net::get_associated_allocator(
std::declval<tested_base<none>>(),
std::declval<std::allocator<U>>()
))>::value);
// nested allocator_type
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<U>,
net::associated_allocator_t<
tested_base<with_alloc>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<U>,
decltype(net::get_associated_allocator(
std::declval<tested_base<with_alloc>>()
))>::value);
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<U>,
decltype(net::get_associated_allocator(
std::declval<tested_base<with_alloc>>(),
std::declval<std::allocator<V>>()
))>::value);
// specialization of associated_allocator
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<int>,
net::associated_allocator_t<
tested_base<
specialized_handler>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<int>,
decltype(net::get_associated_allocator(
std::declval<tested_base<
specialized_handler>>()
))>::value);
BOOST_STATIC_ASSERT(
std::is_same<
std::allocator<int>,
decltype(net::get_associated_allocator(
std::declval<tested_base<
specialized_handler>>(),
std::declval<std::allocator<V>>()
))>::value);
// no nested executor type
BOOST_STATIC_ASSERT(
std::is_same<
default_exec,
net::associated_executor_t<
tested_base<none>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
executor<U>,
net::associated_executor_t<
tested_base<none, executor<U>>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
default_exec,
decltype(net::get_associated_executor(
std::declval<tested_base<none>>()
))>::value);
BOOST_STATIC_ASSERT(
std::is_same<
executor<U>,
decltype(net::get_associated_executor(std::declval<
tested_base<none, executor<U>>>()
))>::value);
// shouldn't work due to net.ts limitations
BOOST_STATIC_ASSERT(
! std::is_same<
executor<U>,
decltype(net::get_associated_executor(
std::declval<tested_base<none>>(),
std::declval<executor<U>>()
))>::value);
BOOST_STATIC_ASSERT(
std::is_same<
default_exec,
decltype(net::get_associated_executor(
std::declval<tested_base<none>>(),
std::declval<executor<U>>()
))>::value);
// nested executor_type
BOOST_STATIC_ASSERT(
std::is_same<
executor<U>,
net::associated_executor_t<
tested_base<with_exec>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
executor<U>,
decltype(net::get_associated_executor(
std::declval<tested_base<with_exec>>()
))>::value);
BOOST_STATIC_ASSERT(
std::is_same<
executor<U>,
decltype(net::get_associated_executor(
std::declval<tested_base<with_exec>>(),
std::declval<executor<V>>()
))>::value);
// specialization of associated_executor
BOOST_STATIC_ASSERT(
std::is_same<
specialized_handler::executor,
net::associated_executor_t<
tested_base<
specialized_handler>
>>::value);
BOOST_STATIC_ASSERT(
std::is_same<
specialized_handler::executor,
decltype(net::get_associated_executor(std::declval<
tested_base<
specialized_handler>>()
))>::value);
BOOST_STATIC_ASSERT(
std::is_same<
specialized_handler::executor,
decltype(net::get_associated_executor(std::declval<
tested_base<
specialized_handler>>(),
std::declval<executor<V>>()
))>::value);
//--------------------------------------------------------------------------
template <class Handler>
struct wrapped_handler : operation_base<
Handler, net::associated_executor_t<Handler>>
{
template <class Handler_>
explicit wrapped_handler (Handler_&& handler)
: operation_base<Handler, net::associated_executor_t <Handler>>(
std::forward<Handler_>(handler), net::get_associated_executor(handler))
{
}
template <class... Args>
void operator()(Args&&... args)
{
this->handler_(std::forward<Args>(args)...);
}
};
void
testJavadocs()
{
wrapped_handler<none>{none{}}();
}
//--------------------------------------------------------------------------
void
testLegacyHooks()
{
// asio_handler_invoke
{
test_handler h;
BEAST_EXPECT(! h.invoked());
bool invoked = false;
using net::asio_handler_invoke;
asio_handler_invoke(
[&invoked]
{
invoked =true;
}, &h);
BEAST_EXPECT(invoked);
BEAST_EXPECT(h.invoked());
}
// asio_handler_allocate
{
test_handler h;
BEAST_EXPECT(! h.invoked());
using net::asio_handler_allocate;
asio_handler_allocate(0, &h);
BEAST_EXPECT(h.invoked());
}
// asio_handler_deallocate
{
test_handler h;
BEAST_EXPECT(! h.invoked());
using net::asio_handler_deallocate;
asio_handler_deallocate(nullptr, 0, &h);
BEAST_EXPECT(h.invoked());
}
// asio_handler_deallocate
{
test_handler h;
BEAST_EXPECT(! h.invoked());
using net::asio_handler_is_continuation;
asio_handler_is_continuation(&h);
BEAST_EXPECT(h.invoked());
}
}
void
testSpecialMembers()
{
{
test_handler h1;
test_handler h2(std::move(h1));
test_handler h3(h2);
boost::ignore_unused(h3);
}
{
movable_handler h1;
movable_handler h2(std::move(h1));
boost::ignore_unused(h2);
}
}
void
run() override
{
testJavadocs();
testLegacyHooks();
testSpecialMembers();
}
};
BEAST_DEFINE_TESTSUITE(beast,core,operation_base);
} // detail
} // beast
} // boost