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:
Klemens
2022-08-31 10:03:35 +08:00
committed by Klemens Morgenstern
parent 0bf3d971a0
commit 3ebff60b1a
35 changed files with 1216 additions and 29 deletions

View File

@ -1,3 +1,9 @@
Version 342:
* Support per operation cancellation
--------------------------------------------------------------------------------
Version 341: Version 341:
* Expect header field with the "100-continue" is handled in upgrade. * Expect header field with the "100-continue" is handled in upgrade.
@ -19,7 +25,6 @@ Version 339:
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
Version 338: Version 338:
* Added per message compression options. * Added per message compression options.

View File

@ -199,6 +199,10 @@ has both an
[@boost:/doc/html/boost_asio/overview/core/allocation.html ['associated allocator]] [@boost:/doc/html/boost_asio/overview/core/allocation.html ['associated allocator]]
returned by returned by
[@boost:/doc/html/boost_asio/reference/get_associated_allocator.html `net::get_associated_allocator`], [@boost:/doc/html/boost_asio/reference/get_associated_allocator.html `net::get_associated_allocator`],
, an
[@boost:/doc/html/boost_asio/reference/associated_cancellation_slot.html ['associated cancellation slot]]
returned by
[@boost:/doc/html/boost_asio/reference/associated_cancellation_slot.html `net::get_associated_cancellation_slot`].
and an and an
[@boost:/doc/html/boost_asio/reference/associated_executor.html ['associated executor]] [@boost:/doc/html/boost_asio/reference/associated_executor.html ['associated executor]]
returned by returned by
@ -210,6 +214,8 @@ These associations may be specified intrusively:
Or these associations may be specified non-intrusively, by specializing Or these associations may be specified non-intrusively, by specializing
the class templates the class templates
[@boost:/doc/html/boost_asio/reference/associated_allocator.html `net::associated_allocator`] [@boost:/doc/html/boost_asio/reference/associated_allocator.html `net::associated_allocator`]
,
[@boost:/doc/html/boost_asio/reference/associated_cancellation_slot.html `net::associated_cancellation_slot`]
and and
[@boost:/doc/html/boost_asio/reference/associated_executor.html `net::associated_executor`]: [@boost:/doc/html/boost_asio/reference/associated_executor.html `net::associated_executor`]:
@ -227,6 +233,36 @@ object providing the algorithm used to invoke the completion handler. Unless
customized by the caller, a completion handler defaults to using customized by the caller, a completion handler defaults to using
`std::allocator<void>` and the executor of the corresponding I/O object. `std::allocator<void>` and the executor of the corresponding I/O object.
The function
[@boost:/doc/html/boost_asio/reference/bind_allocator.html `net::bind_allocator`]
can be used whent he caller wants to assign a custom allocator to the operation.
A completion token's associated cancellation_slot can be used to cancel single
operations. This is often passed through by the completion token such as
[@boost:/doc/html/boost_asio/reference/use_awaitable.html `net::use_awaitable`]
or
[@boost:/doc/html/boost_asio/reference/yield_context.html `net::yield_context`]
.
The available [@boost:/doc/html/boost_asio/reference/cancellation_type.html cancellation types] are listed below.
# `terminal`
Requests cancellation where, following a successful cancellation,
the only safe operations on the I/O object are closure or destruction.
# `partial`
Requests cancellation where a successful cancellation may result in partial
side effects or no side effects. Following cancellation,
the I/O object is in a well-known state, and may be used for further operations.
# `total`
Requests cancellation where a successful cancellation results in no apparent side effects.
Following cancellation, the I/O object is in the same observable state as it was prior to the operation.
Networking prescribes facilities to determine the context in which Networking prescribes facilities to determine the context in which
handlers run. Every I/O object refers to an __ExecutionContext__ for handlers run. Every I/O object refers to an __ExecutionContext__ for
obtaining the __Executor__ instance used to invoke completion handlers. obtaining the __Executor__ instance used to invoke completion handlers.

View File

@ -77,6 +77,16 @@ class basic_stream<Executor>::read_op : public detail::stream_read_op_base
return net::get_associated_allocator(h_); return net::get_associated_allocator(h_);
} }
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());
}
void void
operator()(error_code ec) operator()(error_code ec)
{ {

View File

@ -14,6 +14,7 @@
#include <boost/beast/core/bind_handler.hpp> #include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/detail/allocator.hpp> #include <boost/beast/core/detail/allocator.hpp>
#include <boost/beast/core/detail/async_base.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/beast/core/detail/work_guard.hpp>
#include <boost/asio/associated_allocator.hpp> #include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp> #include <boost/asio/associated_executor.hpp>
@ -187,7 +188,7 @@ class async_base
Handler h_; Handler h_;
detail::select_work_guard_t<Executor1> wg1_; detail::select_work_guard_t<Executor1> wg1_;
net::cancellation_type act_{net::cancellation_type::terminal};
public: public:
/** The type of executor associated with this object. /** The type of executor associated with this object.
@ -308,6 +309,38 @@ public:
h_, wg1_.get_executor()); h_, wg1_.get_executor());
} }
/** The type of cancellation_slot associated with this object.
If a class derived from @ref async_base is a completion
handler, then the associated cancellation_slot of the
derived class will be this type.
The default type is a filtering cancellation slot,
that only allows terminal cancellation.
*/
using cancellation_slot_type =
beast::detail::filtering_cancellation_slot<net::associated_cancellation_slot_t<Handler>>;
/** Returns the cancellation_slot associated with this object.
If a class derived from @ref async_base is a completion
handler, then the object returned from this function will be used
as the associated cancellation_slot of the derived class.
*/
cancellation_slot_type
get_cancellation_slot() const noexcept
{
return cancellation_slot_type(act_, net::get_associated_cancellation_slot(h_,
net::cancellation_slot()));
}
/// Set the allowed cancellation types, default is `terminal`.
void set_allowed_cancellation(
net::cancellation_type allowed_cancellation_types = net::cancellation_type::terminal)
{
act_ = allowed_cancellation_types;
}
/// Returns the handler associated with this object /// Returns the handler associated with this object
Handler const& Handler const&
handler() const noexcept handler() const noexcept

View File

@ -919,6 +919,17 @@ public:
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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 @see async_connect
*/ */
template< template<
@ -972,6 +983,18 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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< template<
class EndpointSequence, class EndpointSequence,
@ -1064,6 +1087,17 @@ public:
} }
}; };
@endcode @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< template<
class EndpointSequence, class EndpointSequence,
@ -1129,6 +1163,17 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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< template<
class Iterator, class Iterator,
@ -1189,6 +1234,17 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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< template<
class Iterator, class Iterator,
@ -1316,6 +1372,17 @@ public:
number of bytes. Consider using the function `net::async_read` if you need 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 to ensure that the requested amount of data is read before the asynchronous
operation completes. 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< template<
class MutableBufferSequence, class MutableBufferSequence,
@ -1439,6 +1506,17 @@ public:
number of bytes. Consider using the function `net::async_write` if you need 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 to ensure that the requested amount of data is sent before the asynchronous
operation completes. 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< template<
class ConstBufferSequence, class ConstBufferSequence,

