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:
* Expect header field with the "100-continue" is handled in upgrade.
@ -19,7 +25,6 @@ Version 339:
--------------------------------------------------------------------------------
Version 338:
* 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]]
returned by
[@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
[@boost:/doc/html/boost_asio/reference/associated_executor.html ['associated executor]]
returned by
@ -210,6 +214,8 @@ These associations may be specified intrusively:
Or these associations may be specified non-intrusively, by specializing
the class templates
[@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
[@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
`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
handlers run. Every I/O object refers to an __ExecutionContext__ for
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_);
}
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
operator()(error_code ec)
{

View File

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

View File

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

View File

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

View File

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

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_()
, 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((

View File

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

View File

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

View File

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

View File

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

View File

@ -200,6 +200,19 @@ read_some(
@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
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<
class AsyncReadStream,
@ -397,6 +410,19 @@ read_header(
there is sufficient pre-existing message data in the dynamic buffer. The
implementation will call @ref basic_parser::eager with the value `false`
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<
class AsyncReadStream,
@ -594,6 +620,19 @@ read(
there is sufficient pre-existing message data in the dynamic buffer. The
implementation will call @ref basic_parser::eager with the value `true`
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<
class AsyncReadStream,
@ -800,6 +839,19 @@ read(
there is sufficient pre-existing message data in the dynamic buffer. The
implementation will call @ref basic_parser::eager with the value `true`
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<
class AsyncReadStream,

View File

@ -161,6 +161,18 @@ write_some(
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
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
*/
template<
@ -291,6 +303,18 @@ write_header(
@note The implementation will call @ref serializer::split with
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
*/
template<
@ -412,6 +436,18 @@ write(
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
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
*/
template<
@ -637,6 +673,18 @@ write(
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
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
*/
template<
@ -699,6 +747,18 @@ async_write(
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
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
*/
template<

View File

@ -266,6 +266,9 @@ public:
// 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 Handler, class Decorator>
class stream<NextLayer, deflateSupported>::accept_op

View File

@ -90,9 +90,15 @@ public:
BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__,
"websocket::async_close"));
impl.op_close.emplace(std::move(*this));
this->set_allowed_cancellation(net::cancellation_type::all);
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);
BOOST_ASIO_CORO_YIELD
{
@ -143,9 +149,17 @@ public:
BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__,
"websocket::async_close"));
// terminal only, that's the default
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);
BOOST_ASIO_CORO_YIELD
{
@ -185,7 +199,7 @@ public:
beast::detail::bind_continuation(std::move(*this)));
}
impl.rd_buf.commit(bytes_transferred);
if(impl.check_stop_now(ec))
if(impl.check_stop_now(ec)) //< this catches cancellation
goto upcall;
}
if(detail::is_control(impl.rd_fh.op))

View File

@ -86,9 +86,12 @@ public:
BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__,
"websocket::async_ping"));
impl.op_ping.emplace(std::move(*this));
this->set_allowed_cancellation(net::cancellation_type::all);
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);
BOOST_ASIO_CORO_YIELD
{

View File

@ -107,8 +107,14 @@ public:
__FILE__, __LINE__,
"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);
BOOST_ASIO_CORO_YIELD
{
@ -275,6 +281,9 @@ public:
impl.op_rd.emplace(std::move(*this));
}
if (ec)
return this->complete(cont, ec, bytes_written_);
impl.wr_block.lock(this);
BOOST_ASIO_CORO_YIELD
{
@ -629,6 +638,9 @@ public:
impl.op_rd.emplace(std::move(*this));
}
if (ec)
return this->complete(cont, ec, bytes_written_);
impl.wr_block.lock(this);
BOOST_ASIO_CORO_YIELD
{

View File

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

View File

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

View File

@ -181,9 +181,14 @@ operator()(
"websocket::async_write" :
"websocket::async_write_some"
));
impl.op_wr.emplace(std::move(*this));
this->set_allowed_cancellation(net::cancellation_type::all);
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);
BOOST_ASIO_CORO_YIELD
{

View File

@ -68,6 +68,18 @@ teardown(
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_teardown
and @c async_shutdown operation.
*/
template<class AsyncStream, class TeardownHandler>
void

View File

@ -1602,6 +1602,20 @@ 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::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
@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
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::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<
BOOST_BEAST_ASYNC_TPARAM1 WriteHandler =
@ -1814,6 +1843,21 @@ 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::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<
BOOST_BEAST_ASYNC_TPARAM1 WriteHandler =
@ -1975,6 +2019,21 @@ 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::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 DynamicBuffer,
@ -2154,6 +2213,21 @@ 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::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 DynamicBuffer,
@ -2258,6 +2332,21 @@ public:
from the beginning.
@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>
std::size_t
@ -2329,6 +2418,22 @@ 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::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,
@ -2452,6 +2557,21 @@ 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::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 ConstBufferSequence,
@ -2490,6 +2610,7 @@ public:
@return The number of bytes sent from the buffers.
@throws system_error Thrown on failure.
*/
template<class ConstBufferSequence>
std::size_t
@ -2575,6 +2696,21 @@ 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::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 ConstBufferSequence,

View File

@ -163,6 +163,17 @@ teardown(
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_wait operation.
*/
template<
class Protocol, class Executor,

View File

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

View File

@ -39,6 +39,7 @@ local SOURCES =
file_posix.cpp
file_stdio.cpp
file_win32.cpp
filtering_cancellation_slot.cpp
flat_buffer.cpp
flat_static_buffer.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.
#include <boost/beast/core/saved_handler.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <stdexcept>
@ -46,7 +46,7 @@ public:
}
void
operator()()
operator()(system::error_code ec_ = {})
{
failed_ = false;
}
@ -74,7 +74,7 @@ public:
}
void
operator()()
operator()(system::error_code = {})
{
invoked_ = true;
}
@ -90,7 +90,7 @@ public:
}
void
operator()()
operator()(system::error_code = {})
{
}
};
@ -119,7 +119,7 @@ public:
{
saved_handler sh;
try
{
{
sh.emplace(throwing_handler{});
fail();
}
@ -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
run() override
{
testSavedHandler();
testSavedHandlerCancellation();
}
};

View File

@ -25,6 +25,12 @@
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.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>
#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
run() override
{
@ -761,6 +817,11 @@ public:
testReadSomeHeader(yield);
});
testReadSomeHeader();
yield_to(
[&](yield_context yield)
{
testCancellation(yield);
});
}

View File

@ -22,8 +22,13 @@
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/test/yield_to.hpp>
#include <boost/beast/_experimental/unit_test/suite.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 <boost/asio/steady_timer.hpp>
#include <boost/asio/strand.hpp>
#include <sstream>
#include <string>
@ -1050,6 +1055,69 @@ public:
}
#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
run() override
@ -1077,6 +1145,11 @@ public:
#if BOOST_ASIO_HAS_CO_AWAIT
boost::ignore_unused(&write_test::testAwaitableCompiles);
#endif
yield_to(
[&](yield_context yield)
{
testCancellation(yield);
});
}
};

View File

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

View File

@ -12,6 +12,7 @@ local SOURCES =
_detail_impl_base.cpp
_detail_prng.cpp
accept.cpp
cancel.cpp
close.cpp
error.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;
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);
};
//]
@ -265,6 +268,12 @@ inline auto handler::get_executor() const noexcept ->
static boost::asio::io_context ioc;
return ioc.get_executor();
}
inline auto handler::get_cancellation_slot() const noexcept ->
cancellation_slot_type
{
return cancellation_slot_type();
}
inline void handler::operator()(
boost::beast::error_code, std::size_t)
{
@ -296,6 +305,18 @@ struct associated_executor<handler, Executor>
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
} // asio
//]
@ -315,6 +336,15 @@ get(handler const&, Executor const&) noexcept -> type
return {};
}
template<class CancellationSlot>
auto
boost::asio::associated_cancellation_slot<handler, CancellationSlot>::
get(handler const&, CancellationSlot const&) noexcept -> type
{
return {};
}
//------------------------------------------------------------------------------
namespace boost {