saved_handler is a public interface:

This container allows completion handlers to be safely
suspended and resumed later, or destroyed.
This commit is contained in:
Vinnie Falco
2019-01-12 14:13:20 -08:00
parent be800436ea
commit 3092e43879
9 changed files with 486 additions and 2 deletions

View File

@ -2,6 +2,7 @@ Version 203
* Update networking refresher doc
* Include error code in call to set_option
* saved_handler is a public interface
--------------------------------------------------------------------------------

View File

@ -97,6 +97,13 @@ composed operations:
handler's associated allocator, benefiting from all handler memory
management optimizations transparently.
]]
[[
[link beast.ref.boost__beast__saved_handler `saved_handler`]
][
This wrapper safely stores a completion handler so it may be invoked
later, allowing an implementation to "pause" an operation until some
condition is met.
]]
]

View File

@ -203,6 +203,7 @@
<member><link linkend="beast.ref.boost__beast__iequal">iequal</link></member>
<member><link linkend="beast.ref.boost__beast__iless">iless</link></member>
<member><link linkend="beast.ref.boost__beast__multi_buffer">multi_buffer</link></member>
<member><link linkend="beast.ref.boost__beast__saved_handler">saved_handler</link></member>
<member><link linkend="beast.ref.boost__beast__span">span</link></member>
<member><link linkend="beast.ref.boost__beast__static_buffer">static_buffer</link></member>
<member><link linkend="beast.ref.boost__beast__static_buffer_base">static_buffer_base</link></member>

View File

@ -0,0 +1,193 @@
//
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_CORE_IMPL_SAVED_HANDLER_HPP
#define BOOST_BEAST_CORE_IMPL_SAVED_HANDLER_HPP
#include <boost/beast/core/detail/allocator.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/assert.hpp>
#include <boost/core/empty_value.hpp>
#include <boost/core/exchange.hpp>
#include <memory>
#include <utility>
namespace boost {
namespace beast {
//------------------------------------------------------------------------------
class saved_handler::base
{
protected:
~base() = default;
public:
base() = default;
virtual void destroy() = 0;
virtual void invoke() = 0;
};
//------------------------------------------------------------------------------
template<class Handler, class Alloc>
class saved_handler::impl final : public base
{
using alloc_type = typename
beast::detail::allocator_traits<
Alloc>::template rebind_alloc<impl>;
using alloc_traits =
beast::detail::allocator_traits<alloc_type>;
struct ebo_pair : boost::empty_value<alloc_type>
{
Handler h;
template<class Handler_>
ebo_pair(
alloc_type const& a,
Handler_&& h_)
: boost::empty_value<alloc_type>(
boost::empty_init_t{}, a)
, h(std::forward<Handler_>(h_))
{
}
};
ebo_pair v_;
net::executor_work_guard<
net::associated_executor_t<Handler>> wg_;
public:
template<class Handler_>
impl(alloc_type const& a, Handler_&& h)
: v_(a, std::forward<Handler_>(h))
, wg_(net::get_associated_executor(v_.h))
{
}
void
destroy() override
{
auto v = std::move(v_);
alloc_traits::destroy(v.get(), this);
alloc_traits::deallocate(v.get(), this, 1);
}
void
invoke() override
{
auto v = std::move(v_);
alloc_traits::destroy(v.get(), this);
alloc_traits::deallocate(v.get(), this, 1);
v.h();
}
};
//------------------------------------------------------------------------------
saved_handler::
~saved_handler()
{
if(p_)
p_->destroy();
}
saved_handler::
saved_handler(saved_handler&& other) noexcept
: p_(boost::exchange(other.p_, nullptr))
{
}
saved_handler&
saved_handler::
operator=(saved_handler&& other) noexcept
{
// Can't delete a handler before invoking
BOOST_ASSERT(! has_value());
p_ = boost::exchange(other.p_, nullptr);
return *this;
}
template<class Handler, class Allocator>
void
saved_handler::
emplace(Handler&& handler, Allocator const& alloc)
{
// Can't delete a handler before invoking
BOOST_ASSERT(! has_value());
using alloc_type = typename
beast::detail::allocator_traits<Allocator>::
template rebind_alloc<impl<Handler, Allocator>>;
using alloc_traits =
beast::detail::allocator_traits<alloc_type>;
struct storage
{
alloc_type a;
impl<Handler, Allocator>* p;
explicit
storage(Allocator const& a_)
: a(a_)
, p(alloc_traits::allocate(a, 1))
{
}
~storage()
{
if(p)
alloc_traits::deallocate(a, p, 1);
}
};
storage s(alloc);
alloc_traits::construct(s.a, s.p,
s.a, std::forward<Handler>(handler));
p_ = boost::exchange(s.p, nullptr);
}
template<class Handler>
void
saved_handler::
emplace(Handler&& handler)
{
// Can't delete a handler before invoking
BOOST_ASSERT(! has_value());
emplace(
std::forward<Handler>(handler),
net::get_associated_allocator(handler));
}
void
saved_handler::
invoke()
{
// Can't invoke without a value
BOOST_ASSERT(has_value());
boost::exchange(
p_, nullptr)->invoke();
}
bool
saved_handler::
maybe_invoke()
{
if(! p_)
return false;
boost::exchange(
p_, nullptr)->invoke();
return true;
}
} // beast
} // boost
#endif

View File

