Fixes to timeout services (experimental)

This commit is contained in:
Vinnie Falco
2018-11-23 18:14:03 -08:00
parent 5cddf63e3b
commit 02fb4983f6
16 changed files with 1432 additions and 375 deletions

View File

@ -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)
--------------------------------------------------------------------------------

View File

@ -310,12 +310,15 @@
<member><link linkend="beast.ref.boost__beast__http__icy_stream">http::icy_stream</link></member>
<member><link linkend="beast.ref.boost__beast__test__fail_count">test::fail_count</link></member>
<member><link linkend="beast.ref.boost__beast__test__stream">test::stream</link></member>
<member><link linkend="beast.ref.boost__beast__timeout_handle">timeout_handle</link></member>
<member><link linkend="beast.ref.boost__beast__timeout_socket">timeout_socket</link></member>
<member><link linkend="beast.ref.boost__beast__timeout_work_guard">timeout_work_guard</link></member>
</simplelist>
</entry>
<entry valign="top">
<bridgehead renderas="sect3">Functions</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="beast.ref.boost__beast__async_connect">async_connect</link></member>
<member><link linkend="beast.ref.boost__beast__set_timeout_service_options">set_timeout_service_options</link></member>
<member><link linkend="beast.ref.boost__beast__test__connect">test::connect</link></member>
</simplelist>

View File

