From 02fb4983f6fc9ee24e525a42469a5baaedcd2ac9 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 23 Nov 2018 18:14:03 -0800 Subject: [PATCH] Fixes to timeout services (experimental) --- CHANGELOG.md | 3 +- doc/qbk/quickref.xml | 3 + .../core/detail/impl/timeout_service.hpp | 326 ++++++++++++++ .../core/detail/impl/timeout_service.ipp | 181 -------- .../core/detail/saved_handler.hpp | 109 +++++ .../core/detail/timeout_service.hpp | 139 +++--- .../core/detail/timeout_service_base.hpp | 38 ++ .../core/impl/timeout_service.hpp | 64 +++ .../core/impl/timeout_service.ipp | 31 -- .../core/impl/timeout_socket.hpp | 341 +++++++++++--- .../_experimental/core/timeout_service.hpp | 105 ++++- .../_experimental/core/timeout_socket.hpp | 419 +++++++++++++++++- .../core/{detail => }/timeout_work_guard.hpp | 39 +- include/boost/beast/core/detail/config.hpp | 2 + test/beast/experimental/timeout_socket.cpp | 5 +- .../beast/experimental/timeout_work_guard.cpp | 2 +- 16 files changed, 1432 insertions(+), 375 deletions(-) create mode 100644 include/boost/beast/_experimental/core/detail/impl/timeout_service.hpp delete mode 100644 include/boost/beast/_experimental/core/detail/impl/timeout_service.ipp create mode 100644 include/boost/beast/_experimental/core/detail/saved_handler.hpp create mode 100644 include/boost/beast/_experimental/core/detail/timeout_service_base.hpp create mode 100644 include/boost/beast/_experimental/core/impl/timeout_service.hpp delete mode 100644 include/boost/beast/_experimental/core/impl/timeout_service.ipp rename include/boost/beast/_experimental/core/{detail => }/timeout_work_guard.hpp (51%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e4325e7..8b5dbfe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,8 @@ -Version 192: - * Use mp11::integer_sequence * Tidy up warnings and deprecated usage * http::message is not-a boost::empty_value * Fix link in docs +* Fixes to timeout services (experimental) -------------------------------------------------------------------------------- diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 1459744f..162c9dbc 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -310,12 +310,15 @@ http::icy_stream test::fail_count test::stream + timeout_handle timeout_socket + timeout_work_guard Functions + async_connect set_timeout_service_options test::connect diff --git a/include/boost/beast/_experimental/core/detail/impl/timeout_service.hpp b/include/boost/beast/_experimental/core/detail/impl/timeout_service.hpp new file mode 100644 index 00000000..8bc90b35 --- /dev/null +++ b/include/boost/beast/_experimental/core/detail/impl/timeout_service.hpp @@ -0,0 +1,326 @@ +// +// Copyright (c) 2018 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_DETAIL_IMPL_TIMEOUT_SERVICE_HPP +#define BOOST_BEAST_CORE_DETAIL_IMPL_TIMEOUT_SERVICE_HPP + +namespace boost { +namespace beast { +namespace detail { + +timeout_service:: +timeout_service(boost::asio::io_context& ctx) + : service_base(ctx) + , thunks_(1) // element [0] reserved for "null" + , timer_(ctx) +{ +} + +timeout_handle +timeout_service:: +make_handle() +{ + std::lock_guard lock(m_); + if(free_thunk_ != 0) + { + auto const n = free_thunk_; + auto& t = thunks_[n]; + free_thunk_ = t.pos; // next in free list + t = {}; + return timeout_handle(n, *this); + } + auto const n = thunks_.size(); + thunks_.emplace_back(); + return timeout_handle(n, *this); +} + +void +timeout_service:: +set_option(std::chrono::seconds n) +{ + interval_ = n; +} + +template +void +timeout_service:: +set_callback( + timeout_handle h, + Executor const& ex, + CancelHandler&& handler) +{ + thunks_[h.id_].callback.emplace( + callback< + Executor, + typename std::decay::type>{ + h, ex, + std::forward(handler)}); +} + +void +timeout_service:: +on_work_started(timeout_handle h) +{ + BOOST_ASSERT(h.id_ != 0); + if( [this, h] + { + std::lock_guard lock(m_); + auto& t = thunks_[h.id_]; + insert(t, *fresh_); + return ++pending_ == 1; + }()) + { + do_async_wait(); + } +} + +void +timeout_service:: +on_work_stopped(timeout_handle h) +{ + BOOST_ASSERT(h.id_ != 0); + std::lock_guard lock(m_); + auto& t = thunks_[h.id_]; + if(t.list != nullptr) + { + BOOST_ASSERT(! t.expired); + remove(t); + } + if(--pending_ == 0) + timer_.cancel(); + BOOST_ASSERT(pending_ >= 0); +} + +/* + Synchronization points + + (A) async_op invoke + (B) timeout handler invoke expired=true + (C) posted handler invoked canceled=true + + ---------------------------------------------- + + Linearized paths (for async_read) + + ---------------------------------------------- + + 1. async_read + 2. async_read complete, async_op posted +(A) 3. async_op invoked + - work_.try_complete() returns true + + expired==false + + thunk is removed from list + + ---------------------------------------------- + + 1. async_read + 2. async_read complete, async_op posted +(B) 3. timeout, cancel posted + - expired=true + - thunk is removed from list +(A) 4. async_op invoked + - work_.try_complete() returns false + + completed=true + - handler is saved +(C) 5. cancel invoked + - saved handler is invoked + + expired==true, canceled==false, completed==true + + work_.try_complete() returns true + + ---------------------------------------------- + + The following two paths are not distinguishable: + + 1. async_read +(B) 2. timeout, cancel posted + - expired=true + - thunk is removed from list + 3. async_read complete, async_op posted +(C) 4. cancel invoked + - socket::cancel called (what does this do?) + - canceled=true +(A) 5. async_op invoked + - expired==true, canceled==true, completed==false + - work_.try_complete() returns `true` + + 1. async_read +(B) 2. timeout, `cancel` posted + - expired=true + - thunk is removed from list +(C) 3. cancel invoked, async_read canceled + - socket::cancel called + - canceled=true +(A) 4. async_op invoked, ec==operation_aborted + - expired==true, canceled==true, completed=false + - work_.try_complete()` returns true +*/ +bool +timeout_service:: +on_try_work_complete(timeout_handle h) +{ + BOOST_ASSERT(h.id_ != 0); + std::lock_guard lock(m_); + auto& t = thunks_[h.id_]; + if(! t.expired) + { + // hot path: operation complete + BOOST_ASSERT(t.list != nullptr); + BOOST_ASSERT(! t.canceled); + BOOST_ASSERT(! t.completed); + remove(t); + return true; + } + BOOST_ASSERT(t.list == nullptr); + if(! t.canceled) + { + // happens when operation completes before + // posted cancel handler is invoked. + t.completed = true; + return false; + } + if(t.completed) + { + // happens when the saved handler is + // invoked from the posted callback + t.expired = false; + t.canceled = false; + t.completed = false; + return true; + } + // happens when operation_aborted is delivered + t.expired = false; + t.canceled = false; + return true; +} + +void +timeout_service:: +on_cancel(timeout_handle h) +{ + std::lock_guard lock(m_); + auto& t = thunks_[h.id_]; + BOOST_ASSERT(t.expired); + t.canceled = true; +} + +//------------------------------------------------------------------------------ + +void +timeout_service:: +destroy(timeout_handle h) +{ + BOOST_ASSERT(h.id_ != 0); + std::lock_guard lock(m_); + thunks_[h.id_].pos = free_thunk_; + free_thunk_ = h.id_; +} + +// Precondition: caller holds the mutex +void +timeout_service:: +insert( + thunk& t, + thunk::list_type& list) +{ + BOOST_ASSERT(t.list == nullptr); + list.emplace_back(&t); // can throw + t.list = &list; + t.pos = list.size(); +} + +// Precondition: caller holds the mutex +void +timeout_service:: +remove(thunk& t) +{ + BOOST_ASSERT(t.list != nullptr); + BOOST_ASSERT( + t.list == stale_ || + t.list == fresh_); + BOOST_ASSERT(t.list->size() > 0); + auto& list = *t.list; + auto const n = list.size() - 1; + if(t.pos != n) + { + // move back element to t.pos + list[t.pos] = list[n]; + list[t.pos]->pos = t.pos; + } + t.list = nullptr; + list.resize(n); +} + +void +timeout_service:: +do_async_wait() +{ + timer_.expires_after(interval_); + timer_.async_wait( + [this](error_code ec) + { + this->on_timer(ec); + }); +} + +void +timeout_service:: +on_timer(error_code ec) +{ + if(ec == boost::asio::error::operation_aborted) + { + BOOST_ASSERT(fresh_->empty()); + BOOST_ASSERT(stale_->empty()); + return; + } + std::vector expired; + { + std::lock_guard lock(m_); + if(! stale_->empty()) + { + for(auto t : *stale_) + { + // remove from list + t->list = nullptr; + t->expired = true; + } + std::swap(expired, *stale_); + stale_->reserve(expired.size() / 2); + } + std::swap(fresh_, stale_); + } + for(auto p : expired) + p->callback(); + if( [this] + { + std::lock_guard lock(m_); + BOOST_ASSERT(pending_); + pending_ = + ! stale_->empty() || + ! fresh_->empty(); + return pending_; + }()) + { + do_async_wait(); + } +} + +void +timeout_service:: +shutdown() noexcept +{ + // The ExecutionContext is already in a stopped + // state, so no synchronization is required. + timer_.cancel(); +} + +} // detail +} // beast +} // boost + +#endif diff --git a/include/boost/beast/_experimental/core/detail/impl/timeout_service.ipp b/include/boost/beast/_experimental/core/detail/impl/timeout_service.ipp deleted file mode 100644 index fd1427bd..00000000 --- a/include/boost/beast/_experimental/core/detail/impl/timeout_service.ipp +++ /dev/null @@ -1,181 +0,0 @@ -// -// Copyright (c) 2018 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_DETAIL_IMPL_TIMEOUT_SERVICE_IPP -#define BOOST_BEAST_CORE_DETAIL_IMPL_TIMEOUT_SERVICE_IPP - -namespace boost { -namespace beast { -namespace detail { - -//------------------------------------------------------------------------------ - -inline -timeout_object:: -timeout_object(boost::asio::io_context& ioc) - : svc_(boost::asio::use_service(ioc)) -{ -} - -//------------------------------------------------------------------------------ - -inline -timeout_service:: -timeout_service(boost::asio::io_context& ctx) - : service_base(ctx) - , strand_(ctx.get_executor()) - , timer_(ctx) -{ -} - -inline -void -timeout_service:: -on_work_started(timeout_object& obj) -{ - std::lock_guard lock(m_); - BOOST_VERIFY(++obj.outstanding_work_ == 1); - insert(obj, *fresh_); - if(++count_ == 1) - do_async_wait(); -} - -inline -void -timeout_service:: -on_work_complete(timeout_object& obj) -{ - std::lock_guard lock(m_); - remove(obj); -} - -inline -void -timeout_service:: -on_work_stopped(timeout_object& obj) -{ - std::lock_guard lock(m_); - BOOST_ASSERT(count_ > 0); - BOOST_VERIFY(--obj.outstanding_work_ == 0); - if(obj.list_ != nullptr) - remove(obj); - if(--count_ == 0) - timer_.cancel(); -} - -inline -void -timeout_service:: -set_option(std::chrono::seconds n) -{ - interval_ = n; -} - -//------------------------------------------------------------------------------ - -// Precondition: caller holds the mutex -inline -void -timeout_service:: -insert(timeout_object& obj, list_type& list) -{ - BOOST_ASSERT(obj.list_ == nullptr); - list.push_back(&obj); // can throw - obj.list_ = &list; - obj.pos_ = list.size(); -} - -// Precondition: caller holds the mutex -inline -void -timeout_service:: -remove(timeout_object& obj) -{ - BOOST_ASSERT(obj.list_ != nullptr); - BOOST_ASSERT( - obj.list_ == stale_ || - obj.list_ == fresh_); - BOOST_ASSERT(obj.list_->size() > 0); - auto& list = *obj.list_; - auto const n = list.size() - 1; - if(obj.pos_ != n) - { - auto other = list[n]; - list[obj.pos_] = other; - other->pos_ = obj.pos_; - } - obj.list_ = nullptr; - list.resize(n); -} - -inline -void -timeout_service:: -do_async_wait() -{ - timer_.expires_after(interval_); - timer_.async_wait( - boost::asio::bind_executor( - strand_, - [this](error_code ec) - { - this->on_timer(ec); - })); -} - -inline -void -timeout_service:: -on_timer(error_code ec) -{ - if(ec == boost::asio::error::operation_aborted) - { - BOOST_ASSERT(fresh_->empty()); - BOOST_ASSERT(stale_->empty()); - return; - } - - { - std::lock_guard lock(m_); - if(! stale_->empty()) - { - for(auto obj : *stale_) - { - obj->list_ = nullptr; - obj->on_timeout(); - } - stale_->clear(); - } - std::swap(fresh_, stale_); - } - - do_async_wait(); -} - -//------------------------------------------------------------------------------ - -inline -void -timeout_service:: -shutdown() noexcept -{ - boost::asio::post( - boost::asio::bind_executor( - strand_, - [this]() - { - timer_.cancel(); - })); -} - -} // detail -} // beast -} // boost - -#endif diff --git a/include/boost/beast/_experimental/core/detail/saved_handler.hpp b/include/boost/beast/_experimental/core/detail/saved_handler.hpp new file mode 100644 index 00000000..93cbff32 --- /dev/null +++ b/include/boost/beast/_experimental/core/detail/saved_handler.hpp @@ -0,0 +1,109 @@ +// +// Copyright (c) 2018 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_DETAIL_SAVED_HANDLER_HPP +#define BOOST_BEAST_CORE_DETAIL_SAVED_HANDLER_HPP + +#include +#include + +namespace boost { +namespace beast { +namespace detail { + +class saved_handler +{ + struct base + { + virtual ~base() = default; + virtual void operator()() = 0; + }; + + template + struct impl : base + { + Handler h_; + + template + explicit + impl(Deduced&& h) + : h_(std::forward(h)) + { + } + + void operator()() override + { + h_(); + } + }; + + std::unique_ptr p_; + +public: + saved_handler() = default; + + template + void + emplace(Handler&& h) + { + p_.reset(new impl::type>( + std::forward(h))); + } + + template + void + emplace(Handler&& h, + T0&& t0, TN&... tn) + { + using type = decltype( + beast::bind_front_handler( + std::forward(h), + std::forward(t0), + std::forward(tn)...)); + p_.reset(new impl( + beast::bind_front_handler( + std::forward(h), + std::forward(t0), + std::forward(tn)...))); + } + + bool + empty() const noexcept + { + return p_.get() == nullptr; + } + + explicit + operator bool() const noexcept + { + return ! empty(); + } + + void + operator()() + { + auto p = std::move(p_); + (*p)(); + } + + void + reset() + { + p_.reset(nullptr); + } +}; + + +} // detail +} // beast +} // boost + +#endif diff --git a/include/boost/beast/_experimental/core/detail/timeout_service.hpp b/include/boost/beast/_experimental/core/detail/timeout_service.hpp index b3d8d076..fbe72ad3 100644 --- a/include/boost/beast/_experimental/core/detail/timeout_service.hpp +++ b/include/boost/beast/_experimental/core/detail/timeout_service.hpp @@ -10,115 +10,124 @@ #ifndef BOOST_BEAST_CORE_DETAIL_TIMEOUT_SERVICE_HPP #define BOOST_BEAST_CORE_DETAIL_TIMEOUT_SERVICE_HPP -#include #include -#include -#include -#include +#include +#include +#include #include -#include +#include // #include #include #include -#include #include #include +#include #include #include #include namespace boost { namespace beast { + +class timeout_handle; + namespace detail { -//------------------------------------------------------------------------------ - -class timeout_service; - -class timeout_object -{ - friend class timeout_service; - - using list_type = std::vector; - - timeout_service& svc_; - std::size_t pos_; - list_type* list_ = nullptr; - char outstanding_work_ = 0; - -public: - timeout_object() = delete; - timeout_object(timeout_object&&) = delete; - timeout_object(timeout_object const&) = delete; - timeout_object& operator=(timeout_object&&) = delete; - timeout_object& operator=(timeout_object const&) = delete; - - // VFALCO should be execution_context - explicit - timeout_object(boost::asio::io_context& ioc); - - timeout_service& - service() const - { - return svc_; - } - - virtual void on_timeout() = 0; -}; - -//------------------------------------------------------------------------------ - class timeout_service : public service_base { + template + struct callback + { + timeout_handle th; + Executor ex; + Handler h; + + void + operator()() + { + boost::asio::post(ex, + beast::bind_front_handler( + std::move(*this), 0)); + } + + void + operator()(int) + { + th.service().on_cancel(th); + h(); + } + }; + public: using key_type = timeout_service; // VFALCO Should be execution_context + BOOST_BEAST_DECL explicit timeout_service(boost::asio::io_context& ctx); - void - on_work_started(timeout_object& obj); + BOOST_BEAST_DECL + timeout_handle + make_handle(); - void - on_work_complete(timeout_object& obj); + BOOST_BEAST_DECL + void set_option(std::chrono::seconds n); - void - on_work_stopped(timeout_object& obj); + // Undefined if work is active + template + void set_callback( + timeout_handle h, + Executor const& ex, + CancelHandler&& handler); - void - set_option(std::chrono::seconds n); + BOOST_BEAST_DECL + void on_work_started(timeout_handle h); + + BOOST_BEAST_DECL + void on_work_stopped(timeout_handle h); + + BOOST_BEAST_DECL + bool on_try_work_complete(timeout_handle h); private: - friend class timeout_object; + friend class beast::timeout_handle; - using list_type = std::vector; + BOOST_BEAST_DECL + void destroy(timeout_handle h); - void insert(timeout_object& obj, list_type& list); - void remove(timeout_object& obj); + BOOST_BEAST_DECL + void insert(thunk& t, thunk::list_type& list); + + BOOST_BEAST_DECL + void remove(thunk& t); + + BOOST_BEAST_DECL void do_async_wait(); + + BOOST_BEAST_DECL + void on_cancel(timeout_handle h); + + BOOST_BEAST_DECL void on_timer(error_code ec); + BOOST_BEAST_DECL virtual void shutdown() noexcept override; - boost::asio::strand< - boost::asio::io_context::executor_type> strand_; - std::mutex m_; - list_type list_[2]; - list_type* fresh_ = &list_[0]; - list_type* stale_ = &list_[1]; - std::size_t count_ = 0; + thunk::list_type list_[2]; + thunk::list_type* fresh_ = &list_[0]; + thunk::list_type* stale_ = &list_[1]; + std::deque thunks_; + std::size_t free_thunk_ = 0; boost::asio::steady_timer timer_; std::chrono::seconds interval_{30ul}; + long pending_ = 0; }; -//------------------------------------------------------------------------------ - } // detail } // beast } // boost -#include +#include #endif diff --git a/include/boost/beast/_experimental/core/detail/timeout_service_base.hpp b/include/boost/beast/_experimental/core/detail/timeout_service_base.hpp new file mode 100644 index 00000000..ded1cf2d --- /dev/null +++ b/include/boost/beast/_experimental/core/detail/timeout_service_base.hpp @@ -0,0 +1,38 @@ +// +// Copyright (c) 2018 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_DETAIL_TIMEOUT_SERVICE_BASE_HPP +#define BOOST_BEAST_CORE_DETAIL_TIMEOUT_SERVICE_BASE_HPP + +#include + +#include + +namespace boost { +namespace beast { +namespace detail { + +struct thunk +{ + using list_type = + std::vector; + + saved_handler callback; + list_type* list = nullptr; + std::size_t pos = 0; // also: next in free list + bool expired = false; + bool canceled = false; + bool completed = false; +}; + +} // detail +} // beast +} // boost + +#endif diff --git a/include/boost/beast/_experimental/core/impl/timeout_service.hpp b/include/boost/beast/_experimental/core/impl/timeout_service.hpp new file mode 100644 index 00000000..79338b49 --- /dev/null +++ b/include/boost/beast/_experimental/core/impl/timeout_service.hpp @@ -0,0 +1,64 @@ +// +// Copyright (c) 2018 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_TIMEOUT_SERVICE_HPP +#define BOOST_BEAST_CORE_IMPL_TIMEOUT_SERVICE_HPP + +#include +#include +#include + +namespace boost { +namespace beast { + +timeout_handle:: +timeout_handle(boost::asio::io_context& ioc) + : timeout_handle( + boost::asio::use_service< + detail::timeout_service>( + ioc).make_handle()) +{ +} + +void +timeout_handle:: +destroy() +{ + BOOST_ASSERT(svc_ != nullptr); + svc_->destroy(*this); + id_ = 0; + svc_ = nullptr; +} + +template +void +timeout_handle:: +set_callback( + Executor const& ex, CancelHandler&& handler) +{ + svc_->set_callback(*this, ex, + std::forward(handler)); +} + +//------------------------------------------------------------------------------ + +void +set_timeout_service_options( + boost::asio::io_context& ioc, + std::chrono::seconds interval) +{ + boost::asio::use_service< + detail::timeout_service>( + ioc).set_option(interval); +} + +} // beast +} // boost + +#endif diff --git a/include/boost/beast/_experimental/core/impl/timeout_service.ipp b/include/boost/beast/_experimental/core/impl/timeout_service.ipp deleted file mode 100644 index 6f5e6635..00000000 --- a/include/boost/beast/_experimental/core/impl/timeout_service.ipp +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) 2018 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_TIMEOUT_SERVICE_HPP -#define BOOST_BEAST_CORE_IMPL_TIMEOUT_SERVICE_HPP - -#include - -namespace boost { -namespace beast { - -inline -void -set_timeout_service_options( - boost::asio::io_context& ioc, - std::chrono::seconds interval) -{ - boost::asio::use_service< - detail::timeout_service>(ioc).set_option(interval); -} - -} // beast -} // boost - -#endif diff --git a/include/boost/beast/_experimental/core/impl/timeout_socket.hpp b/include/boost/beast/_experimental/core/impl/timeout_socket.hpp index 05b716a1..1c05b133 100644 --- a/include/boost/beast/_experimental/core/impl/timeout_socket.hpp +++ b/include/boost/beast/_experimental/core/impl/timeout_socket.hpp @@ -7,11 +7,11 @@ // Official repository: https://github.com/boostorg/beast // -#ifndef BOOST_BEAST_CORE_IMPL_TIMOUT_SOCKET_HPP -#define BOOST_BEAST_CORE_IMPL_TIMOUT_SOCKET_HPP +#ifndef BOOST_BEAST_CORE_IMPL_TIMEOUT_SOCKET_HPP +#define BOOST_BEAST_CORE_IMPL_TIMEOUT_SOCKET_HPP #include -#include +#include #include #include #include @@ -23,10 +23,6 @@ template template class basic_timeout_socket::async_op { - Handler h_; - basic_timeout_socket& s_; - detail::timeout_work_guard work_; - public: async_op(async_op&&) = default; async_op(async_op const&) = delete; @@ -40,6 +36,10 @@ public: : h_(std::forward(h)) , s_(s) , work_(s.rd_timer_) + , wg0_(s_.get_executor()) + , wg1_(get_executor()) + , saved_(s_.rd_op_) + { s_.sock_.async_read_some(b, std::move(*this)); } @@ -53,6 +53,9 @@ public: : h_(std::forward(h)) , s_(s) , work_(s.wr_timer_) + , wg0_(s_.get_executor()) + , wg1_(get_executor()) + , saved_(s_.wr_op_) { s_.sock_.async_write_some(b, std::move(*this)); } @@ -63,17 +66,18 @@ public: allocator_type get_allocator() const noexcept { - return (boost::asio::get_associated_allocator)(h_); + return boost::asio::get_associated_allocator(h_); } using executor_type = - boost::asio::associated_executor_t&>().get_executor())>; + boost::asio::associated_executor_t&>().get_executor())>; executor_type get_executor() const noexcept { - return (boost::asio::get_associated_executor)( + return boost::asio::get_associated_executor( h_, s_.get_executor()); } @@ -96,66 +100,72 @@ public: void operator()(error_code ec, std::size_t bytes_transferred) { - if(s_.expired_) + if(! work_.try_complete()) { - BOOST_ASSERT(ec == boost::asio::error::operation_aborted); - ec = boost::asio::error::timed_out; - } - else - { - work_.complete(); + saved_.emplace( + std::move(*this), + ec, + bytes_transferred); + return; } h_(ec, bytes_transferred); } + +private: + Handler h_; + basic_timeout_socket& s_; + timeout_work_guard work_; + boost::asio::executor_work_guard wg0_; + boost::asio::executor_work_guard wg1_; + detail::saved_handler& saved_; }; //------------------------------------------------------------------------------ -template -basic_timeout_socket:: -timer:: -timer(basic_timeout_socket& s) - : detail::timeout_object(s.ex_.context()) - , s_(s) -{ -} - -template -auto -basic_timeout_socket:: -timer:: -operator=(timer&&) - -> timer& -{ - return *this; -} - -template -void -basic_timeout_socket:: -timer:: -on_timeout() -{ - boost::asio::post( - s_.ex_, - [this]() - { - s_.expired_ = true; - s_.sock_.cancel(); - }); -} - -//------------------------------------------------------------------------------ - template template basic_timeout_socket:: basic_timeout_socket(ExecutionContext& ctx) : ex_(ctx.get_executor()) - , rd_timer_(*this) - , wr_timer_(*this) + , rd_timer_(ctx) + , wr_timer_(ctx) + , cn_timer_(ctx) , sock_(ctx) { + rd_timer_.set_callback(ex_, + [this] + { + if(rd_op_.empty()) + sock_.cancel(); + else + rd_op_(); + }); + + wr_timer_.set_callback(ex_, + [this] + { + if(wr_op_.empty()) + sock_.cancel(); + else + wr_op_(); + }); + + cn_timer_.set_callback(ex_, + [this] + { + if(cn_op_.empty()) + sock_.cancel(); + else + cn_op_(); + }); +} + +template +basic_timeout_socket:: +~basic_timeout_socket() +{ + rd_timer_.destroy(); + wr_timer_.destroy(); } template @@ -200,6 +210,225 @@ async_write_some( return init.result.get(); } +//------------------------------------------------------------------------------ + +namespace detail { + +template< + class Protocol, class Executor, + class Handler> +class connect_op +{ +public: + template< + class Endpoints, + class Condition, + class DeducedHandler> + connect_op( + basic_timeout_socket& s, + Endpoints const& eps, + Condition cond, + DeducedHandler&& h) + : h_(std::forward(h)) + , work_(s.cnd_timer_) + , s_(s) + , wg0_(s_.get_executor()) + , wg1_(get_executor()) + { + boost::asio::async_connect( + s_.next_layer(), eps, cond, + std::move(*this)); + } + + using allocator_type = + boost::asio::associated_allocator_t; + + allocator_type + get_allocator() const noexcept + { + return boost::asio::get_associated_allocator(h_); + } + + using executor_type = + boost::asio::associated_executor_t&>().get_executor())>; + + executor_type + get_executor() const noexcept + { + return boost::asio::get_associated_executor( + h_, s_.get_executor()); + } + + friend + bool asio_handler_is_continuation(connect_op* op) + { + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); + } + + template + friend + void asio_handler_invoke(Function&& f, connect_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke(f, std::addressof(op->h_)); + } + + template + void + operator()(error_code ec, Arg&& arg) + { + if(! work_.try_complete()) + { + s_.cn_op_.emplace( + std::move(*this), + ec, + std::forward(arg)); + return; + } + h_(ec, std::forward(arg)); + } + +private: + Handler h_; + timeout_work_guard work_; + basic_timeout_socket& s_; + boost::asio::executor_work_guard wg0_; + boost::asio::executor_work_guard wg1_; +}; + +struct any_endpoint +{ + template + bool + operator()(Error const&, Endpoint const&) const noexcept + { + return true; + } +}; + +template +struct endpoint_range_type +{ + using const_iterator = Iterator; + + Iterator begin_; + Iterator end_; + + Iterator begin() const noexcept + { + return begin_; + } + + Iterator end() const noexcept + { + return end_; + } +}; + +template +endpoint_range_type +endpoint_range(Iterator first, Iterator last) +{ + return endpoint_range_type< + Iterator>(first, last); +} + +} // detail + +template< + class Protocol, class Executor, + class EndpointSequence, + class RangeConnectHandler, class> +BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, + void(boost::system::error_code, typename Protocol::endpoint)) +async_connect( + basic_timeout_socket& s, + EndpointSequence const& endpoints, + RangeConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, + void(boost::system::error_code, typename Protocol::endpoint)); + detail::connect_op( + s, endpoints, detail::any_endpoint{}, + std::forward(handler)); + return init.result.get(); +} + +template< + class Protocol, class Executor, + class EndpointSequence, + class ConnectCondition, + class RangeConnectHandler, class> +BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, + void (boost::system::error_code, typename Protocol::endpoint)) +async_connect( + basic_timeout_socket& s, + EndpointSequence const& endpoints, + ConnectCondition connect_condition, + RangeConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, + void(boost::system::error_code, typename Protocol::endpoint)); + detail::connect_op( + s, endpoints, connect_condition, + std::forward(handler)); + return init.result.get(); +} + +template< + class Protocol, class Executor, + class Iterator, + class IteratorConnectHandler, class> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (boost::system::error_code, Iterator)) +async_connect( + basic_timeout_socket& s, + Iterator begin, Iterator end, + IteratorConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, + void(boost::system::error_code, Iterator)); + detail::connect_op( + s, detail::endpoint_range(begin, end), detail::any_endpoint{}, + std::forward(handler)); + return init.result.get(); +} + +template< + class Protocol, class Executor, + class Iterator, + class ConnectCondition, + class IteratorConnectHandler, class> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (boost::system::error_code, Iterator)) +async_connect( + basic_timeout_socket& s, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + IteratorConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, + void(boost::system::error_code, Iterator)); + detail::connect_op( + s, detail::endpoint_range(begin, end), connect_condition, + std::forward(handler)); + return init.result.get(); +} + } // beast } // boost diff --git a/include/boost/beast/_experimental/core/timeout_service.hpp b/include/boost/beast/_experimental/core/timeout_service.hpp index 7f411cfa..f44daf96 100644 --- a/include/boost/beast/_experimental/core/timeout_service.hpp +++ b/include/boost/beast/_experimental/core/timeout_service.hpp @@ -10,13 +10,109 @@ #ifndef BOOST_BEAST_CORE_TIMEOUT_SERVICE_HPP #define BOOST_BEAST_CORE_TIMEOUT_SERVICE_HPP -//#include -#include -#include +#include +#include // #include +#include + +#include // temporary namespace boost { namespace beast { +namespace detail { +class timeout_service; +} // detail + +class timeout_work_guard; + +class timeout_handle +{ + std::size_t id_ = 0; + detail::timeout_service* svc_ = nullptr; + + timeout_handle( + std::size_t id, + detail::timeout_service& svc) + : id_(id) + , svc_(&svc) + { + } + + detail::timeout_service& + service() const + { + return *svc_; + } + + friend class detail::timeout_service; + friend class timeout_work_guard; + +public: + timeout_handle() = default; + timeout_handle(timeout_handle const&) = default; + timeout_handle& operator=(timeout_handle const&) = default; + + timeout_handle(std::nullptr_t) + { + } + + timeout_handle& + operator=(std::nullptr_t) + { + id_ = 0; + svc_ = nullptr; + return *this; + } + + // VFALCO should be execution_context + BOOST_BEAST_DECL + explicit + timeout_handle(boost::asio::io_context& ioc); + + BOOST_BEAST_DECL + void + destroy(); + + template + void + set_callback( + Executor const& ex, CancelHandler&& handler); + + explicit + operator bool() const noexcept + { + return svc_ != nullptr; + } + + friend bool operator==( + timeout_handle const& lhs, + std::nullptr_t) noexcept + { + return lhs.svc_ == nullptr; + } + + friend bool operator==( + std::nullptr_t, + timeout_handle const& rhs) noexcept + { + return rhs.svc_ == nullptr; + } + + friend bool operator!=( + timeout_handle const& lhs, + std::nullptr_t) noexcept + { + return lhs.svc_ != nullptr; + } + + friend bool operator!=( + std::nullptr_t, + timeout_handle const& rhs) noexcept + { + return rhs.svc_ != nullptr; + } +}; + /** Set timeout service options in an execution context. This changes the time interval for all timeouts associated @@ -27,6 +123,7 @@ namespace beast { @param interval The approximate amount of time until a timeout occurs. */ +BOOST_BEAST_DECL void set_timeout_service_options( boost::asio::io_context& ctx, // VFALCO should be execution_context @@ -35,6 +132,6 @@ set_timeout_service_options( } // beast } // boost -#include +#include #endif diff --git a/include/boost/beast/_experimental/core/timeout_socket.hpp b/include/boost/beast/_experimental/core/timeout_socket.hpp index 3681f3bb..5fd69dc7 100644 --- a/include/boost/beast/_experimental/core/timeout_socket.hpp +++ b/include/boost/beast/_experimental/core/timeout_socket.hpp @@ -13,9 +13,11 @@ #include #include #include -#include +#include +#include #include #include +#include #include #include @@ -30,6 +32,11 @@ class tcp; namespace boost { namespace beast { +namespace detail { +template +class connect_op; +} // detail + /** A socket wrapper which automatically times out on asynchronous reads. This wraps a normal stream socket and implements a simple and efficient @@ -44,22 +51,16 @@ template< class basic_timeout_socket { template class async_op; - - class timer : public detail::timeout_object - { - basic_timeout_socket& s_; - - public: - explicit timer(basic_timeout_socket& s); - timer& operator=(timer&& other); - void on_timeout() override; - }; + template friend class detail::connect_op; Executor ex_; // must come first - timer rd_timer_; - timer wr_timer_; + timeout_handle rd_timer_; + timeout_handle wr_timer_; + timeout_handle cn_timer_; boost::asio::basic_stream_socket sock_; - bool expired_ = false; + detail::saved_handler rd_op_; + detail::saved_handler wr_op_; + detail::saved_handler cn_op_; public: /// The type of the next layer. @@ -74,6 +75,13 @@ public: /// The type of the executor associated with the object. using executor_type = Executor; + /** Destructor + + The behavior of destruction while asynchronous operations + are pending is undefined. + */ + ~basic_timeout_socket(); + // VFALCO we only support default-construction // of the contained socket for now. // This constructor needs a protocol parameter. @@ -227,11 +235,394 @@ public: WriteHandler&& handler); }; +//------------------------------------------------------------------------------ + /// A TCP/IP socket wrapper which has a built-in asynchronous timeout using timeout_socket = basic_timeout_socket< boost::asio::ip::tcp, boost::asio::io_context::executor_type>; +/** + @defgroup async_connect boost::beast::async_connect + + @brief Asynchronously establishes a socket connection by trying each + endpoint in a sequence, and terminating if a timeout occurs. +*/ +/* @{ */ +/** Asynchronously establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the underlying socket's + @c async_connect member function, once for each endpoint in the sequence, + until a connection is successfully established or a timeout occurs. + + @param s The @ref basic_timeout_socket to be connected. If the socket + is already open, it will be closed. + + @param endpoints A sequence of endpoints. + + @param handler The handler to be called when the connect operation + completes. Ownership of the handler may be transferred. The function + signature of the handler must be: + @code + void handler( + // Result of operation. if the sequence is empty, set to + // boost::asio::error::not_found. Otherwise, contains the + // error from the last connection attempt. + error_code const& error, + + // On success, the successfully connected endpoint. + // Otherwise, a default-constructed endpoint. + typename Protocol::endpoint const& endpoint + ); + @endcode + + Regardless of whether the asynchronous operation completes 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 + `boost::asio::io_context::post()`. + + @par Example + + @code + boost::asio::tcp::resolver r(ioc); + boost::asio::tcp::resolver::query q("host", "service"); + timeout_socket s(ioc.get_executor()); + + // ... + + r.async_resolve(q, resolve_handler); + + // ... + + void resolve_handler( + boost::system::error_code const& ec, + tcp::resolver::results_type results) + { + if (!ec) + { + async_connect(s, results, connect_handler); + } + } + + // ... + + void connect_handler( + boost::system::error_code const& ec, + tcp::endpoint const& endpoint) + { + // ... + } + @endcode +*/ +template< + class Protocol, class Executor, + class EndpointSequence, + class RangeConnectHandler +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + boost::asio::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, + void (boost::system::error_code, class Protocol::endpoint)) +async_connect( + basic_timeout_socket& s, + EndpointSequence const& endpoints, + RangeConnectHandler&& handler); + +/** Asynchronously establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the underlying socket's + @c async_connect member function, once for each endpoint in the sequence, + until a connection is successfully established or a timeout occurs. + + @param s The @ref basic_timeout_socket to be connected. If the socket + is already open, it will be closed. + + @param endpoints A sequence of endpoints. + + @param connect_condition A function object that is called prior to each + connection attempt. The signature of the function object must be: + + @code + bool connect_condition( + boost::system::error_code const& ec, + typename Protocol::endpoint const& next); + @endcode + + The @c ec parameter contains the result from the most recent connect + operation. Before the first connection attempt, @c ec is always set to + indicate success. The @c next parameter is the next endpoint to be tried. + The function object should return true if the next endpoint should be tried, + and false if it should be skipped. + + @param handler The handler to be called when the connect operation + completes. Ownership of the handler may be transferred. The function + signature of the handler must be: + @code + void handler( + // Result of operation. if the sequence is empty, set to + // boost::asio::error::not_found. Otherwise, contains the + // error from the last connection attempt. + error_code const& error, + + // On success, the successfully connected endpoint. + // Otherwise, a default-constructed endpoint. + typename Protocol::endpoint const& endpoint + ); + @endcode + + Regardless of whether the asynchronous operation completes 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 + `boost::asio::io_context::post()`. + + @par Example + + The following connect condition function object can be used to output + information about the individual connection attempts: + + @code + struct my_connect_condition + { + bool operator()( + boost::system::error_code const& ec, + boost::asio::ip::tcp::endpoint const& next) + { + if (ec) std::cout << "Error: " << ec.message() << std::endl; + std::cout << "Trying: " << next << std::endl; + return true; + } + }; + @endcode + + It would be used with the @ref boost::beast::async_connect + function as follows: + + @code + boost::asio::tcp::resolver r(ioc); + boost::asio::tcp::resolver::query q("host", "service"); + timeout_socket s(ioc.get_executor()); + + // ... + + r.async_resolve(q, resolve_handler); + + // ... + + void resolve_handler( + boost::system::error_code const& ec, + tcp::resolver::results_type results) + { + if (!ec) + { + async_connect(s, results, my_connect_condition{}, connect_handler); + } + } + + // ... + + void connect_handler( + boost::system::error_code const& ec, + tcp::endpoint const& endpoint) + { + // ... + } + @endcode +*/ +template< + class Protocol, class Executor, + class EndpointSequence, + class ConnectCondition, + class RangeConnectHandler +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + boost::asio::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, + void (boost::system::error_code, class Protocol::endpoint)) +async_connect( + basic_timeout_socket& s, + EndpointSequence const& endpoints, + ConnectCondition connect_condition, + RangeConnectHandler&& handler); + +/** Asynchronously establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the underlying socket's + @c async_connect member function, once for each endpoint in the sequence, + until a connection is successfully established or a timeout occurs. + + @param s The @ref timeout_socket to be connected. If the socket + is already open, it will be closed. + + @param begin An iterator pointing to the start of a sequence of endpoints. + + @param end An iterator pointing to the end of a sequence of endpoints. + + @param handler The handler to be called when the connect operation + completes. Ownership of the handler may be transferred. The function + signature of the handler must be: + @code + void handler( + // Result of operation. if the sequence is empty, set to + // boost::asio::error::not_found. Otherwise, contains the + // error from the last connection attempt. + error_code const& error, + + // On success, an iterator denoting the successfully + // connected endpoint. Otherwise, the end iterator. + Iterator iterator + ); + @endcode + + Regardless of whether the asynchronous operation completes 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 + `boost::asio::io_context::post()`. + + @par Example + + @code + std::vector endpoints = ...; + timeout_socket s(ioc.get_executor()); + + async_connect(s, + endpoints.begin(), endpoints.end(), + connect_handler); + + void connect_handler( + boost::system::error_code const& ec, + std::vector::iterator) + { + // ... + } + @endcode +*/ +template< + class Protocol, class Executor, + class Iterator, + class IteratorConnectHandler +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + ! boost::asio::is_endpoint_sequence< + Iterator>::value>::type +#endif +> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (boost::system::error_code, Iterator)) +async_connect( + basic_timeout_socket& s, + Iterator begin, Iterator end, + IteratorConnectHandler&& handler); + +/** Asynchronously establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the underlying socket's + @c async_connect member function, once for each endpoint in the sequence, + until a connection is successfully established or a timeout occurs. + + @param s The @ref basic_timeout_socket to be connected. If the socket + is already open, it will be closed. + + @param begin An iterator pointing to the start of a sequence of endpoints. + + @param end An iterator pointing to the end of a sequence of endpoints. + + @param connect_condition A function object that is called prior to each + connection attempt. The signature of the function object must be: + + @code + bool connect_condition( + boost::system::error_code const& ec, + Iterator next); + @endcode + + @param handler The handler to be called when the connect operation + completes. Ownership of the handler may be transferred. The function + signature of the handler must be: + @code + void handler( + // Result of operation. if the sequence is empty, set to + // boost::asio::error::not_found. Otherwise, contains the + // error from the last connection attempt. + error_code const& error, + + // On success, an iterator denoting the successfully + // connected endpoint. Otherwise, the end iterator. + Iterator iterator + ); + @endcode + + Regardless of whether the asynchronous operation completes 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 + `boost::asio::io_context::post()`. + + @par Example + + The following connect condition function object can be used to output + information about the individual connection attempts: + + @code + struct my_connect_condition + { + bool operator()( + boost::system::error_code const& ec, + boost::asio::ip::tcp::endpoint const& next) + { + if (ec) std::cout << "Error: " << ec.message() << std::endl; + std::cout << "Trying: " << next << std::endl; + return true; + } + }; + @endcode + + It would be used with the @ref boost::beast::async_connect + function as follows: + + @code + std::vector endpoints = ...; + timeout_socket s(ioc.get_executor()); + + async_connect(s, endpoints.begin(), endpoints.end(), + my_connect_condition{}, connect_handler); + + void connect_handler( + boost::system::error_code const& ec, + std::vector::iterator) + { + // ... + } + @endcode +*/ +template< + class Protocol, class Executor, + class Iterator, + class ConnectCondition, + class IteratorConnectHandler +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + ! boost::asio::is_endpoint_sequence< + Iterator>::value>::type +#endif +> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (boost::system::error_code, Iterator)) +async_connect( + basic_timeout_socket& s, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + IteratorConnectHandler&& handler); +/* @} */ + } // beast } // boost diff --git a/include/boost/beast/_experimental/core/detail/timeout_work_guard.hpp b/include/boost/beast/_experimental/core/timeout_work_guard.hpp similarity index 51% rename from include/boost/beast/_experimental/core/detail/timeout_work_guard.hpp rename to include/boost/beast/_experimental/core/timeout_work_guard.hpp index f353478a..13ce472c 100644 --- a/include/boost/beast/_experimental/core/detail/timeout_work_guard.hpp +++ b/include/boost/beast/_experimental/core/timeout_work_guard.hpp @@ -7,24 +7,21 @@ // Official repository: https://github.com/boostorg/beast // -#ifndef BOOST_BEAST_CORE_DETAIL_TIMEOUT_WORK_GUARD_HPP -#define BOOST_BEAST_CORE_DETAIL_TIMEOUT_WORK_GUARD_HPP +#ifndef BOOST_BEAST_CORE_TIMEOUT_WORK_GUARD_HPP +#define BOOST_BEAST_CORE_TIMEOUT_WORK_GUARD_HPP -#include +#include #include -#include namespace boost { namespace beast { -namespace detail { class timeout_work_guard { - timeout_object* obj_; + timeout_handle h_; public: timeout_work_guard(timeout_work_guard const&) = delete; - timeout_work_guard& operator=(timeout_work_guard&&) = delete; timeout_work_guard& operator=(timeout_work_guard const&) = delete; ~timeout_work_guard() @@ -33,40 +30,42 @@ public: } timeout_work_guard(timeout_work_guard&& other) - : obj_(boost::exchange(other.obj_, nullptr)) + : h_(other.h_) { + other.h_ = nullptr; } explicit - timeout_work_guard(timeout_object& obj) - : obj_(&obj) + timeout_work_guard(timeout_handle h) + : h_(h) { - obj_->service().on_work_started(*obj_); + h_.service().on_work_started(h_); } bool owns_work() const { - return obj_ != nullptr; + return h_ != nullptr; } void reset() { - if(obj_) - obj_->service().on_work_stopped(*obj_); + if(h_) + h_.service().on_work_stopped(h_); } - void - complete() + bool + try_complete() { - BOOST_ASSERT(obj_ != nullptr); - obj_->service().on_work_complete(*obj_); - obj_ = nullptr; + BOOST_ASSERT(h_ != nullptr); + auto result = + h_.service().on_try_work_complete(h_); + h_ = nullptr; + return result; } }; -} // detail } // beast } // boost diff --git a/include/boost/beast/core/detail/config.hpp b/include/boost/beast/core/detail/config.hpp index 994db5a5..796b75ba 100644 --- a/include/boost/beast/core/detail/config.hpp +++ b/include/boost/beast/core/detail/config.hpp @@ -60,4 +60,6 @@ # endif #endif +#define BOOST_BEAST_DECL inline + #endif diff --git a/test/beast/experimental/timeout_socket.cpp b/test/beast/experimental/timeout_socket.cpp index c5241c91..30b8f8d9 100644 --- a/test/beast/experimental/timeout_socket.cpp +++ b/test/beast/experimental/timeout_socket.cpp @@ -137,7 +137,10 @@ public: std::make_shared( std::move(socket_), log_)->run(); acceptor_.async_accept(socket_, - [this](error_code ec){ this->on_accept(ec); }); + [this](error_code ec) + { + this->on_accept(ec); + }); } }; diff --git a/test/beast/experimental/timeout_work_guard.cpp b/test/beast/experimental/timeout_work_guard.cpp index 20364608..e4d0e21b 100644 --- a/test/beast/experimental/timeout_work_guard.cpp +++ b/test/beast/experimental/timeout_work_guard.cpp @@ -8,4 +8,4 @@ // // Test that header file is self-contained. -#include +#include