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