View File

@ -67,6 +67,16 @@ public:
h_, this->get()); 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 // The allocation hooks are still defined because they trivially forward to
// user hooks. Forward here ensures that the user will get a compile error // user hooks. Forward here ensures that the user will get a compile error
// if they build their code with BOOST_ASIO_NO_DEPRECATED. // if they build their code with BOOST_ASIO_NO_DEPRECATED.

View File

@ -13,6 +13,7 @@
#include <boost/beast/core/error.hpp> #include <boost/beast/core/error.hpp>
#include <boost/beast/core/detail/tuple.hpp> #include <boost/beast/core/detail/tuple.hpp>
#include <boost/asio/associated_allocator.hpp> #include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp> #include <boost/asio/associated_executor.hpp>
#include <boost/asio/handler_alloc_hook.hpp> #include <boost/asio/handler_alloc_hook.hpp>
#include <boost/asio/handler_continuation_hook.hpp> #include <boost/asio/handler_continuation_hook.hpp>
@ -48,6 +49,9 @@ class bind_wrapper
template<class T, class Allocator> template<class T, class Allocator>
friend struct net::associated_allocator; friend struct net::associated_allocator;
template<class T, class CancellationSlot>
friend struct net::associated_cancellation_slot;
template<class Arg, class Vals> template<class Arg, class Vals>
static static
typename std::enable_if< typename std::enable_if<
@ -217,6 +221,10 @@ class bind_front_wrapper
template<class T, class Allocator> template<class T, class Allocator>
friend struct net::associated_allocator; friend struct net::associated_allocator;
template<class T, class CancellationSlot>
friend struct net::associated_cancellation_slot;
template<std::size_t... I, class... Ts> template<std::size_t... I, class... Ts>
void void
invoke( 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 } // asio
} // boost } // boost

View File

@ -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

View File

