diff --git a/CHANGELOG.md b/CHANGELOG.md index d462cdb2..d327fdf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Version 203 * Update networking refresher doc * Include error code in call to set_option +* saved_handler is a public interface -------------------------------------------------------------------------------- diff --git a/doc/qbk/03_core/5_composed.qbk b/doc/qbk/03_core/5_composed.qbk index eca3836f..ee2c03d6 100644 --- a/doc/qbk/03_core/5_composed.qbk +++ b/doc/qbk/03_core/5_composed.qbk @@ -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. +]] ] diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 67078606..c6119a11 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -203,6 +203,7 @@ iequal iless multi_buffer + saved_handler span static_buffer static_buffer_base diff --git a/include/boost/beast/core/impl/saved_handler.hpp b/include/boost/beast/core/impl/saved_handler.hpp new file mode 100644 index 00000000..2492beb8 --- /dev/null +++ b/include/boost/beast/core/impl/saved_handler.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +//------------------------------------------------------------------------------ + +class saved_handler::base +{ +protected: + ~base() = default; + +public: + base() = default; + virtual void destroy() = 0; + virtual void invoke() = 0; +}; + +//------------------------------------------------------------------------------ + +template +class saved_handler::impl final : public base +{ + using alloc_type = typename + beast::detail::allocator_traits< + Alloc>::template rebind_alloc; + + using alloc_traits = + beast::detail::allocator_traits; + + struct ebo_pair : boost::empty_value + { + Handler h; + + template + ebo_pair( + alloc_type const& a, + Handler_&& h_) + : boost::empty_value( + boost::empty_init_t{}, a) + , h(std::forward(h_)) + { + } + }; + + ebo_pair v_; + net::executor_work_guard< + net::associated_executor_t> wg_; + +public: + template + impl(alloc_type const& a, Handler_&& h) + : v_(a, std::forward(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 +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:: + template rebind_alloc>; + using alloc_traits = + beast::detail::allocator_traits; + struct storage + { + alloc_type a; + impl* 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)); + p_ = boost::exchange(s.p, nullptr); +} + +template +void +saved_handler:: +emplace(Handler&& handler) +{ + // Can't delete a handler before invoking + BOOST_ASSERT(! has_value()); + emplace( + std::forward(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 diff --git a/include/boost/beast/core/saved_handler.hpp b/include/boost/beast/core/saved_handler.hpp new file mode 100644 index 00000000..7677bc19 --- /dev/null +++ b/include/boost/beast/core/saved_handler.hpp @@ -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 + +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 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 + 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 + 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 + +#endif diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt index cac9f85e..73708202 100644 --- a/test/beast/core/CMakeLists.txt +++ b/test/beast/core/CMakeLists.txt @@ -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 diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile index e2e76f43..f7b927fc 100644 --- a/test/beast/core/Jamfile +++ b/test/beast/core/Jamfile @@ -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 diff --git a/test/beast/core/_detail_buffer.cpp b/test/beast/core/_detail_buffer.cpp index 2471e328..7b5c5b6d 100644 --- a/test/beast/core/_detail_buffer.cpp +++ b/test/beast/core/_detail_buffer.cpp @@ -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 @@ -76,7 +76,7 @@ public: } }; -BEAST_DEFINE_TESTSUITE(beast,core,detail_buffer); +BEAST_DEFINE_TESTSUITE(beast,core,buffer); } // detail } // beast diff --git a/test/beast/core/saved_handler.cpp b/test/beast/core/saved_handler.cpp new file mode 100644 index 00000000..0bd15bac --- /dev/null +++ b/test/beast/core/saved_handler.cpp @@ -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 + +#include +#include + +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{}); + 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