diff --git a/CHANGELOG.md b/CHANGELOG.md
index df814ecf..75802af4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+Version 187:
+
+* Add experimental timeout_socket
+
+--------------------------------------------------------------------------------
+
Version 186:
* basic_fields uses intrusive base hooks
diff --git a/doc/qbk/09_releases.qbk b/doc/qbk/09_releases.qbk
index 40c44af3..5c26a735 100644
--- a/doc/qbk/09_releases.qbk
+++ b/doc/qbk/09_releases.qbk
@@ -27,12 +27,16 @@
* ([issue 1091]) Fix timer on websocket upgrade in examples
-* ([issue 1270]) `basic_fields` uses intrusive base hooks
+* ([issue 1270]) [link beast.ref.boost__beast__http__basic_fields `basic_fields`] uses intrusive base hooks
* ([issue 1267]) Fix parsing of out-of-bounds hex values
* Workaround for http-server-fast and libstdc++
+[*Experimental]
+
+* Add [link beast.ref.boost__beast__timeout_socket `timeout_socket`]
+
diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml
index c5d9bef7..13354b97 100644
--- a/doc/qbk/quickref.xml
+++ b/doc/qbk/quickref.xml
@@ -302,16 +302,19 @@
Classes
+ basic_timeout_socket
flat_stream
ssl_stream
http::icy_stream
test::fail_count
test::stream
+ timeout_socket
Functions
+ set_timeout_service_options
test::connect
diff --git a/include/boost/beast/experimental/core/detail/impl/timeout_service.ipp b/include/boost/beast/experimental/core/detail/impl/timeout_service.ipp
new file mode 100644
index 00000000..fd1427bd
--- /dev/null
+++ b/include/boost/beast/experimental/core/detail/impl/timeout_service.ipp
@@ -0,0 +1,181 @@
+//
+// 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/service_base.hpp b/include/boost/beast/experimental/core/detail/service_base.hpp
new file mode 100644
index 00000000..278db3f7
--- /dev/null
+++ b/include/boost/beast/experimental/core/detail/service_base.hpp
@@ -0,0 +1,50 @@
+//
+// 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_SERVICE_BASE_HPP
+#define BOOST_BEAST_CORE_DETAIL_SERVICE_BASE_HPP
+
+#include
+
+namespace boost {
+namespace beast {
+namespace detail {
+
+template
+class service_id : public boost::asio::execution_context::id
+{
+};
+
+template
+class service_base
+ : public boost::asio::execution_context::service
+{
+protected:
+ boost::asio::execution_context& ctx_;
+
+public:
+ static service_id id;
+
+ explicit
+ service_base(boost::asio::execution_context& ctx)
+ : boost::asio::execution_context::service(ctx)
+ , ctx_(ctx)
+ {
+ }
+};
+
+template
+service_id
+service_base::id;
+
+} // 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
new file mode 100644
index 00000000..b32cedda
--- /dev/null
+++ b/include/boost/beast/experimental/core/detail/timeout_service.hpp
@@ -0,0 +1,124 @@
+//
+// 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_HPP
+#define BOOST_BEAST_CORE_DETAIL_TIMEOUT_SERVICE_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace boost {
+namespace beast {
+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
+{
+public:
+ using key_type = timeout_service;
+
+ // VFALCO Should be execution_context
+ explicit
+ timeout_service(boost::asio::io_context& ctx);
+
+ void
+ on_work_started(timeout_object& obj);
+
+ void
+ on_work_complete(timeout_object& obj);
+
+ void
+ on_work_stopped(timeout_object& obj);
+
+ void
+ set_option(std::chrono::seconds n);
+
+private:
+ friend class timeout_object;
+
+ using list_type = std::vector;
+
+ void insert(timeout_object& obj, list_type& list);
+ void remove(timeout_object& obj);
+ void do_async_wait();
+ void on_timer(error_code ec);
+
+ 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;
+ boost::asio::steady_timer timer_;
+ std::chrono::seconds interval_{30ul};
+};
+
+//------------------------------------------------------------------------------
+
+} // detail
+} // beast
+} // boost
+
+#include
+
+#endif
diff --git a/include/boost/beast/experimental/core/detail/timeout_work_guard.hpp b/include/boost/beast/experimental/core/detail/timeout_work_guard.hpp
new file mode 100644
index 00000000..463567ae
--- /dev/null
+++ b/include/boost/beast/experimental/core/detail/timeout_work_guard.hpp
@@ -0,0 +1,73 @@
+//
+// 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_WORK_GUARD_HPP
+#define BOOST_BEAST_CORE_DETAIL_TIMEOUT_WORK_GUARD_HPP
+
+#include
+#include
+#include
+
+namespace boost {
+namespace beast {
+namespace detail {
+
+class timeout_work_guard
+{
+ timeout_object* obj_;
+
+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()
+ {
+ reset();
+ }
+
+ timeout_work_guard(timeout_work_guard&& other)
+ : obj_(boost::exchange(other.obj_, nullptr))
+ {
+ }
+
+ explicit
+ timeout_work_guard(timeout_object& obj)
+ : obj_(&obj)
+ {
+ obj_->service().on_work_started(*obj_);
+ }
+
+ bool
+ owns_work() const
+ {
+ return obj_ != nullptr;
+ }
+
+ void
+ reset()
+ {
+ if(obj_)
+ obj_->service().on_work_stopped(*obj_);
+ }
+
+ void
+ complete()
+ {
+ BOOST_ASSERT(obj_ != nullptr);
+ obj_->service().on_work_complete(*obj_);
+ obj_ = nullptr;
+ }
+};
+
+} // detail
+} // 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
new file mode 100644
index 00000000..4da0eed1
--- /dev/null
+++ b/include/boost/beast/experimental/core/impl/timeout_service.ipp
@@ -0,0 +1,31 @@
+//
+// 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
new file mode 100644
index 00000000..8c5a7d74
--- /dev/null
+++ b/include/boost/beast/experimental/core/impl/timeout_socket.hpp
@@ -0,0 +1,207 @@
+//
+// 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_TIMOUT_SOCKET_HPP
+#define BOOST_BEAST_CORE_IMPL_TIMOUT_SOCKET_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace boost {
+namespace beast {
+
+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;
+
+ template
+ async_op(
+ Buffers const& b,
+ DeducedHandler&& h,
+ basic_timeout_socket& s,
+ std::true_type)
+ : h_(std::forward(h))
+ , s_(s)
+ , work_(s.rd_timer_)
+ {
+ s_.sock_.async_read_some(b, std::move(*this));
+ }
+
+ template
+ async_op(
+ Buffers const& b,
+ DeducedHandler&& h,
+ basic_timeout_socket& s,
+ std::false_type)
+ : h_(std::forward(h))
+ , s_(s)
+ , work_(s.wr_timer_)
+ {
+ s_.sock_.async_write_some(b, 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(async_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, async_op* op)
+ {
+ using boost::asio::asio_handler_invoke;
+ asio_handler_invoke(f, std::addressof(op->h_));
+ }
+
+ void
+ operator()(error_code ec, std::size_t bytes_transferred)
+ {
+ if(s_.expired_)
+ {
+ BOOST_ASSERT(ec == boost::asio::error::operation_aborted);
+ ec = boost::asio::error::timed_out;
+ }
+ else
+ {
+ work_.complete();
+ }
+ h_(ec, bytes_transferred);
+ }
+};
+
+//------------------------------------------------------------------------------
+
+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)
+ , sock_(ctx)
+{
+}
+
+template
+template
+BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
+ void(boost::system::error_code, std::size_t))
+basic_timeout_socket::
+async_read_some(
+ MutableBufferSequence const& buffers,
+ ReadHandler&& handler)
+{
+ static_assert(boost::asio::is_mutable_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence requirements not met");
+ BOOST_BEAST_HANDLER_INIT(
+ ReadHandler, void(error_code, std::size_t));
+ async_op(buffers,
+ std::forward(handler), *this,
+ std::true_type{});
+ return init.result.get();
+}
+
+template
+template
+BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
+ void(boost::system::error_code, std::size_t))
+basic_timeout_socket::
+async_write_some(
+ ConstBufferSequence const& buffers,
+ WriteHandler&& handler)
+{
+ static_assert(boost::asio::is_const_buffer_sequence<
+ ConstBufferSequence>::value,
+ "ConstBufferSequence requirements not met");
+ BOOST_BEAST_HANDLER_INIT(
+ WriteHandler, void(error_code, std::size_t));
+ async_op(buffers,
+ std::forward(handler), *this,
+ std::false_type{});
+ return init.result.get();
+}
+
+} // beast
+} // boost
+
+#endif
diff --git a/include/boost/beast/experimental/core/timeout_service.hpp b/include/boost/beast/experimental/core/timeout_service.hpp
new file mode 100644
index 00000000..2a5da2b8
--- /dev/null
+++ b/include/boost/beast/experimental/core/timeout_service.hpp
@@ -0,0 +1,40 @@
+//
+// 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_TIMEOUT_SERVICE_HPP
+#define BOOST_BEAST_CORE_TIMEOUT_SERVICE_HPP
+
+//#include
+#include
+#include
+
+namespace boost {
+namespace beast {
+
+/** Set timeout service options in an execution context.
+
+ This changes the time interval for all timeouts associated
+ with the execution context. The option must be set before any
+ timeout objects are constructed.
+
+ @param ctx The execution context.
+
+ @param interval The approximate amount of time until a timeout occurs.
+*/
+void
+set_timeout_service_options(
+ boost::asio::io_context& ctx, // VFALCO should be execution_context
+ std::chrono::seconds interval);
+
+} // beast
+} // boost
+
+#include
+
+#endif
diff --git a/include/boost/beast/experimental/core/timeout_socket.hpp b/include/boost/beast/experimental/core/timeout_socket.hpp
new file mode 100644
index 00000000..b2c2a22e
--- /dev/null
+++ b/include/boost/beast/experimental/core/timeout_socket.hpp
@@ -0,0 +1,240 @@
+//
+// 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_TIMEOUT_SOCKET_HPP
+#define BOOST_BEAST_CORE_TIMEOUT_SOCKET_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace boost {
+namespace asio {
+namespace ip {
+class tcp;
+} // ip
+} // asio
+} // boost
+
+namespace boost {
+namespace beast {
+
+/** A socket wrapper which automatically times out on asynchronous reads.
+
+ This wraps a normal stream socket and implements a simple and efficient
+ timeout for asynchronous read operations.
+
+ @note Meets the requirements of @b AsyncReadStream and @b AsyncWriteStream
+*/
+template<
+ class Protocol,
+ class Executor = boost::asio::executor
+>
+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;
+ };
+
+ Executor ex_; // must come first
+ timer rd_timer_;
+ timer wr_timer_;
+ boost::asio::basic_stream_socket sock_;
+ bool expired_ = false;
+
+public:
+ /// The type of the next layer.
+ using next_layer_type = boost::asio::basic_stream_socket;
+
+ /// The type of the lowest layer.
+ using lowest_layer_type = get_lowest_layer;
+
+ /// The protocol used by the stream.
+ using protocol_type = Protocol;
+
+ /// The type of the executor associated with the object.
+ using executor_type = Executor;
+
+ // VFALCO we only support default-construction
+ // of the contained socket for now.
+ // This constructor needs a protocol parameter.
+ //
+ /** Constructor
+ */
+ template::value &&
+ std::is_constructible<
+ executor_type,
+ typename ExecutionContext::executor_type>::value
+ >::type
+#endif
+ >
+ explicit
+ basic_timeout_socket(ExecutionContext& ctx);
+
+ //--------------------------------------------------------------------------
+
+ /** Get the executor associated with the object.
+
+ This function may be used to obtain the executor object that the
+ stream uses to dispatch handlers for asynchronous operations.
+
+ @return A copy of the executor that stream will use to dispatch handlers.
+ */
+ executor_type
+ get_executor() const noexcept
+ {
+ return ex_;
+ }
+
+ /** Get a reference to the next layer
+
+ This function returns a reference to the next layer
+ in a stack of stream layers.
+
+ @return A reference to the next layer in the stack of
+ stream layers.
+ */
+ next_layer_type&
+ next_layer()
+ {
+ return sock_;
+ }
+
+ /** Get a reference to the next layer
+
+ This function returns a reference to the next layer in a
+ stack of stream layers.
+
+ @return A reference to the next layer in the stack of
+ stream layers.
+ */
+ next_layer_type const&
+ next_layer() const
+ {
+ return sock_;
+ }
+
+ /** Get a reference to the lowest layer
+
+ This function returns a reference to the lowest layer
+ in a stack of stream layers.
+
+ @return A reference to the lowest layer in the stack of
+ stream layers.
+ */
+ lowest_layer_type&
+ lowest_layer()
+ {
+ return sock_.lowest_layer();
+ }
+
+ /** Get a reference to the lowest layer
+
+ This function returns a reference to the lowest layer
+ in a stack of stream layers.
+
+ @return A reference to the lowest layer in the stack of
+ stream layers. Ownership is not transferred to the caller.
+ */
+ lowest_layer_type const&
+ lowest_layer() const
+ {
+ return sock_.lowest_layer();
+ }
+
+ //--------------------------------------------------------------------------
+
+ /** Start an asynchronous read.
+
+ This function is used to asynchronously read data from the stream socket.
+ The function call always returns immediately.
+
+ @param buffers One or more buffers into which the data will be read.
+ Although the buffers object may be copied as necessary, ownership of the
+ underlying memory blocks is retained by the caller, which must guarantee
+ that they remain valid until the handler is called.
+
+ @param handler The handler to be called when the read operation completes.
+ Copies will be made of the handler as required. The function signature of
+ the handler must be:
+ @code void handler(
+ const boost::system::error_code& error, // Result of operation.
+ std::size_t bytes_transferred // Number of bytes read.
+ ); @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().
+ */
+ template
+ BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
+ void(boost::system::error_code, std::size_t))
+ async_read_some(
+ MutableBufferSequence const& buffers,
+ ReadHandler&& handler);
+
+ /** Start an asynchronous write.
+
+ This function is used to asynchronously write data to the stream socket.
+ The function call always returns immediately.
+
+ @param buffers One or more data buffers to be written to the socket.
+ Although the buffers object may be copied as necessary, ownership of the
+ underlying memory blocks is retained by the caller, which must guarantee
+ that they remain valid until the handler is called.
+
+ @param handler The handler to be called when the write operation completes.
+ Copies will be made of the handler as required. The function signature of
+ the handler must be:
+ @code void handler(
+ const boost::system::error_code& error, // Result of operation.
+ std::size_t bytes_transferred // Number of bytes written.
+ ); @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().
+ */
+ template
+ BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
+ void(boost::system::error_code, std::size_t))
+ async_write_some(
+ ConstBufferSequence const& buffers,
+ 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>;
+
+} // beast
+} // boost
+
+#include
+
+#endif
diff --git a/test/beast/experimental/CMakeLists.txt b/test/beast/experimental/CMakeLists.txt
index 012907bc..45d36c74 100644
--- a/test/beast/experimental/CMakeLists.txt
+++ b/test/beast/experimental/CMakeLists.txt
@@ -22,6 +22,9 @@ add_executable (tests-beast-experimental
icy_stream.cpp
ssl_stream.cpp
stream.cpp
+ timeout_socket.cpp
+ timeout_service.cpp
+ timeout_work_guard.cpp
)
set_property(TARGET tests-beast-experimental PROPERTY FOLDER "tests")
diff --git a/test/beast/experimental/Jamfile b/test/beast/experimental/Jamfile
index b96b8ebc..c916f73a 100644
--- a/test/beast/experimental/Jamfile
+++ b/test/beast/experimental/Jamfile
@@ -13,6 +13,9 @@ local SOURCES =
icy_stream.cpp
ssl_stream.cpp
stream.cpp
+ timeout_socket.cpp
+ timeout_service.cpp
+ timeout_work_guard.cpp
;
local RUN_TESTS ;
diff --git a/test/beast/experimental/timeout_service.cpp b/test/beast/experimental/timeout_service.cpp
new file mode 100644
index 00000000..a95edd17
--- /dev/null
+++ b/test/beast/experimental/timeout_service.cpp
@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// Official repository: https://github.com/boostorg/beast
+//
+
+// Test that header file is self-contained.
+#include
+
+#include
+
+namespace boost {
+namespace beast {
+
+class timeout_service_test
+ : public beast::unit_test::suite
+{
+public:
+ void
+ run() override
+ {
+ boost::asio::io_context ctx;
+ set_timeout_service_options(ctx,
+ std::chrono::seconds(1));
+ pass();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(beast,core,timeout_service);
+
+} // beast
+} // boost
diff --git a/test/beast/experimental/timeout_socket.cpp b/test/beast/experimental/timeout_socket.cpp
new file mode 100644
index 00000000..5ac931b8
--- /dev/null
+++ b/test/beast/experimental/timeout_socket.cpp
@@ -0,0 +1,181 @@
+//
+// 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
+//
+
+// Test that header file is self-contained.
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+namespace boost {
+namespace beast {
+
+class timeout_socket_test
+ : public beast::unit_test::suite
+ , public test::enable_yield_to
+{
+public:
+ class server
+ {
+ std::ostream& log_;
+ boost::asio::io_context ioc_;
+ boost::asio::ip::tcp::acceptor acceptor_;
+ boost::asio::ip::tcp::socket socket_;
+ std::thread t_;
+
+ void
+ fail(error_code ec, string_view what)
+ {
+ if(ec != boost::asio::error::operation_aborted)
+ log_ << what << ": " << ec.message() << "\n";
+ }
+
+ public:
+ server(
+ boost::asio::ip::tcp::endpoint ep,
+ std::ostream& log)
+ : log_(log)
+ , ioc_(1)
+ , acceptor_(ioc_)
+ , socket_(ioc_)
+ {
+ boost::system::error_code ec;
+
+ acceptor_.open(ep.protocol(), ec);
+ if(ec)
+ {
+ fail(ec, "open");
+ return;
+ }
+
+ acceptor_.set_option(
+ boost::asio::socket_base::reuse_address(true), ec);
+ if(ec)
+ {
+ fail(ec, "set_option");
+ return;
+ }
+
+ acceptor_.bind(ep, ec);
+ if(ec)
+ {
+ fail(ec, "bind");
+ return;
+ }
+
+ acceptor_.listen(
+ boost::asio::socket_base::max_listen_connections, ec);
+ if(ec)
+ {
+ fail(ec, "listen");
+ return;
+ }
+
+ acceptor_.async_accept(socket_,
+ [this](error_code ec){ this->on_accept(ec); });
+
+ t_ = std::thread([this]{ ioc_.run(); });
+ }
+
+ ~server()
+ {
+ ioc_.stop();
+ t_.join();
+ }
+
+ private:
+ class session
+ : public std::enable_shared_from_this
+ {
+ boost::asio::ip::tcp::socket socket_;
+ std::ostream& log_;
+
+ public:
+ session(
+ boost::asio::ip::tcp::socket sock,
+ std::ostream& log)
+ : socket_(std::move(sock))
+ , log_(log)
+ {
+ }
+
+ void
+ run()
+ {
+ socket_.async_wait(
+ boost::asio::socket_base::wait_read,
+ std::bind(
+ &session::on_read,
+ shared_from_this(),
+ std::placeholders::_1));
+ }
+
+ protected:
+ void
+ on_read(error_code ec)
+ {
+ boost::ignore_unused(ec);
+ }
+ };
+
+ void
+ on_accept(error_code ec)
+ {
+ if(! acceptor_.is_open())
+ return;
+ if(ec)
+ fail(ec, "accept");
+ else
+ std::make_shared(
+ std::move(socket_), log_)->run();
+ acceptor_.async_accept(socket_,
+ [this](error_code ec){ this->on_accept(ec); });
+ }
+ };
+
+ void
+ testAsync()
+ {
+ boost::asio::ip::tcp::endpoint ep(
+ boost::asio::ip::make_address("127.0.0.1"), 8080);
+ server srv(ep, log);
+ {
+ boost::asio::io_context ioc;
+ set_timeout_service_options(
+ ioc, std::chrono::seconds(1));
+ timeout_socket s(ioc);
+ s.next_layer().connect(ep);
+ char buf[32];
+ s.async_read_some(boost::asio::buffer(buf),
+ [&](error_code ec, std::size_t n)
+ {
+ log << "read_some: " << ec.message() << "\n";
+ boost::ignore_unused(ec, n);
+ });
+ ioc.run();
+ }
+ }
+
+ void
+ run()
+ {
+ testAsync();
+
+ pass();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(beast,core,timeout_socket);
+
+} // beast
+} // boost
diff --git a/test/beast/experimental/timeout_work_guard.cpp b/test/beast/experimental/timeout_work_guard.cpp
new file mode 100644
index 00000000..aa5135ec
--- /dev/null
+++ b/test/beast/experimental/timeout_work_guard.cpp
@@ -0,0 +1,11 @@
+//
+// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// Official repository: https://github.com/boostorg/beast
+//
+
+// Test that header file is self-contained.
+#include