From ef0658f67863d36506255eb365d0989c1f1c5198 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 28 Jul 2018 12:01:15 -0700 Subject: [PATCH] Add experimental timeout_socket: This is a socket wrapper which has a built-in timeout feature on reads and writes. --- CHANGELOG.md | 6 + doc/qbk/09_releases.qbk | 6 +- doc/qbk/quickref.xml | 3 + .../core/detail/impl/timeout_service.ipp | 181 +++++++++++++ .../experimental/core/detail/service_base.hpp | 50 ++++ .../core/detail/timeout_service.hpp | 124 +++++++++ .../core/detail/timeout_work_guard.hpp | 73 ++++++ .../core/impl/timeout_service.ipp | 31 +++ .../experimental/core/impl/timeout_socket.hpp | 207 +++++++++++++++ .../experimental/core/timeout_service.hpp | 40 +++ .../experimental/core/timeout_socket.hpp | 240 ++++++++++++++++++ test/beast/experimental/CMakeLists.txt | 3 + test/beast/experimental/Jamfile | 3 + test/beast/experimental/timeout_service.cpp | 35 +++ test/beast/experimental/timeout_socket.cpp | 181 +++++++++++++ .../beast/experimental/timeout_work_guard.cpp | 11 + 16 files changed, 1193 insertions(+), 1 deletion(-) create mode 100644 include/boost/beast/experimental/core/detail/impl/timeout_service.ipp create mode 100644 include/boost/beast/experimental/core/detail/service_base.hpp create mode 100644 include/boost/beast/experimental/core/detail/timeout_service.hpp create mode 100644 include/boost/beast/experimental/core/detail/timeout_work_guard.hpp create mode 100644 include/boost/beast/experimental/core/impl/timeout_service.ipp create mode 100644 include/boost/beast/experimental/core/impl/timeout_socket.hpp create mode 100644 include/boost/beast/experimental/core/timeout_service.hpp create mode 100644 include/boost/beast/experimental/core/timeout_socket.hpp create mode 100644 test/beast/experimental/timeout_service.cpp create mode 100644 test/beast/experimental/timeout_socket.cpp create mode 100644 test/beast/experimental/timeout_work_guard.cpp 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