@ -283,6 +283,7 @@ public:
, pg_() , pg_()
, b_(b) , b_(b)
{ {
this->set_allowed_cancellation(net::cancellation_type::all);
if (buffer_bytes(b_) == 0 && state().pending) if (buffer_bytes(b_) == 0 && state().pending)
{ {
// Workaround: // Workaround:
@ -452,6 +453,7 @@ public:
, pg0_(impl_->read.pending) , pg0_(impl_->read.pending)
, pg1_(impl_->write.pending) , pg1_(impl_->write.pending)
{ {
this->set_allowed_cancellation(net::cancellation_type::all);
if(state().timer.expiry() != stream_base::never()) if(state().timer.expiry() != stream_base::never())
{ {
BOOST_ASIO_HANDLER_LOCATION(( BOOST_ASIO_HANDLER_LOCATION((
@ -489,6 +491,7 @@ public:
, pg0_(impl_->read.pending) , pg0_(impl_->read.pending)
, pg1_(impl_->write.pending) , pg1_(impl_->write.pending)
{ {
this->set_allowed_cancellation(net::cancellation_type::all);
if(state().timer.expiry() != stream_base::never()) if(state().timer.expiry() != stream_base::never())
{ {
BOOST_ASIO_HANDLER_LOCATION(( BOOST_ASIO_HANDLER_LOCATION((
@ -526,6 +529,7 @@ public:
, pg0_(impl_->read.pending) , pg0_(impl_->read.pending)
, pg1_(impl_->write.pending) , pg1_(impl_->write.pending)
{ {
this->set_allowed_cancellation(net::cancellation_type::all);
if(state().timer.expiry() != stream_base::never()) if(state().timer.expiry() != stream_base::never())
{ {
BOOST_ASIO_HANDLER_LOCATION(( BOOST_ASIO_HANDLER_LOCATION((

View File

@ -12,7 +12,9 @@
#include <boost/beast/core/detail/allocator.hpp> #include <boost/beast/core/detail/allocator.hpp>
#include <boost/asio/associated_allocator.hpp> #include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp> #include <boost/asio/associated_executor.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/executor_work_guard.hpp> #include <boost/asio/executor_work_guard.hpp>
#include <boost/assert.hpp> #include <boost/assert.hpp>
#include <boost/core/empty_value.hpp> #include <boost/core/empty_value.hpp>
@ -28,9 +30,10 @@ class saved_handler::base
{ {
protected: protected:
~base() = default; ~base() = default;
saved_handler * owner_;
public: 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 destroy() = 0;
virtual void invoke() = 0; virtual void invoke() = 0;
}; };
@ -72,11 +75,12 @@ class saved_handler::impl final : public base
net::executor_work_guard< net::executor_work_guard<
net::associated_executor_t<Handler>> wg2_; net::associated_executor_t<Handler>> wg2_;
#endif // defined(BOOST_ASIO_NO_TS_EXECUTORS) #endif // defined(BOOST_ASIO_NO_TS_EXECUTORS)
net::cancellation_slot slot_{net::get_associated_cancellation_slot(v_.h)};
public: public:
template<class Handler_> template<class Handler_>
impl(alloc_type const& a, Handler_&& h) impl(alloc_type const& a, Handler_&& h,
: v_(a, std::forward<Handler_>(h)) saved_handler * owner)
: base(owner), v_(a, std::forward<Handler_>(h))
#if defined(BOOST_ASIO_NO_TS_EXECUTORS) #if defined(BOOST_ASIO_NO_TS_EXECUTORS)
, wg2_(net::prefer( , wg2_(net::prefer(
net::get_associated_executor(v_.h), net::get_associated_executor(v_.h),
@ -87,10 +91,15 @@ public:
{ {
} }
~impl()
{
}
void void
destroy() override destroy() override
{ {
auto v = std::move(v_); auto v = std::move(v_);
slot_.clear();
alloc_traits::destroy(v.get(), this); alloc_traits::destroy(v.get(), this);
alloc_traits::deallocate(v.get(), this, 1); alloc_traits::deallocate(v.get(), this, 1);
} }
@ -98,11 +107,22 @@ public:
void void
invoke() override invoke() override
{ {
slot_.clear();
auto v = std::move(v_); auto v = std::move(v_);
alloc_traits::destroy(v.get(), this); alloc_traits::destroy(v.get(), this);
alloc_traits::deallocate(v.get(), this, 1); alloc_traits::deallocate(v.get(), this, 1);
v.h(); 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> template<class Handler, class Allocator>
void void
saved_handler:: 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 // Can't delete a handler before invoking
BOOST_ASSERT(! has_value()); BOOST_ASSERT(! has_value());
@ -140,22 +161,47 @@ emplace(Handler&& handler, Allocator const& alloc)
alloc_traits::deallocate(a, p, 1); alloc_traits::deallocate(a, p, 1);
} }
}; };
auto cancel_slot = net::get_associated_cancellation_slot(handler);
storage s(alloc); storage s(alloc);
alloc_traits::construct(s.a, s.p, alloc_traits::construct(s.a, s.p,
s.a, std::forward<Handler>(handler)); s.a, std::forward<Handler>(handler), this);
p_ = boost::exchange(s.p, nullptr);
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> template<class Handler>
void void
saved_handler:: saved_handler::
emplace(Handler&& handler) emplace(Handler&& handler, net::cancellation_type cancel_type)
{ {
// Can't delete a handler before invoking // Can't delete a handler before invoking
BOOST_ASSERT(! has_value()); BOOST_ASSERT(! has_value());
emplace( emplace(
std::forward<Handler>(handler), std::forward<Handler>(handler),
net::get_associated_allocator(handler)); net::get_associated_allocator(handler),
cancel_type);
} }
} // beast } // beast

View File

@ -27,6 +27,7 @@ saved_handler::
saved_handler(saved_handler&& other) noexcept saved_handler(saved_handler&& other) noexcept
: p_(boost::exchange(other.p_, nullptr)) : p_(boost::exchange(other.p_, nullptr))
{ {
p_->set_owner(this);
} }
saved_handler& saved_handler&
@ -36,6 +37,7 @@ operator=(saved_handler&& other) noexcept
// Can't delete a handler before invoking // Can't delete a handler before invoking
BOOST_ASSERT(! has_value()); BOOST_ASSERT(! has_value());
p_ = boost::exchange(other.p_, nullptr); p_ = boost::exchange(other.p_, nullptr);
p_->set_owner(this);
return *this; return *this;
} }

View File

@ -11,6 +11,7 @@
#define BOOST_BEAST_CORE_SAVED_HANDLER_HPP #define BOOST_BEAST_CORE_SAVED_HANDLER_HPP
#include <boost/beast/core/detail/config.hpp> #include <boost/beast/core/detail/config.hpp>
#include <boost/asio/cancellation_type.hpp>
namespace boost { namespace boost {
namespace beast { namespace beast {
@ -72,10 +73,13 @@ public:
The implementation takes ownership of the handler by performing a decay-copy. The implementation takes ownership of the handler by performing a decay-copy.
@param alloc The allocator to use. @param alloc The allocator to use.
@param cancel_type The type of cancellation allowed to complete this op.
*/ */
template<class Handler, class Allocator> template<class Handler, class Allocator>
void 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. /** Store a completion handler in the container.
@ -85,10 +89,13 @@ public:
@param handler The completion handler to store. @param handler The completion handler to store.
The implementation takes ownership of the handler by performing a decay-copy. 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> template<class Handler>
void void
emplace(Handler&& handler); emplace(Handler&& handler,
net::cancellation_type cancel_type = net::cancellation_type::terminal);
/** Discard the saved handler, if one exists. /** Discard the saved handler, if one exists.

View File

@ -178,8 +178,18 @@ class write_op
Stream& s_; Stream& s_;
serializer<isRequest, Body, Fields>& sr_; serializer<isRequest, Body, Fields>& sr_;
std::size_t bytes_transferred_ = 0; std::size_t bytes_transferred_ = 0;
net::cancellation_state st_{this->
beast::async_base<Handler, beast::executor_type<Stream>>
::get_cancellation_slot()};
public: public:
using cancellation_slot_type = net::cancellation_slot;
cancellation_slot_type get_cancellation_slot() const noexcept
{
return st_.slot();
}
template<class Handler_> template<class Handler_>
write_op( write_op(
Handler_&& h, Handler_&& h,
@ -227,6 +237,8 @@ public:
s_, sr_, std::move(*this)); s_, sr_, std::move(*this));
} }
bytes_transferred_ += bytes_transferred; bytes_transferred_ += bytes_transferred;
if (!ec && st_.cancelled() != net::cancellation_type::none)
ec = net::error::operation_aborted;
if(ec) if(ec)
goto upcall; goto upcall;
if(Predicate{}(sr_)) if(Predicate{}(sr_))

View File

@ -200,6 +200,19 @@ read_some(
@note The completion handler will receive as a parameter the total number @note The completion handler will receive as a parameter the total number
of bytes transferred from the stream. This may be zero for the case where of bytes transferred from the stream. This may be zero for the case where
there is sufficient pre-existing message data in the dynamic buffer. there is sufficient pre-existing message data in the dynamic buffer.
@par Per-Operation Cancellation
This asynchronous operation supports cancellation for the following
net::cancellation_type values:
@li @c net::cancellation_type::terminal
if the `stream` also supports terminal cancellation.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
class AsyncReadStream, class AsyncReadStream,
@ -397,6 +410,19 @@ read_header(
there is sufficient pre-existing message data in the dynamic buffer. The there is sufficient pre-existing message data in the dynamic buffer. The
implementation will call @ref basic_parser::eager with the value `false` implementation will call @ref basic_parser::eager with the value `false`
on the parser passed in. on the parser passed in.
@par Per-Operation Cancellation
This asynchronous operation supports cancellation for the following
net::cancellation_type values:
@li @c net::cancellation_type::terminal
if the `stream` also supports terminal cancellation.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
class AsyncReadStream, class AsyncReadStream,
@ -594,6 +620,19 @@ read(
there is sufficient pre-existing message data in the dynamic buffer. The there is sufficient pre-existing message data in the dynamic buffer. The
implementation will call @ref basic_parser::eager with the value `true` implementation will call @ref basic_parser::eager with the value `true`
on the parser passed in. on the parser passed in.
@par Per-Operation Cancellation
This asynchronous operation supports cancellation for the following
net::cancellation_type values:
@li @c net::cancellation_type::terminal
if the `stream` also supports terminal cancellation.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
class AsyncReadStream, class AsyncReadStream,
@ -800,6 +839,19 @@ read(
there is sufficient pre-existing message data in the dynamic buffer. The there is sufficient pre-existing message data in the dynamic buffer. The
implementation will call @ref basic_parser::eager with the value `true` implementation will call @ref basic_parser::eager with the value `true`
on the parser passed in. on the parser passed in.
@par Per-Operation Cancellation
This asynchronous operation supports cancellation for the following
net::cancellation_type values:
@li @c net::cancellation_type::terminal
if the `stream` also supports terminal cancellation.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
class AsyncReadStream, class AsyncReadStream,

View File

@ -161,6 +161,18 @@ write_some(
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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
if the `stream` also supports terminal cancellation.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
@see serializer @see serializer
*/ */
template< template<
@ -291,6 +303,18 @@ write_header(
@note The implementation will call @ref serializer::split with @note The implementation will call @ref serializer::split with
the value `true` on the serializer passed in. the value `true` on the serializer passed in.
@par Per-Operation Cancellation
This asynchronous operation supports cancellation for the following
net::cancellation_type values:
@li @c net::cancellation_type::terminal
if the `stream` also supports terminal cancellation.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
@see serializer @see serializer
*/ */
template< template<
@ -412,6 +436,18 @@ write(
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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
if the `stream` also supports terminal cancellation.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
@see serializer @see serializer
*/ */
template< template<
@ -637,6 +673,18 @@ write(
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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
if the `stream` also supports terminal cancellation.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
@see message @see message
*/ */
template< template<
@ -699,6 +747,18 @@ async_write(
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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
if the `stream` also supports terminal cancellation.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
@see message @see message
*/ */
template< template<

View File

@ -266,6 +266,9 @@ public:
// read and respond to an upgrade request // read and respond to an upgrade request
// //
// Cancellation: the async_accept cancellation can be terminal
// because it will just interrupt the reading of the header.
//
template<class NextLayer, bool deflateSupported> template<class NextLayer, bool deflateSupported>
template<class Handler, class Decorator> template<class Handler, class Decorator>
class stream<NextLayer, deflateSupported>::accept_op class stream<NextLayer, deflateSupported>::accept_op

View File

@ -90,9 +90,15 @@ public:
BOOST_ASIO_HANDLER_LOCATION(( BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__, __FILE__, __LINE__,
"websocket::async_close")); "websocket::async_close"));
this->set_allowed_cancellation(net::cancellation_type::all);
impl.op_close.emplace(std::move(*this)); impl.op_close.emplace(std::move(*this),
net::cancellation_type::all);
} }
// cancel fired before we could do anything.
if (ec == net::error::operation_aborted)
return this->complete(cont, ec);
this->set_allowed_cancellation(net::cancellation_type::terminal);
impl.wr_block.lock(this); impl.wr_block.lock(this);
BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD
{ {
@ -143,9 +149,17 @@ public:
BOOST_ASIO_HANDLER_LOCATION(( BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__, __FILE__, __LINE__,
"websocket::async_close")); "websocket::async_close"));
// terminal only, that's the default
impl.op_r_close.emplace(std::move(*this)); impl.op_r_close.emplace(std::move(*this));
} }
if (ec == net::error::operation_aborted)
{
// if a cancellation fires here, we do a dirty shutdown
impl.change_status(status::closed);
close_socket(get_lowest_layer(impl.stream()));
return this->complete(cont, ec);
}
impl.rd_block.lock(this); impl.rd_block.lock(this);
BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD
{ {
@ -185,7 +199,7 @@ public:
beast::detail::bind_continuation(std::move(*this))); beast::detail::bind_continuation(std::move(*this)));
} }
impl.rd_buf.commit(bytes_transferred); impl.rd_buf.commit(bytes_transferred);
if(impl.check_stop_now(ec)) if(impl.check_stop_now(ec)) //< this catches cancellation
goto upcall; goto upcall;
} }
if(detail::is_control(impl.rd_fh.op)) if(detail::is_control(impl.rd_fh.op))

View File

@ -86,9 +86,12 @@ public:
BOOST_ASIO_HANDLER_LOCATION(( BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__, __FILE__, __LINE__,
"websocket::async_ping")); "websocket::async_ping"));
this->set_allowed_cancellation(net::cancellation_type::all);
impl.op_ping.emplace(std::move(*this)); impl.op_ping.emplace(std::move(*this), net::cancellation_type::all);
} }
if (ec)
return this->complete(cont, ec);
this->set_allowed_cancellation(net::cancellation_type::terminal);
impl.wr_block.lock(this); impl.wr_block.lock(this);
BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD
{ {

View File

@ -107,8 +107,14 @@ public:
__FILE__, __LINE__, __FILE__, __LINE__,
"websocket::async_read_some")); "websocket::async_read_some"));
impl.op_r_rd.emplace(std::move(*this)); this->set_allowed_cancellation(net::cancellation_type::all);
impl.op_r_rd.emplace(std::move(*this), net::cancellation_type::all);
} }
if (ec)
return this->complete(cont, ec, bytes_written_);
this->set_allowed_cancellation(net::cancellation_type::terminal);
impl.rd_block.lock(this); impl.rd_block.lock(this);
BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD
{ {
@ -275,6 +281,9 @@ public:
impl.op_rd.emplace(std::move(*this)); impl.op_rd.emplace(std::move(*this));
} }
if (ec)
return this->complete(cont, ec, bytes_written_);
impl.wr_block.lock(this); impl.wr_block.lock(this);
BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD
{ {
@ -629,6 +638,9 @@ public:
impl.op_rd.emplace(std::move(*this)); impl.op_rd.emplace(std::move(*this));
} }
if (ec)
return this->complete(cont, ec, bytes_written_);
impl.wr_block.lock(this); impl.wr_block.lock(this);
BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD
{ {

View File

@ -66,6 +66,8 @@ struct ssl_shutdown_op
{ {
BOOST_ASIO_CORO_REENTER(*this) BOOST_ASIO_CORO_REENTER(*this)
{ {
self.reset_cancellation_state(net::enable_total_cancellation());
BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD
s_.async_shutdown(std::move(self)); s_.async_shutdown(std::move(self));
ec_ = ec; ec_ = ec;

View File

@ -59,6 +59,7 @@ public:
, nb_(false) , nb_(false)
{ {
(*this)({}, 0, false); (*this)({}, 0, false);
this->set_allowed_cancellation(net::cancellation_type::all);
} }
void void

View File

@ -181,9 +181,14 @@ operator()(
"websocket::async_write" : "websocket::async_write" :
"websocket::async_write_some" "websocket::async_write_some"
)); ));
this->set_allowed_cancellation(net::cancellation_type::all);
impl.op_wr.emplace(std::move(*this)); impl.op_wr.emplace(std::move(*this),
net::cancellation_type::all);
} }
if (ec)
return this->complete(cont, ec, bytes_transferred_);
this->set_allowed_cancellation(net::cancellation_type::terminal);
impl.wr_block.lock(this); impl.wr_block.lock(this);
BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD
{ {

View File

@ -68,6 +68,18 @@ teardown(
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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_teardown
and @c async_shutdown operation.
*/ */
template<class AsyncStream, class TeardownHandler> template<class AsyncStream, class TeardownHandler>
void void

View File

@ -1602,6 +1602,20 @@ public:
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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::total
`total` cancellation succeeds if the operation is suspended due to ongoing
control operations such as a ping/pong.
`terminal` cancellation succeeds when supported by the underlying stream.
@note `terminal` cancellation will may close the underlying socket.
@see @see
@li <a href="https://tools.ietf.org/html/rfc6455#section-7.1.2">Websocket Closing Handshake (RFC6455)</a> @li <a href="https://tools.ietf.org/html/rfc6455#section-7.1.2">Websocket Closing Handshake (RFC6455)</a>
*/ */
@ -1706,6 +1720,21 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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::total
`total` cancellation succeeds if the operation is suspended due to ongoing
control operations such as a ping/pong.
`terminal` cancellation succeeds when supported by the underlying stream.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
BOOST_BEAST_ASYNC_TPARAM1 WriteHandler = BOOST_BEAST_ASYNC_TPARAM1 WriteHandler =
@ -1814,6 +1843,21 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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::total
`total` cancellation succeeds if the operation is suspended due to ongoing
control operations such as a ping/pong.
`terminal` cancellation succeeds when supported by the underlying stream.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
BOOST_BEAST_ASYNC_TPARAM1 WriteHandler = BOOST_BEAST_ASYNC_TPARAM1 WriteHandler =
@ -1975,6 +2019,21 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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::total
`total` cancellation succeeds if the operation is suspended due to ongoing
control operations such as a ping/pong.
`terminal` cancellation succeeds when supported by the underlying stream.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
class DynamicBuffer, class DynamicBuffer,
@ -2154,6 +2213,21 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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::total
`total` cancellation succeeds if the operation is suspended due to ongoing
control operations such as a ping/pong.
`terminal` cancellation succeeds when supported by the underlying stream.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
class DynamicBuffer, class DynamicBuffer,
@ -2258,6 +2332,21 @@ public:
from the beginning. from the beginning.
@param ec Set to indicate what error occurred, if any. @param ec Set to indicate what error occurred, if any.
@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::total
`total` cancellation succeeds if the operation is suspended due to ongoing
control operations such as a ping/pong.
`terminal` cancellation succeeds when supported by the underlying stream.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template<class MutableBufferSequence> template<class MutableBufferSequence>
std::size_t std::size_t
@ -2329,6 +2418,22 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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::total
`total` cancellation succeeds if the operation is suspended due to ongoing
control operations such as a ping/pong.
`terminal` cancellation succeeds when supported by the underlying stream.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
class MutableBufferSequence, class MutableBufferSequence,
@ -2452,6 +2557,21 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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::total
`total` cancellation succeeds if the operation is suspended due to ongoing
control operations such as a ping/pong.
`terminal` cancellation succeeds when supported by the underlying stream.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
class ConstBufferSequence, class ConstBufferSequence,
@ -2490,6 +2610,7 @@ public:
@return The number of bytes sent from the buffers. @return The number of bytes sent from the buffers.
@throws system_error Thrown on failure. @throws system_error Thrown on failure.
*/ */
template<class ConstBufferSequence> template<class ConstBufferSequence>
std::size_t std::size_t
@ -2575,6 +2696,21 @@ public:
immediately or not, the handler will not be invoked from within immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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::total
`total` cancellation succeeds if the operation is suspended due to ongoing
control operations such as a ping/pong.
`terminal` cancellation succeeds when supported by the underlying stream.
`terminal` cancellation leaves the stream in an undefined state,
so that only closing it is guaranteed to succeed.
*/ */
template< template<
class ConstBufferSequence, class ConstBufferSequence,

View File

@ -163,6 +163,17 @@ teardown(
this function. Invocation of the handler will be performed in a this function. Invocation of the handler will be performed in a
manner equivalent to using `net::post`. 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_wait operation.
*/ */
template< template<
class Protocol, class Executor, class Protocol, class Executor,

View File

@ -48,6 +48,7 @@ add_executable (tests-beast-core
file_posix.cpp file_posix.cpp
file_stdio.cpp file_stdio.cpp
file_win32.cpp file_win32.cpp
filtering_cancellation_slot.cpp
flat_buffer.cpp flat_buffer.cpp
flat_static_buffer.cpp flat_static_buffer.cpp
flat_stream.cpp flat_stream.cpp

View File

@ -39,6 +39,7 @@ local SOURCES =
file_posix.cpp file_posix.cpp
file_stdio.cpp file_stdio.cpp
file_win32.cpp file_win32.cpp
filtering_cancellation_slot.cpp
flat_buffer.cpp flat_buffer.cpp
flat_static_buffer.cpp flat_static_buffer.cpp
flat_stream.cpp flat_stream.cpp

View File

@ -0,0 +1,48 @@
//
// 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)
//
// Test that header file is self-contained.
#include <boost/beast/core/detail/filtering_cancellation_slot.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
namespace boost {
namespace beast {
struct filtering_cancellation_slot_test : beast::unit_test::suite
{
void
run()
{
using ct = net::cancellation_type;
ct fired = ct::none;
auto l = [&fired](ct tp){fired = tp;};
net::cancellation_signal sl;
detail::filtering_cancellation_slot<> slot{ct::terminal, sl.slot()};
slot.type |= ct::total;
slot = sl.slot();
slot.assign(l);
BEAST_EXPECT(fired == ct::none);
sl.emit(ct::total);
BEAST_EXPECT(fired == ct::total);
sl.emit(ct::partial);
BEAST_EXPECT(fired == ct::total);
sl.emit(ct::terminal);
BEAST_EXPECT(fired == ct::terminal);
}
};
BEAST_DEFINE_TESTSUITE(beast,core,filtering_cancellation_slot);
} // beast
} // boost

View File

@ -9,7 +9,7 @@
// Test that header file is self-contained. // Test that header file is self-contained.
#include <boost/beast/core/saved_handler.hpp> #include <boost/beast/core/saved_handler.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp> #include <boost/beast/_experimental/unit_test/suite.hpp>
#include <stdexcept> #include <stdexcept>
@ -46,7 +46,7 @@ public:
} }
void void
operator()() operator()(system::error_code ec_ = {})
{ {
failed_ = false; failed_ = false;
} }
@ -74,7 +74,7 @@ public:
} }
void void
operator()() operator()(system::error_code = {})
{ {
invoked_ = true; invoked_ = true;
} }
@ -90,7 +90,7 @@ public:
} }
void void
operator()() operator()(system::error_code = {})
{ {
} }
}; };
@ -131,10 +131,88 @@ public:
} }
} }
void
testSavedHandlerCancellation()
{
{
net::cancellation_signal sig;
saved_handler sh;
BEAST_EXPECT(! sh.has_value());
sh.emplace(
net::bind_cancellation_slot(
sig.slot(), handler{}));
BEAST_EXPECT(sh.has_value());
BEAST_EXPECT(sig.slot().has_handler());
sig.emit(net::cancellation_type::all);
BEAST_EXPECT(! sh.has_value());
BEAST_EXPECT(!sig.slot().has_handler());
sh.emplace(
net::bind_cancellation_slot(
sig.slot(), handler{}));
BEAST_EXPECT(sh.has_value());
BEAST_EXPECT(sig.slot().has_handler());
sig.emit(net::cancellation_type::total);
BEAST_EXPECT(sh.has_value());
BEAST_EXPECT(sig.slot().has_handler());
sig.emit(net::cancellation_type::terminal);
BEAST_EXPECT(! sh.has_value());
BEAST_EXPECT(!sig.slot().has_handler());
sh.emplace(
net::bind_cancellation_slot(
sig.slot(), handler{}),
net::cancellation_type::total);
BEAST_EXPECT(sh.has_value());
BEAST_EXPECT(sig.slot().has_handler());
sig.emit(net::cancellation_type::total);
BEAST_EXPECT(! sh.has_value());
BEAST_EXPECT(!sig.slot().has_handler());
{
saved_handler sh_inner;
sh_inner.emplace(
net::bind_cancellation_slot(
sig.slot(), handler{}));
sh = std::move(sh_inner);
}
BEAST_EXPECT(sh.has_value());
BEAST_EXPECT(sig.slot().has_handler());
sig.emit(net::cancellation_type::all);
BEAST_EXPECT(! sh.has_value());
BEAST_EXPECT(!sig.slot().has_handler());
}
{
saved_handler sh;
net::cancellation_signal sig;
try
{
sh.emplace(
net::bind_cancellation_slot(
sig.slot(),
throwing_handler{}));
fail();
}
catch(std::exception const&)
{
pass();
}
BEAST_EXPECT(!sig.slot().has_handler());
BEAST_EXPECT(! sh.has_value());
}
}
void void
run() override run() override
{ {
testSavedHandler(); testSavedHandler();
testSavedHandlerCancellation();
} }
}; };

View File

@ -25,6 +25,12 @@
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp> #include <boost/asio/strand.hpp>
#include <boost/asio/write.hpp> #include <boost/asio/write.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/connect_pipe.hpp>
#include <boost/asio/readable_pipe.hpp>
#include <boost/asio/writable_pipe.hpp>
#include <atomic> #include <atomic>
#if BOOST_ASIO_HAS_CO_AWAIT #if BOOST_ASIO_HAS_CO_AWAIT
@ -729,7 +735,57 @@ public:
} }
} }
void
testCancellation(yield_context do_yield)
{
// this is tested on a pipe
// because the test::stream doesn't implement cancellation
{
response<string_body> m;
error_code ec;
net::writable_pipe ts{ioc_};
net::readable_pipe tr{ioc_};
net::connect_pipe(tr, ts);
net::cancellation_signal cl;
net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
net::steady_timer timeout(ioc_, std::chrono::seconds(5));
timeout.async_wait(
[&](error_code ec)
{
BEAST_EXPECT(ec == net::error::operation_aborted);
if (!ec) // this means the cancel failed!
ts.close();
});
multi_buffer b;
async_read(tr, b, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
timeout.cancel();
BEAST_EXPECT(ec == net::error::operation_aborted);
}
{
response<string_body> m;
error_code ec;
net::writable_pipe ts{ioc_};
net::readable_pipe tr{ioc_};
net::connect_pipe(tr, ts);
net::cancellation_signal cl;
net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
net::steady_timer timeout(ioc_, std::chrono::seconds(5));
timeout.async_wait(
[&](error_code ec)
{
// using BEAST_EXPECT HERE is a race condition, since the test suite might
BEAST_EXPECT(ec == net::error::operation_aborted);
if (!ec) // this means the cancel failed!
ts.close();
});
multi_buffer b;
async_read(tr, b, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
timeout.cancel();
BEAST_EXPECT(ec == net::error::operation_aborted);
}
// the timer handler may be invoked after the test suite is complete if we don't post.
asio::post(ioc_, do_yield);
}
void void
run() override run() override
{ {
@ -761,6 +817,11 @@ public:
testReadSomeHeader(yield); testReadSomeHeader(yield);
}); });
testReadSomeHeader(); testReadSomeHeader();
yield_to(
[&](yield_context yield)
{
testCancellation(yield);
});
} }

View File

@ -22,8 +22,13 @@
#include <boost/beast/_experimental/test/stream.hpp> #include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/test/yield_to.hpp> #include <boost/beast/test/yield_to.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp> #include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/error.hpp> #include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include <boost/asio/connect_pipe.hpp>
#include <boost/asio/readable_pipe.hpp>
#include <boost/asio/writable_pipe.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/strand.hpp> #include <boost/asio/strand.hpp>
#include <sstream> #include <sstream>
#include <string> #include <string>
@ -1050,6 +1055,69 @@ public:
} }
#endif #endif
void
testCancellation(yield_context do_yield)
{
// this is tested on a pipe
// because the test::stream doesn't implement cancellation
{
response<string_body> m;
m.version(10);
m.result(status::ok);
m.set(field::server, "test");
m.set(field::content_length, "5");
// make the content big enough so it overflows the buffer
// that'll make the op never complete if we don't cancel
m.body().assign(10000000, '*');
error_code ec;
net::writable_pipe ts{ioc_};
net::readable_pipe tr{ioc_};
net::connect_pipe(tr, ts);
net::cancellation_signal cl;
net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
net::steady_timer timeout(ioc_, std::chrono::seconds(5));
timeout.async_wait(
[&](error_code ec)
{
BEAST_EXPECT(ec == net::error::operation_aborted);
if (!ec) // this means the cancel failed!
ts.close();
});
async_write(ts, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
timeout.cancel();
net::post(ioc_, do_yield); // wait for the timeout to finish
BEAST_EXPECT(ec == net::error::operation_aborted);
}
{
response<string_body> m;
m.version(11);
m.result(status::ok);
m.set(field::server, "test");
m.set(field::transfer_encoding, "chunked");
m.body().assign(10000000, '*');
error_code ec;
net::writable_pipe ts{ioc_};
net::readable_pipe tr{ioc_};
net::connect_pipe(tr, ts);
net::cancellation_signal cl;
net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
net::steady_timer timeout(ioc_, std::chrono::seconds(5));
timeout.async_wait(
[&](error_code ec)
{
BEAST_EXPECT(ec == net::error::operation_aborted);
if (!ec) // this means the cancel failed!
ts.close();
});
async_write(ts, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
timeout.cancel();
net::post(ioc_, do_yield); // wait for the timeout to finish
BEAST_EXPECT(ec == net::error::operation_aborted);
}
// the timer handler may be invoked after the test suite is complete if we don't post.
asio::post(ioc_, do_yield);
}
void void
run() override run() override
@ -1077,6 +1145,11 @@ public:
#if BOOST_ASIO_HAS_CO_AWAIT #if BOOST_ASIO_HAS_CO_AWAIT
boost::ignore_unused(&write_test::testAwaitableCompiles); boost::ignore_unused(&write_test::testAwaitableCompiles);
#endif #endif
yield_to(
[&](yield_context yield)
{
testCancellation(yield);
});
} }
}; };