@ -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<std::mutex> 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<class Executor, class CancelHandler>
void
timeout_service::
set_callback(
timeout_handle h,
Executor const& ex,
CancelHandler&& handler)
{
thunks_[h.id_].callback.emplace(
callback<
Executor,
typename std::decay<CancelHandler>::type>{
h, ex,
std::forward<CancelHandler>(handler)});
}
void
timeout_service::
on_work_started(timeout_handle h)
{
BOOST_ASSERT(h.id_ != 0);
if( [this, h]
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<thunk*> expired;
{
std::lock_guard<std::mutex> 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<std::mutex> 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

View File

@ -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<timeout_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<std::mutex> 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<std::mutex> lock(m_);
remove(obj);
}
inline
void
timeout_service::
on_work_stopped(timeout_object& obj)
{
std::lock_guard<std::mutex> 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<std::mutex> 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

View File

@ -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 <boost/beast/core/bind_handler.hpp>
#include <memory>
namespace boost {
namespace beast {
namespace detail {
class saved_handler
{
struct base
{
virtual ~base() = default;
virtual void operator()() = 0;
};
template<class Handler>
struct impl : base
{
Handler h_;
template<class Deduced>
explicit
impl(Deduced&& h)
: h_(std::forward<Deduced>(h))
{
}
void operator()() override
{
h_();
}
};
std::unique_ptr<base> p_;
public:
saved_handler() = default;
template<class Handler>
void
emplace(Handler&& h)
{
p_.reset(new impl<typename
std::decay<Handler>::type>(
std::forward<Handler>(h)));
}
template<class Handler,
class T0, class... TN>
void
emplace(Handler&& h,
T0&& t0, TN&... tn)
{
using type = decltype(
beast::bind_front_handler(
std::forward<Handler>(h),
std::forward<T0>(t0),
std::forward<TN>(tn)...));
p_.reset(new impl<type>(
beast::bind_front_handler(
std::forward<Handler>(h),
std::forward<T0>(t0),
std::forward<TN>(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

View File

@ -10,115 +10,124 @@
#ifndef BOOST_BEAST_CORE_DETAIL_TIMEOUT_SERVICE_HPP
#define BOOST_BEAST_CORE_DETAIL_TIMEOUT_SERVICE_HPP
#include <boost/beast/core/error.hpp>
#include <boost/beast/_experimental/core/detail/service_base.hpp>
#include <boost/assert.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/beast/_experimental/core/detail/timeout_service_base.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/asio/executor.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/io_context.hpp> // #include <boost/asio/execution_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/strand.hpp>
#include <chrono>
#include <cstdlib>
#include <deque>
#include <mutex>
#include <utility>
#include <vector>
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_object*>;
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<timeout_service>
{
template<class Executor, class Handler>
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<class Executor, class CancelHandler>
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<timeout_object*>;
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<thunk> thunks_;
std::size_t free_thunk_ = 0;
boost::asio::steady_timer timer_;
std::chrono::seconds interval_{30ul};
long pending_ = 0;
};
//------------------------------------------------------------------------------
} // detail
} // beast
} // boost
#include <boost/beast/_experimental/core/detail/impl/timeout_service.ipp>
#include <boost/beast/_experimental/core/detail/impl/timeout_service.hpp>
#endif

View File

@ -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 <boost/beast/_experimental/core/detail/saved_handler.hpp>
#include <vector>
namespace boost {
namespace beast {
namespace detail {
struct thunk
{
using list_type =
std::vector<thunk*>;
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

View File

@ -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 <boost/beast/_experimental/core/detail/timeout_service.hpp>
#include <boost/asio/execution_context.hpp>
#include <boost/assert.hpp>
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<class Executor, class CancelHandler>
void
timeout_handle::
set_callback(
Executor const& ex, CancelHandler&& handler)
{
svc_->set_callback(*this, ex,
std::forward<CancelHandler>(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

View File

@ -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 <boost/beast/_experimental/core/detail/timeout_service.hpp>
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

View File

@ -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 <boost/beast/core/type_traits.hpp>
#include <boost/beast/_experimental/core/detail/timeout_work_guard.hpp>
#include <boost/beast/_experimental/core/timeout_work_guard.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <memory>
#include <utility>
@ -23,10 +23,6 @@ template<class Protocol, class Executor>
template<class Handler>
class basic_timeout_socket<Protocol, Executor>::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<DeducedHandler>(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<DeducedHandler>(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<Handler, decltype(
std::declval<basic_timeout_socket<Protocol>&>().get_executor())>;
boost::asio::associated_executor_t<Handler,
decltype(std::declval<basic_timeout_socket<
Protocol, Executor>&>().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<Executor> wg0_;
boost::asio::executor_work_guard<executor_type> wg1_;
detail::saved_handler& saved_;
};
//------------------------------------------------------------------------------
template<class Protocol, class Executor>
basic_timeout_socket<Protocol, Executor>::
timer::
timer(basic_timeout_socket& s)
: detail::timeout_object(s.ex_.context())
, s_(s)
{
}
template<class Protocol, class Executor>
auto
basic_timeout_socket<Protocol, Executor>::
timer::
operator=(timer&&)
-> timer&
{
return *this;
}
template<class Protocol, class Executor>
void
basic_timeout_socket<Protocol, Executor>::
timer::
on_timeout()
{
boost::asio::post(
s_.ex_,
[this]()
{
s_.expired_ = true;
s_.sock_.cancel();
});
}
//------------------------------------------------------------------------------
template<class Protocol, class Executor>
template<class ExecutionContext, class>
basic_timeout_socket<Protocol, Executor>::
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<class Protocol, class Executor>
basic_timeout_socket<Protocol, Executor>::
~basic_timeout_socket()
{
rd_timer_.destroy();
wr_timer_.destroy();
}
template<class Protocol, class Executor>
@ -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<Protocol, Executor>& s,
Endpoints const& eps,
Condition cond,
DeducedHandler&& h)
: h_(std::forward<DeducedHandler>(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<Handler>;
allocator_type
get_allocator() const noexcept
{
return boost::asio::get_associated_allocator(h_);
}
using executor_type =
boost::asio::associated_executor_t<Handler,
decltype(std::declval<basic_timeout_socket<
Protocol, Executor>&>().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<class Function>
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<class Arg>
void
operator()(error_code ec, Arg&& arg)
{
if(! work_.try_complete())
{
s_.cn_op_.emplace(
std::move(*this),
ec,
std::forward<Arg>(arg));
return;
}
h_(ec, std::forward<Arg>(arg));
}
private:
Handler h_;
timeout_work_guard work_;
basic_timeout_socket<Protocol, Executor>& s_;
boost::asio::executor_work_guard<Executor> wg0_;
boost::asio::executor_work_guard<executor_type> wg1_;
};
struct any_endpoint
{
template<class Error, class Endpoint>
bool
operator()(Error const&, Endpoint const&) const noexcept
{
return true;
}
};
template<class Iterator>
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<class Iterator>
endpoint_range_type<Iterator>
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<Protocol, Executor>& s,
EndpointSequence const& endpoints,
RangeConnectHandler&& handler)
{
BOOST_BEAST_HANDLER_INIT(RangeConnectHandler,
void(boost::system::error_code, typename Protocol::endpoint));
detail::connect_op<Protocol, Executor,
BOOST_ASIO_HANDLER_TYPE(RangeConnectHandler,
void(boost::system::error_code,
typename Protocol::endpoint))>(
s, endpoints, detail::any_endpoint{},
std::forward<RangeConnectHandler>(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<Protocol, Executor>& 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<Protocol, Executor,
BOOST_ASIO_HANDLER_TYPE(RangeConnectHandler,
void(boost::system::error_code,
typename Protocol::endpoint))>(
s, endpoints, connect_condition,
std::forward<RangeConnectHandler>(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<Protocol, Executor>& s,
Iterator begin, Iterator end,
IteratorConnectHandler&& handler)
{
BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler,
void(boost::system::error_code, Iterator));
detail::connect_op<Protocol, Executor,
BOOST_ASIO_HANDLER_TYPE(IteratorConnectHandler,
void(boost::system::error_code, Iterator))>(
s, detail::endpoint_range(begin, end), detail::any_endpoint{},
std::forward<IteratorConnectHandler>(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<Protocol, Executor>& s,
Iterator begin, Iterator end,
ConnectCondition connect_condition,
IteratorConnectHandler&& handler)
{
BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler,
void(boost::system::error_code, Iterator));
detail::connect_op<Protocol, Executor,
BOOST_ASIO_HANDLER_TYPE(IteratorConnectHandler,
void(boost::system::error_code, Iterator))>(
s, detail::endpoint_range(begin, end), connect_condition,
std::forward<IteratorConnectHandler>(handler));
return init.result.get();
}
} // beast
} // boost

View File

@ -10,13 +10,109 @@
#ifndef BOOST_BEAST_CORE_TIMEOUT_SERVICE_HPP
#define BOOST_BEAST_CORE_TIMEOUT_SERVICE_HPP
//#include <boost/asio/execution_context.hpp>
#include <boost/asio/io_context.hpp>
#include <chrono>
#include <boost/beast/core/detail/config.hpp>
#include <boost/asio/io_context.hpp> // #include <boost/asio/execution_context.hpp>
#include <cstddef>
#include <functional> // 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<class Executor, class CancelHandler>
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 <boost/beast/_experimental/core/impl/timeout_service.ipp>
#include <boost/beast/_experimental/core/impl/timeout_service.hpp>
#endif

View File

@ -13,9 +13,11 @@
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/type_traits.hpp>
#include <boost/beast/_experimental/core/detail/timeout_service.hpp>
#include <boost/beast/_experimental/core/timeout_service.hpp>
#include <boost/beast/_experimental/core/detail/saved_handler.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/basic_stream_socket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/executor.hpp>
#include <chrono>
@ -30,6 +32,11 @@ class tcp;
namespace boost {
namespace beast {
namespace detail {
template<class, class, class>
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> 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<class, class, class> 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<Protocol> 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<Protocol, Executor>& 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<Protocol, Executor>& 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<tcp::endpoint> 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<tcp::endpoint>::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<Protocol, Executor>& 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<tcp::endpoint> 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<tcp::endpoint>::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<Protocol, Executor>& s,
Iterator begin, Iterator end,
ConnectCondition connect_condition,
IteratorConnectHandler&& handler);
/* @} */
} // beast
} // boost

View File

@ -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 <boost/beast/_experimental/core/detail/timeout_service.hpp>
#include <boost/beast/_experimental/core/timeout_service.hpp>
#include <boost/assert.hpp>
#include <boost/core/exchange.hpp>
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

View File

@ -60,4 +60,6 @@
# endif
#endif
#define BOOST_BEAST_DECL inline
#endif

View File

@ -137,7 +137,10 @@ public:
std::make_shared<session>(
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);
});
}
};

View File

@ -8,4 +8,4 @@
//
// Test that header file is self-contained.
#include <boost/beast/_experimental/core/detail/timeout_work_guard.hpp>
#include <boost/beast/_experimental/core/timeout_work_guard.hpp>