@ -0,0 +1,122 @@
//
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_CORE_SAVED_HANDLER_HPP
#define BOOST_BEAST_CORE_SAVED_HANDLER_HPP
#include <boost/beast/core/detail/config.hpp>
namespace boost {
namespace beast {
/** An invocable, nullary function object which holds a completion handler.
This container can hold a type-erased instance of any completion
handler, or it can be empty. When the container holds a value,
the implementation maintains an instance of `net::executor_work_guard`
for the handler's associated executor. Memory is dynamically allocated
to store the completion handler, and the allocator may optionally
be specified. Otherwise, the implementation uses the handler's
associated allocator.
*/
class saved_handler
{
class base;
template<class, class>
class impl;
base* p_ = nullptr;
public:
/// Default Constructor
saved_handler() = default;
/// Copy Constructor (deleted)
saved_handler(saved_handler const&) = delete;
/// Copy Assignment (deleted)
saved_handler& operator=(saved_handler const&) = delete;
/// Destructor
BOOST_BEAST_DECL
~saved_handler();
/// Move Constructor
BOOST_BEAST_DECL
saved_handler(saved_handler&& other) noexcept;
/// Move Assignment
BOOST_BEAST_DECL
saved_handler&
operator=(saved_handler&& other) noexcept;
/// Returns `true` if `*this` contains a completion handler.
bool
has_value() const noexcept
{
return p_ != nullptr;
}
/** Store a completion handler in the container.
Requires `this->has_value() == false`.
@param handler The completion handler to store.
@param alloc The allocator to use.
*/
template<class Handler, class Allocator>
void
emplace(Handler&& handler, Allocator const& alloc);
/** Store a completion handler in the container.
Requires `this->has_value() == false`. The
implementation will use the handler's associated
allocator to obtian storage.
@param handler The completion handler to store.
*/
template<class Handler>
void
emplace(Handler&& handler);
/** Unconditionally invoke the stored completion handler.
Requires `this->has_value() == true`. Any dynamic memory
used is deallocated before the stored completion handler
is invoked. The executor work guard is also reset before
the invocation.
*/
BOOST_BEAST_DECL
void
invoke();
/** Conditionally invoke the stored completion handler.
Invokes the stored completion handler if
`this->has_value() == true`, otherwise does nothing. Any
dynamic memory used is deallocated before the stored completion
handler is invoked. The executor work guard is also reset before
the invocation.
@return `true` if the invocation took place.
*/
BOOST_BEAST_DECL
bool
maybe_invoke();
};
} // beast
} // boost
#include <boost/beast/core/impl/saved_handler.hpp>
#endif

View File

@ -51,6 +51,7 @@ add_executable (tests-beast-core
multi_buffer.cpp
ostream.cpp
read_size.cpp
saved_handler.cpp
span.cpp
static_buffer.cpp
static_string.cpp

View File

@ -40,6 +40,7 @@ local SOURCES =
multi_buffer.cpp
ostream.cpp
read_size.cpp
saved_handler.cpp
span.cpp
static_buffer.cpp
static_string.cpp

View File

@ -24,7 +24,7 @@ namespace detail {
// VFALCO No idea why boost::system::errc::message_size fails
// to compile, so we use net::error::eof instead.
//
class detail_buffer_test : public beast::unit_test::suite
class buffer_test : public beast::unit_test::suite
{
public:
template<class DynamicBuffer>
@ -76,7 +76,7 @@ public:
}
};
BEAST_DEFINE_TESTSUITE(beast,core,detail_buffer);
BEAST_DEFINE_TESTSUITE(beast,core,buffer);
} // detail
} // beast

View File

@ -0,0 +1,158 @@
//
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
// Test that header file is self-contained.
#include <boost/beast/core/saved_handler.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <stdexcept>
namespace boost {
namespace beast {
//------------------------------------------------------------------------------
class saved_handler_test : public unit_test::suite
{
public:
class handler
{
unit_test::suite& s_;
bool failed_ = true;
bool throw_on_move_ = false;
public:
handler(handler const&) = delete;
handler& operator=(handler&&) = delete;
handler& operator=(handler const&) = delete;
~handler()
{
s_.BEAST_EXPECT(! failed_);
}
explicit
handler(unit_test::suite& s)
: s_(s)
{
}
handler(handler&& other)
: s_(other.s_)
, failed_(boost::exchange(
other.failed_, false))
{
if(throw_on_move_)
throw std::bad_alloc{};
}
void
operator()()
{
failed_ = false;
}
};
class unhandler
{
unit_test::suite& s_;
bool invoked_ = false;
public:
unhandler(unhandler const&) = delete;
unhandler& operator=(unhandler&&) = delete;
unhandler& operator=(unhandler const&) = delete;
~unhandler()
{
s_.BEAST_EXPECT(! invoked_);
}
explicit
unhandler(unit_test::suite& s)
: s_(s)
{
}
unhandler(unhandler&& other)
: s_(other.s_)
, invoked_(boost::exchange(
other.invoked_, false))
{
}
void
operator()()
{
invoked_ = true;
}
};
struct throwing_handler
{
throwing_handler() = default;
throwing_handler(throwing_handler&&)
{
BOOST_THROW_EXCEPTION(std::exception{});
}
void
operator()()
{
}
};
void
testSavedHandler()
{
{
saved_handler sh;
BEAST_EXPECT(! sh.has_value());
sh.emplace(handler{*this});
BEAST_EXPECT(sh.has_value());
sh.invoke();
BEAST_EXPECT(! sh.has_value());
sh.emplace(handler{*this}, std::allocator<char>{});
BEAST_EXPECT(sh.has_value());
sh.invoke();
BEAST_EXPECT(! sh.has_value());
sh.emplace(unhandler{*this});
BEAST_EXPECT(sh.has_value());
}
{
saved_handler sh;
try
{
sh.emplace(throwing_handler{});
fail();
}
catch(std::exception const&)
{
pass();
}
BEAST_EXPECT(! sh.has_value());
}
}
void
run() override
{
testSavedHandler();
}
};
BEAST_DEFINE_TESTSUITE(beast,core,saved_handler);
} // beast
} // boost