View File

@ -21,6 +21,7 @@ add_executable (tests-beast-websocket
test.hpp test.hpp
_detail_prng.cpp _detail_prng.cpp
accept.cpp accept.cpp
cancel.cpp
close.cpp close.cpp
error.cpp error.cpp
frame.cpp frame.cpp

View File

@ -12,6 +12,7 @@ local SOURCES =
_detail_impl_base.cpp _detail_impl_base.cpp
_detail_prng.cpp _detail_prng.cpp
accept.cpp accept.cpp
cancel.cpp
close.cpp close.cpp
error.cpp error.cpp
frame.cpp frame.cpp

View File

@ -0,0 +1,230 @@
//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
// Test that header file is self-contained.
#include <boost/beast/websocket/stream.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/_experimental/test/tcp.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/redirect_error.hpp>
#include "test.hpp"
namespace boost {
namespace beast {
namespace websocket {
struct async_all_server_op : boost::asio::coroutine
{
stream<asio::ip::tcp::socket> & ws;
async_all_server_op(stream<asio::ip::tcp::socket> & ws) : ws(ws) {}
template<typename Self>
void operator()(Self && self, error_code ec = {}, std::size_t sz = 0)
{
if (ec)
return self.complete(ec);
BOOST_ASIO_CORO_REENTER(*this)
{
self.reset_cancellation_state([](net::cancellation_type ct){return ct;});
BOOST_ASIO_CORO_YIELD ws.async_handshake("test", "/", std::move(self));
BOOST_ASIO_CORO_YIELD ws.async_ping("", std::move(self));
BOOST_ASIO_CORO_YIELD ws.async_write_some(false, net::buffer("FOO", 3), std::move(self));
BOOST_ASIO_CORO_YIELD ws.async_write_some(true, net::buffer("BAR", 3), std::move(self));
BOOST_ASIO_CORO_YIELD ws.async_close("testing", std::move(self));
self.complete({});
}
}
};
template<BOOST_BEAST_ASYNC_TPARAM1 CompletionToken>
BOOST_BEAST_ASYNC_RESULT1(CompletionToken)
async_all_server(
stream<asio::ip::tcp::socket> & ws,
CompletionToken && token)
{
return net::async_compose<CompletionToken, void(error_code)>
(
async_all_server_op{ws},
token, ws
);
}
struct async_all_client_op : boost::asio::coroutine
{
stream<asio::ip::tcp::socket> & ws;
async_all_client_op(stream<asio::ip::tcp::socket> & ws) : ws(ws) {}
struct impl_t
{
impl_t () = default;
std::string res;
net::dynamic_string_buffer<char, std::char_traits<char>, std::allocator<char>> buf =
net::dynamic_buffer(res);
};
std::shared_ptr<impl_t> impl{std::make_shared<impl_t>()};
template<typename Self>
void operator()(Self && self, error_code ec = {}, std::size_t sz = 0)
{
if (ec)
return self.complete(ec);
BOOST_ASIO_CORO_REENTER(*this)
{
// let everything pass
self.reset_cancellation_state([](net::cancellation_type ct){return ct;});
BOOST_ASIO_CORO_YIELD ws.async_accept(std::move(self));
BOOST_ASIO_CORO_YIELD ws.async_pong("", std::move(self));
BOOST_ASIO_CORO_YIELD ws.async_read(impl->buf, std::move(self));
BEAST_EXPECTS(impl->res == "FOOBAR", impl->res);
BOOST_ASIO_CORO_YIELD ws.async_read(impl->buf, std::move(self));
BEAST_EXPECTS(ec == websocket::error::closed
|| ec == net::error::connection_reset
// hard coded winapi error (WSAECONNRESET), same as connection_reset, but asio delivers it with system_category
|| (ec.value() == 10054
&& ec.category() == system::system_category())
|| ec == net::error::not_connected, ec.message());
self.complete({});
}
}
};
template<BOOST_BEAST_ASYNC_TPARAM1 CompletionToken>
BOOST_BEAST_ASYNC_RESULT1(CompletionToken)
async_all_client(
stream<asio::ip::tcp::socket> & ws,
CompletionToken && token)
{
return net::async_compose<CompletionToken, void(error_code)>
(
async_all_client_op{ws},
token, ws
);
}
class cancel_test : public websocket_test_suite
{
public:
std::size_t run_impl(net::cancellation_signal &sl,
net::io_context &ctx,
std::size_t trigger = -1,
net::cancellation_type tp = net::cancellation_type::terminal)
{
std::size_t cnt = 0;
std::size_t res = 0u;
while ((res = ctx.run_one()) != 0)
if (trigger == cnt ++)
{
sl.emit(tp);
}
return cnt;
}
std::size_t testAll(std::size_t & cancel_counter,
bool cancel_server = false,
std::size_t trigger = -1)
{
net::cancellation_signal sig1, sig2;
net::io_context ioc;
using tcp = net::ip::tcp;
stream<tcp::socket> ws1(ioc.get_executor());
stream<tcp::socket> ws2(ioc.get_executor());
test::connect(ws1.next_layer(), ws2.next_layer());
async_all_server(ws1,
net::bind_cancellation_slot(sig1.slot(), [&](system::error_code ec)
{
if (ec)
{
if (ec == net::error::operation_aborted &&
(cancel_server || trigger == static_cast<std::size_t>(-1)))
cancel_counter++;
BEAST_EXPECTS(ec == net::error::operation_aborted
|| ec == net::error::broken_pipe
// winapi WSAECONNRESET, as system_category
|| ec == error_code(10054, boost::system::system_category())
|| ec == net::error::bad_descriptor
|| ec == net::error::eof, ec.message());
get_lowest_layer(ws1).close();
}
}));
async_all_client(
ws2,
net::bind_cancellation_slot(sig2.slot(), [&](system::error_code ec)
{
if (ec)
{
if (ec == net::error::operation_aborted &&
(!cancel_server || trigger == static_cast<std::size_t>(-1)))
cancel_counter++;
BEAST_EXPECTS(ec == net::error::operation_aborted
|| ec == error::closed
|| ec == net::error::broken_pipe
|| ec == net::error::connection_reset
|| ec == net::error::not_connected
// winapi WSAECONNRESET, as system_category
|| ec == error_code(10054, boost::system::system_category())
|| ec == net::error::eof, ec.message());
get_lowest_layer(ws1).close();
}
}));
return run_impl(cancel_server ? sig1 : sig2, ioc, trigger);
}
void brute_force()
{
std::size_t cancel_counter = 0;
const auto init = testAll(cancel_counter);
BEAST_EXPECT(cancel_counter == 0u);
for (std::size_t cnt = 0; cnt < init; cnt ++)
testAll(cancel_counter, true, cnt);
BEAST_EXPECT(cancel_counter > 0u);
cancel_counter = 0u;
for (std::size_t cnt = 0; cnt < init; cnt ++)
testAll(cancel_counter, false, cnt);
BEAST_EXPECT(cancel_counter > 0u);
}
void
run() override
{
brute_force();
}
};
BEAST_DEFINE_TESTSUITE(beast,websocket,cancel);
} // websocket
} // beast
} // boost

View File

@ -251,6 +251,9 @@ struct handler
using executor_type = boost::asio::io_context::executor_type; using executor_type = boost::asio::io_context::executor_type;
executor_type get_executor() const noexcept; executor_type get_executor() const noexcept;
using cancellation_slot_type = boost::asio::cancellation_slot;
cancellation_slot_type get_cancellation_slot() const noexcept;
void operator()(boost::beast::error_code, std::size_t); void operator()(boost::beast::error_code, std::size_t);
}; };
//] //]
@ -265,6 +268,12 @@ inline auto handler::get_executor() const noexcept ->
static boost::asio::io_context ioc; static boost::asio::io_context ioc;
return ioc.get_executor(); return ioc.get_executor();
} }
inline auto handler::get_cancellation_slot() const noexcept ->
cancellation_slot_type
{
return cancellation_slot_type();
}
inline void handler::operator()( inline void handler::operator()(
boost::beast::error_code, std::size_t) boost::beast::error_code, std::size_t)
{ {
@ -296,6 +305,18 @@ struct associated_executor<handler, Executor>
Executor const& ex = Executor{}) noexcept; Executor const& ex = Executor{}) noexcept;
}; };
template<class CancellationSlot>
struct associated_cancellation_slot<handler, CancellationSlot>
{
using type = cancellation_slot;
static
type
get(handler const& h,
CancellationSlot const& cs = CancellationSlot{}) noexcept;
};
} // boost } // boost
} // asio } // asio
//] //]
@ -315,6 +336,15 @@ get(handler const&, Executor const&) noexcept -> type
return {}; return {};
} }
template<class CancellationSlot>
auto
boost::asio::associated_cancellation_slot<handler, CancellationSlot>::
get(handler const&, CancellationSlot const&) noexcept -> type
{
return {};
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace boost { namespace boost {