forked from boostorg/beast
beast support per-op cancellation
- websocket supports cancellation. - Iterating test for ws cancellation. - Only terminal cancellation is forwarded by default. - basic_stream supports cancellation. - supported cancellation is documented. - http cancellation additions. - Added cancellation_slot tests to http, utils and saved_handler. - Added post to write.cpp, to avoid SIGSEV in test. - Refresher describes cancellation in more detail.
This commit is contained in:
committed by
Klemens Morgenstern
parent
0bf3d971a0
commit
3ebff60b1a
@@ -14,6 +14,7 @@
|
||||
#include <boost/beast/core/bind_handler.hpp>
|
||||
#include <boost/beast/core/detail/allocator.hpp>
|
||||
#include <boost/beast/core/detail/async_base.hpp>
|
||||
#include <boost/beast/core/detail/filtering_cancellation_slot.hpp>
|
||||
#include <boost/beast/core/detail/work_guard.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
@@ -187,7 +188,7 @@ class async_base
|
||||
|
||||
Handler h_;
|
||||
detail::select_work_guard_t<Executor1> wg1_;
|
||||
|
||||
net::cancellation_type act_{net::cancellation_type::terminal};
|
||||
public:
|
||||
/** The type of executor associated with this object.
|
||||
|
||||
@@ -308,6 +309,38 @@ public:
|
||||
h_, wg1_.get_executor());
|
||||
}
|
||||
|
||||
/** The type of cancellation_slot associated with this object.
|
||||
|
||||
If a class derived from @ref async_base is a completion
|
||||
handler, then the associated cancellation_slot of the
|
||||
derived class will be this type.
|
||||
|
||||
The default type is a filtering cancellation slot,
|
||||
that only allows terminal cancellation.
|
||||
*/
|
||||
using cancellation_slot_type =
|
||||
beast::detail::filtering_cancellation_slot<net::associated_cancellation_slot_t<Handler>>;
|
||||
|
||||
/** Returns the cancellation_slot associated with this object.
|
||||
|
||||
If a class derived from @ref async_base is a completion
|
||||
handler, then the object returned from this function will be used
|
||||
as the associated cancellation_slot of the derived class.
|
||||
*/
|
||||
cancellation_slot_type
|
||||
get_cancellation_slot() const noexcept
|
||||
{
|
||||
return cancellation_slot_type(act_, net::get_associated_cancellation_slot(h_,
|
||||
net::cancellation_slot()));
|
||||
}
|
||||
|
||||
/// Set the allowed cancellation types, default is `terminal`.
|
||||
void set_allowed_cancellation(
|
||||
net::cancellation_type allowed_cancellation_types = net::cancellation_type::terminal)
|
||||
{
|
||||
act_ = allowed_cancellation_types;
|
||||
}
|
||||
|
||||
/// Returns the handler associated with this object
|
||||
Handler const&
|
||||
handler() const noexcept
|
||||
|
||||
@@ -919,6 +919,17 @@ public:
|
||||
this function. Invocation of the handler will be performed in a
|
||||
manner equivalent to using `net::post`.
|
||||
|
||||
@par Per-Operation Cancellation
|
||||
|
||||
This asynchronous operation supports cancellation for the following
|
||||
net::cancellation_type values:
|
||||
|
||||
@li @c net::cancellation_type::terminal
|
||||
@li @c net::cancellation_type::partial
|
||||
@li @c net::cancellation_type::total
|
||||
|
||||
if they are also supported by the socket's @c async_connect operation.
|
||||
|
||||
@see async_connect
|
||||
*/
|
||||
template<
|
||||
@@ -972,6 +983,18 @@ public:
|
||||
immediately or not, the handler will not be invoked from within
|
||||
this function. Invocation of the handler will be performed in a
|
||||
manner equivalent to using `net::post`.
|
||||
|
||||
@par Per-Operation Cancellation
|
||||
|
||||
This asynchronous operation supports cancellation for the following
|
||||
net::cancellation_type values:
|
||||
|
||||
@li @c net::cancellation_type::terminal
|
||||
@li @c net::cancellation_type::partial
|
||||
@li @c net::cancellation_type::total
|
||||
|
||||
if they are also supported by the socket's @c async_connect operation.
|
||||
|
||||
*/
|
||||
template<
|
||||
class EndpointSequence,
|
||||
@@ -1064,6 +1087,17 @@ public:
|
||||
}
|
||||
};
|
||||
@endcode
|
||||
|
||||
@par Per-Operation Cancellation
|
||||
|
||||
This asynchronous operation supports cancellation for the following
|
||||
net::cancellation_type values:
|
||||
|
||||
@li @c net::cancellation_type::terminal
|
||||
@li @c net::cancellation_type::partial
|
||||
@li @c net::cancellation_type::total
|
||||
|
||||
if they are also supported by the socket's @c async_connect operation.
|
||||
*/
|
||||
template<
|
||||
class EndpointSequence,
|
||||
@@ -1129,6 +1163,17 @@ public:
|
||||
immediately or not, the handler will not be invoked from within
|
||||
this function. Invocation of the handler will be performed in a
|
||||
manner equivalent to using `net::post`.
|
||||
|
||||
@par Per-Operation Cancellation
|
||||
|
||||
This asynchronous operation supports cancellation for the following
|
||||
net::cancellation_type values:
|
||||
|
||||
@li @c net::cancellation_type::terminal
|
||||
@li @c net::cancellation_type::partial
|
||||
@li @c net::cancellation_type::total
|
||||
|
||||
if they are also supported by the socket's @c async_connect operation.
|
||||
*/
|
||||
template<
|
||||
class Iterator,
|
||||
@@ -1189,6 +1234,17 @@ public:
|
||||
immediately or not, the handler will not be invoked from within
|
||||
this function. Invocation of the handler will be performed in a
|
||||
manner equivalent to using `net::post`.
|
||||
|
||||
@par Per-Operation Cancellation
|
||||
|
||||
This asynchronous operation supports cancellation for the following
|
||||
net::cancellation_type values:
|
||||
|
||||
@li @c net::cancellation_type::terminal
|
||||
@li @c net::cancellation_type::partial
|
||||
@li @c net::cancellation_type::total
|
||||
|
||||
if they are also supported by the socket's @c async_connect operation.
|
||||
*/
|
||||
template<
|
||||
class Iterator,
|
||||
@@ -1316,6 +1372,17 @@ public:
|
||||
number of bytes. Consider using the function `net::async_read` if you need
|
||||
to ensure that the requested amount of data is read before the asynchronous
|
||||
operation completes.
|
||||
|
||||
@par Per-Operation Cancellation
|
||||
|
||||
This asynchronous operation supports cancellation for the following
|
||||
net::cancellation_type values:
|
||||
|
||||
@li @c net::cancellation_type::terminal
|
||||
@li @c net::cancellation_type::partial
|
||||
@li @c net::cancellation_type::total
|
||||
|
||||
if they are also supported by the socket's @c async_read_some operation.
|
||||
*/
|
||||
template<
|
||||
class MutableBufferSequence,
|
||||
@@ -1439,6 +1506,17 @@ public:
|
||||
number of bytes. Consider using the function `net::async_write` if you need
|
||||
to ensure that the requested amount of data is sent before the asynchronous
|
||||
operation completes.
|
||||
|
||||
@par Per-Operation Cancellation
|
||||
|
||||
This asynchronous operation supports cancellation for the following
|
||||
net::cancellation_type values:
|
||||
|
||||
@li @c net::cancellation_type::terminal
|
||||
@li @c net::cancellation_type::partial
|
||||
@li @c net::cancellation_type::total
|
||||
|
||||
if they are also supported by the socket's @c async_write_some operation.
|
||||
*/
|
||||
template<
|
||||
class ConstBufferSequence,
|
||||
|
||||
@@ -67,6 +67,16 @@ public:
|
||||
h_, this->get());
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
net::associated_cancellation_slot_t<Handler>;
|
||||
|
||||
cancellation_slot_type
|
||||
get_cancellation_slot() const noexcept
|
||||
{
|
||||
return net::get_associated_cancellation_slot(h_,
|
||||
net::cancellation_slot());
|
||||
}
|
||||
|
||||
// The allocation hooks are still defined because they trivially forward to
|
||||
// user hooks. Forward here ensures that the user will get a compile error
|
||||
// if they build their code with BOOST_ASIO_NO_DEPRECATED.
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <boost/beast/core/error.hpp>
|
||||
#include <boost/beast/core/detail/tuple.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/handler_alloc_hook.hpp>
|
||||
#include <boost/asio/handler_continuation_hook.hpp>
|
||||
@@ -48,6 +49,9 @@ class bind_wrapper
|
||||
template<class T, class Allocator>
|
||||
friend struct net::associated_allocator;
|
||||
|
||||
template<class T, class CancellationSlot>
|
||||
friend struct net::associated_cancellation_slot;
|
||||
|
||||
template<class Arg, class Vals>
|
||||
static
|
||||
typename std::enable_if<
|
||||
@@ -217,6 +221,10 @@ class bind_front_wrapper
|
||||
template<class T, class Allocator>
|
||||
friend struct net::associated_allocator;
|
||||
|
||||
template<class T, class CancellationSlot>
|
||||
friend struct net::associated_cancellation_slot;
|
||||
|
||||
|
||||
template<std::size_t... I, class... Ts>
|
||||
void
|
||||
invoke(
|
||||
@@ -385,6 +393,42 @@ struct associated_allocator<
|
||||
}
|
||||
};
|
||||
|
||||
template<class Handler, class... Args, class CancellationSlot>
|
||||
struct associated_cancellation_slot<
|
||||
beast::detail::bind_wrapper<Handler, Args...>, CancellationSlot>
|
||||
{
|
||||
using type = typename
|
||||
associated_cancellation_slot<Handler>::type;
|
||||
|
||||
static
|
||||
type
|
||||
get(beast::detail::bind_wrapper<Handler, Args...> const& op,
|
||||
CancellationSlot const& slot = CancellationSlot{}) noexcept
|
||||
{
|
||||
return associated_cancellation_slot<
|
||||
Handler, CancellationSlot>::get(op.h_, slot);
|
||||
}
|
||||
};
|
||||
|
||||
template<class Handler, class... Args, class CancellationSlot>
|
||||
struct associated_cancellation_slot<
|
||||
beast::detail::bind_front_wrapper<Handler, Args...>, CancellationSlot>
|
||||
{
|
||||
using type = typename
|
||||
associated_cancellation_slot<Handler>::type;
|
||||
|
||||
static
|
||||
type
|
||||
get(beast::detail::bind_front_wrapper<Handler, Args...> const& op,
|
||||
CancellationSlot const& slot = CancellationSlot{}) noexcept
|
||||
{
|
||||
return associated_cancellation_slot<
|
||||
Handler, CancellationSlot>::get(op.h_, slot);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // asio
|
||||
} // boost
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
#ifndef BOOST_BEAST_CORE_DETAIL_FILTERING_CANCELLATION_SLOT_HPP
|
||||
#define BOOST_BEAST_CORE_DETAIL_FILTERING_CANCELLATION_SLOT_HPP
|
||||
|
||||
#include <boost/beast/core/detail/config.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace beast {
|
||||
namespace detail {
|
||||
|
||||
template<typename CancellationSlot = net::cancellation_slot>
|
||||
struct filtering_cancellation_slot : CancellationSlot
|
||||
{
|
||||
template<typename ... Args>
|
||||
filtering_cancellation_slot(net::cancellation_type type, Args && ... args)
|
||||
: CancellationSlot(std::forward<Args>(args)...), type(type) {}
|
||||
|
||||
net::cancellation_type type = net::cancellation_type::terminal;
|
||||
|
||||
using CancellationSlot::operator=;
|
||||
|
||||
template<typename Handler>
|
||||
struct handler_wrapper
|
||||
{
|
||||
Handler handler;
|
||||
const net::cancellation_type type;
|
||||
|
||||
template<typename ... Args>
|
||||
handler_wrapper(net::cancellation_type type, Args && ... args)
|
||||
: handler(std::forward<Args>(args)...),
|
||||
type(type) {}
|
||||
|
||||
void operator()(net::cancellation_type tp)
|
||||
{
|
||||
if ((tp & type) != net::cancellation_type::none)
|
||||
handler(tp);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename CancellationHandler, typename ... Args>
|
||||
CancellationHandler& emplace(Args && ... args)
|
||||
{
|
||||
return CancellationSlot::template emplace<handler_wrapper<CancellationHandler>>(
|
||||
type, std::forward<Args>(args)...).handler;
|
||||
}
|
||||
|
||||
template <typename CancellationHandler>
|
||||
CancellationHandler& assign(CancellationHandler && ch)
|
||||
{
|
||||
return CancellationSlot::template emplace<handler_wrapper<CancellationHandler>>(
|
||||
type, std::forward<CancellationHandler>(ch)).handler;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif //BOOST_BEAST_CORE_DETAIL_FILTERING_CANCELLATION_SLOT_HPP
|
||||
@@ -283,6 +283,7 @@ public:
|
||||
, pg_()
|
||||
, b_(b)
|
||||
{
|
||||
this->set_allowed_cancellation(net::cancellation_type::all);
|
||||
if (buffer_bytes(b_) == 0 && state().pending)
|
||||
{
|
||||
// Workaround:
|
||||
@@ -452,6 +453,7 @@ public:
|
||||
, pg0_(impl_->read.pending)
|
||||
, pg1_(impl_->write.pending)
|
||||
{
|
||||
this->set_allowed_cancellation(net::cancellation_type::all);
|
||||
if(state().timer.expiry() != stream_base::never())
|
||||
{
|
||||
BOOST_ASIO_HANDLER_LOCATION((
|
||||
@@ -489,6 +491,7 @@ public:
|
||||
, pg0_(impl_->read.pending)
|
||||
, pg1_(impl_->write.pending)
|
||||
{
|
||||
this->set_allowed_cancellation(net::cancellation_type::all);
|
||||
if(state().timer.expiry() != stream_base::never())
|
||||
{
|
||||
BOOST_ASIO_HANDLER_LOCATION((
|
||||
@@ -526,6 +529,7 @@ public:
|
||||
, pg0_(impl_->read.pending)
|
||||
, pg1_(impl_->write.pending)
|
||||
{
|
||||
this->set_allowed_cancellation(net::cancellation_type::all);
|
||||
if(state().timer.expiry() != stream_base::never())
|
||||
{
|
||||
BOOST_ASIO_HANDLER_LOCATION((
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
#include <boost/beast/core/detail/allocator.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/core/empty_value.hpp>
|
||||
@@ -28,9 +30,10 @@ class saved_handler::base
|
||||
{
|
||||
protected:
|
||||
~base() = default;
|
||||
|
||||
saved_handler * owner_;
|
||||
public:
|
||||
base() = default;
|
||||
base(saved_handler * owner) : owner_(owner){}
|
||||
void set_owner(saved_handler * new_owner) { owner_ = new_owner;}
|
||||
virtual void destroy() = 0;
|
||||
virtual void invoke() = 0;
|
||||
};
|
||||
@@ -72,11 +75,12 @@ class saved_handler::impl final : public base
|
||||
net::executor_work_guard<
|
||||
net::associated_executor_t<Handler>> wg2_;
|
||||
#endif // defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
||||
|
||||
net::cancellation_slot slot_{net::get_associated_cancellation_slot(v_.h)};
|
||||
public:
|
||||
template<class Handler_>
|
||||
impl(alloc_type const& a, Handler_&& h)
|
||||
: v_(a, std::forward<Handler_>(h))
|
||||
impl(alloc_type const& a, Handler_&& h,
|
||||
saved_handler * owner)
|
||||
: base(owner), v_(a, std::forward<Handler_>(h))
|
||||
#if defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
||||
, wg2_(net::prefer(
|
||||
net::get_associated_executor(v_.h),
|
||||
@@ -87,10 +91,15 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
~impl()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
destroy() override
|
||||
{
|
||||
auto v = std::move(v_);
|
||||
slot_.clear();
|
||||
alloc_traits::destroy(v.get(), this);
|
||||
alloc_traits::deallocate(v.get(), this, 1);
|
||||
}
|
||||
@@ -98,11 +107,22 @@ public:
|
||||
void
|
||||
invoke() override
|
||||
{
|
||||
slot_.clear();
|
||||
auto v = std::move(v_);
|
||||
alloc_traits::destroy(v.get(), this);
|
||||
alloc_traits::deallocate(v.get(), this, 1);
|
||||
v.h();
|
||||
}
|
||||
|
||||
void self_complete()
|
||||
{
|
||||
slot_.clear();
|
||||
owner_->p_ = nullptr;
|
||||
auto v = std::move(v_);
|
||||
alloc_traits::destroy(v.get(), this);
|
||||
alloc_traits::deallocate(v.get(), this, 1);
|
||||
v.h(net::error::operation_aborted);
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -110,7 +130,8 @@ public:
|
||||
template<class Handler, class Allocator>
|
||||
void
|
||||
saved_handler::
|
||||
emplace(Handler&& handler, Allocator const& alloc)
|
||||
emplace(Handler&& handler, Allocator const& alloc,
|
||||
net::cancellation_type cancel_type)
|
||||
{
|
||||
// Can't delete a handler before invoking
|
||||
BOOST_ASSERT(! has_value());
|
||||
@@ -140,22 +161,47 @@ emplace(Handler&& handler, Allocator const& alloc)
|
||||
alloc_traits::deallocate(a, p, 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
auto cancel_slot = net::get_associated_cancellation_slot(handler);
|
||||
storage s(alloc);
|
||||
alloc_traits::construct(s.a, s.p,
|
||||
s.a, std::forward<Handler>(handler));
|
||||
p_ = boost::exchange(s.p, nullptr);
|
||||
s.a, std::forward<Handler>(handler), this);
|
||||
|
||||
auto tmp = boost::exchange(s.p, nullptr);
|
||||
p_ = tmp;
|
||||
|
||||
if (cancel_slot.is_connected())
|
||||
{
|
||||
struct cancel_op
|
||||
{
|
||||
impl<Handler, Allocator>* p;
|
||||
net::cancellation_type accepted_ct;
|
||||
cancel_op(impl<Handler, Allocator>* p,
|
||||
net::cancellation_type accepted_ct)
|
||||
: p(p), accepted_ct(accepted_ct) {}
|
||||
|
||||
void operator()(net::cancellation_type ct)
|
||||
{
|
||||
if ((ct & accepted_ct) != net::cancellation_type::none)
|
||||
p->self_complete();
|
||||
}
|
||||
};
|
||||
cancel_slot.template emplace<cancel_op>(tmp, cancel_type);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Handler>
|
||||
void
|
||||
saved_handler::
|
||||
emplace(Handler&& handler)
|
||||
emplace(Handler&& handler, net::cancellation_type cancel_type)
|
||||
{
|
||||
// Can't delete a handler before invoking
|
||||
BOOST_ASSERT(! has_value());
|
||||
emplace(
|
||||
std::forward<Handler>(handler),
|
||||
net::get_associated_allocator(handler));
|
||||
net::get_associated_allocator(handler),
|
||||
cancel_type);
|
||||
}
|
||||
|
||||
} // beast
|
||||
|
||||
@@ -27,6 +27,7 @@ saved_handler::
|
||||
saved_handler(saved_handler&& other) noexcept
|
||||
: p_(boost::exchange(other.p_, nullptr))
|
||||
{
|
||||
p_->set_owner(this);
|
||||
}
|
||||
|
||||
saved_handler&
|
||||
@@ -36,6 +37,7 @@ operator=(saved_handler&& other) noexcept
|
||||
// Can't delete a handler before invoking
|
||||
BOOST_ASSERT(! has_value());
|
||||
p_ = boost::exchange(other.p_, nullptr);
|
||||
p_->set_owner(this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#define BOOST_BEAST_CORE_SAVED_HANDLER_HPP
|
||||
|
||||
#include <boost/beast/core/detail/config.hpp>
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace beast {
|
||||
@@ -72,10 +73,13 @@ public:
|
||||
The implementation takes ownership of the handler by performing a decay-copy.
|
||||
|
||||
@param alloc The allocator to use.
|
||||
|
||||
@param cancel_type The type of cancellation allowed to complete this op.
|
||||
*/
|
||||
template<class Handler, class Allocator>
|
||||
void
|
||||
emplace(Handler&& handler, Allocator const& alloc);
|
||||
emplace(Handler&& handler, Allocator const& alloc,
|
||||
net::cancellation_type cancel_type = net::cancellation_type::terminal);
|
||||
|
||||
/** Store a completion handler in the container.
|
||||
|
||||
@@ -85,10 +89,13 @@ public:
|
||||
|
||||
@param handler The completion handler to store.
|
||||
The implementation takes ownership of the handler by performing a decay-copy.
|
||||
|
||||
@param cancel_type The type of cancellation allowed to complete this op.
|
||||
*/
|
||||
template<class Handler>
|
||||
void
|
||||
emplace(Handler&& handler);
|
||||
emplace(Handler&& handler,
|
||||
net::cancellation_type cancel_type = net::cancellation_type::terminal);
|
||||
|
||||
/** Discard the saved handler, if one exists.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user