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