From 6caca92f0eeceae118a8c80ae520dde6912cbd55 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 20 Dec 2018 08:51:43 -0800 Subject: [PATCH] New stream_socket: This I/O object wraps an ordinary socket and provides a built-in timeout and optional bandwidth rate-limiting facility. Added class template basic_stream_socket * Meets the requirements of AsyncReadStream and AsyncWriteStream * Partially supports P1322R0: "Networking TS enhancement to enable custom I/O executors" http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html A strand or other io_context-compatible executor may be chosen to use for all asynchronous stream operations. * Supports independent timeouts on logical operations: connect, read, write, or both read and write. * Provides an option for a configurable rate limit limit on the maximum rates of reading or writing. * The previous experimental implementation, `timeout_socket` and related types, is removed. * stream_socket is an alias for basic_stream_socket which uses `net::ip::tcp` as its protocol. --- CHANGELOG.md | 1 + doc/qbk/09_releases.qbk | 2 +- doc/qbk/quickref.xml | 29 +- .../core/detail/impl/timeout_service.hpp | 326 ----- .../core/detail/saved_handler.hpp | 7 +- .../core/detail/timeout_service.hpp | 133 -- .../core/detail/timeout_service_base.hpp | 38 - .../core/impl/timeout_service.hpp | 64 - .../core/impl/timeout_socket.hpp | 478 ------- .../_experimental/core/timeout_service.hpp | 135 -- .../_experimental/core/timeout_socket.hpp | 632 --------- .../_experimental/core/timeout_work_guard.hpp | 73 -- include/boost/beast/core.hpp | 2 + .../boost/beast/core/basic_stream_socket.hpp | 1131 +++++++++++++++++ .../beast/core/detail/operation_base.hpp | 241 ++++ .../beast/core/detail/stream_socket_base.hpp | 66 + .../beast/core/impl/basic_stream_socket.hpp | 1073 ++++++++++++++++ include/boost/beast/core/stream_socket.hpp | 30 + .../beast/websocket/detail/saved_handler.hpp | 2 +- test/beast/core/CMakeLists.txt | 7 +- test/beast/core/Jamfile | 3 + test/beast/core/_detail_operation_base.cpp | 500 ++++++++ test/beast/core/basic_stream_socket.cpp | 132 ++ .../stream_socket.cpp} | 125 +- test/beast/experimental/CMakeLists.txt | 3 - test/beast/experimental/Jamfile | 3 - test/beast/experimental/timeout_service.cpp | 35 - .../beast/experimental/timeout_work_guard.cpp | 11 - 28 files changed, 3313 insertions(+), 1969 deletions(-) delete mode 100644 include/boost/beast/_experimental/core/detail/impl/timeout_service.hpp delete mode 100644 include/boost/beast/_experimental/core/detail/timeout_service.hpp delete mode 100644 include/boost/beast/_experimental/core/detail/timeout_service_base.hpp delete mode 100644 include/boost/beast/_experimental/core/impl/timeout_service.hpp delete mode 100644 include/boost/beast/_experimental/core/impl/timeout_socket.hpp delete mode 100644 include/boost/beast/_experimental/core/timeout_service.hpp delete mode 100644 include/boost/beast/_experimental/core/timeout_socket.hpp delete mode 100644 include/boost/beast/_experimental/core/timeout_work_guard.hpp create mode 100644 include/boost/beast/core/basic_stream_socket.hpp create mode 100644 include/boost/beast/core/detail/operation_base.hpp create mode 100644 include/boost/beast/core/detail/stream_socket_base.hpp create mode 100644 include/boost/beast/core/impl/basic_stream_socket.hpp create mode 100644 include/boost/beast/core/stream_socket.hpp create mode 100644 test/beast/core/_detail_operation_base.cpp create mode 100644 test/beast/core/basic_stream_socket.cpp rename test/beast/{experimental/timeout_socket.cpp => core/stream_socket.cpp} (57%) delete mode 100644 test/beast/experimental/timeout_service.cpp delete mode 100644 test/beast/experimental/timeout_work_guard.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 982bb314..0b17efe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Version 201 * Add bind_back_handler * Tidy up default-constructed iterators * Add core errors and conditions +* New basic_stream_socket -------------------------------------------------------------------------------- diff --git a/doc/qbk/09_releases.qbk b/doc/qbk/09_releases.qbk index 000526a2..444162f8 100644 --- a/doc/qbk/09_releases.qbk +++ b/doc/qbk/09_releases.qbk @@ -54,7 +54,7 @@ [*Experimental] -* Add [link beast.ref.boost__beast__timeout_socket `timeout_socket`] +* Add `timeout_socket` diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index d097edda..1f2e1f7d 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -179,6 +179,7 @@ basic_flat_buffer basic_multi_buffer + basic_stream_socket buffered_read_stream buffers_adaptor buffers_cat_view @@ -189,14 +190,14 @@ file_posix file_stdio file_win32 + flat_buffer + flat_static_buffer + flat_static_buffer_base   - flat_buffer - flat_static_buffer - flat_static_buffer_base handler_ptr iequal iless @@ -205,17 +206,24 @@ static_buffer static_buffer_base static_string + stream_socket string_param string_view + Constants + + condition + error + file_mode + Functions + async_connect bind_back_handler bind_front_handler bind_handler - make_printable buffers_cat buffers_front buffers_prefix @@ -224,17 +232,12 @@ buffers_to_string generic_category iequals + make_printable ostream read_size read_size_or_throw to_static_string - Constants - - condition - error - file_mode - Type Traits @@ -312,22 +315,16 @@ Classes - basic_timeout_socket flat_stream ssl_stream http::icy_stream test::fail_count test::stream - timeout_handle - timeout_socket - timeout_work_guard Functions - async_connect - set_timeout_service_options test::connect diff --git a/include/boost/beast/_experimental/core/detail/impl/timeout_service.hpp b/include/boost/beast/_experimental/core/detail/impl/timeout_service.hpp deleted file mode 100644 index 4cbbc821..00000000 --- a/include/boost/beast/_experimental/core/detail/impl/timeout_service.hpp +++ /dev/null @@ -1,326 +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_HPP -#define BOOST_BEAST_CORE_DETAIL_IMPL_TIMEOUT_SERVICE_HPP - -namespace boost { -namespace beast { -namespace detail { - -timeout_service:: -timeout_service(net::io_context& ctx) - : service_base(ctx) - , thunks_(1) // element [0] reserved for "null" - , timer_(ctx) -{ -} - -timeout_handle -timeout_service:: -make_handle() -{ - std::lock_guard lock(m_); - if(free_thunk_ != 0) - { - auto const n = free_thunk_; - auto& t = thunks_[n]; - free_thunk_ = t.pos; // next in free list - t = {}; - return timeout_handle(n, *this); - } - auto const n = thunks_.size(); - thunks_.emplace_back(); - return timeout_handle(n, *this); -} - -void -timeout_service:: -set_option(std::chrono::seconds n) -{ - interval_ = n; -} - -template -void -timeout_service:: -set_callback( - timeout_handle h, - Executor const& ex, - CancelHandler&& handler) -{ - thunks_[h.id_].callback.emplace( - callback< - Executor, - typename std::decay::type>{ - h, ex, - std::forward(handler)}); -} - -void -timeout_service:: -on_work_started(timeout_handle h) -{ - BOOST_ASSERT(h.id_ != 0); - if( [this, h] - { - std::lock_guard lock(m_); - auto& t = thunks_[h.id_]; - insert(t, *fresh_); - return ++pending_ == 1; - }()) - { - do_async_wait(); - } -} - -void -timeout_service:: -on_work_stopped(timeout_handle h) -{ - BOOST_ASSERT(h.id_ != 0); - std::lock_guard lock(m_); - auto& t = thunks_[h.id_]; - if(t.list != nullptr) - { - BOOST_ASSERT(! t.expired); - remove(t); - } - if(--pending_ == 0) - timer_.cancel(); - BOOST_ASSERT(pending_ >= 0); -} - -/* - Synchronization points - - (A) async_op invoke - (B) timeout handler invoke expired=true - (C) posted handler invoked canceled=true - - ---------------------------------------------- - - Linearized paths (for async_read) - - ---------------------------------------------- - - 1. async_read - 2. async_read complete, async_op posted -(A) 3. async_op invoked - - work_.try_complete() returns true - + expired==false - + thunk is removed from list - - ---------------------------------------------- - - 1. async_read - 2. async_read complete, async_op posted -(B) 3. timeout, cancel posted - - expired=true - - thunk is removed from list -(A) 4. async_op invoked - - work_.try_complete() returns false - + completed=true - - handler is saved -(C) 5. cancel invoked - - saved handler is invoked - + expired==true, canceled==false, completed==true - + work_.try_complete() returns true - - ---------------------------------------------- - - The following two paths are not distinguishable: - - 1. async_read -(B) 2. timeout, cancel posted - - expired=true - - thunk is removed from list - 3. async_read complete, async_op posted -(C) 4. cancel invoked - - socket::cancel called (what does this do?) - - canceled=true -(A) 5. async_op invoked - - expired==true, canceled==true, completed==false - - work_.try_complete() returns `true` - - 1. async_read -(B) 2. timeout, `cancel` posted - - expired=true - - thunk is removed from list -(C) 3. cancel invoked, async_read canceled - - socket::cancel called - - canceled=true -(A) 4. async_op invoked, ec==operation_aborted - - expired==true, canceled==true, completed=false - - work_.try_complete()` returns true -*/ -bool -timeout_service:: -on_try_work_complete(timeout_handle h) -{ - BOOST_ASSERT(h.id_ != 0); - std::lock_guard lock(m_); - auto& t = thunks_[h.id_]; - if(! t.expired) - { - // hot path: operation complete - BOOST_ASSERT(t.list != nullptr); - BOOST_ASSERT(! t.canceled); - BOOST_ASSERT(! t.completed); - remove(t); - return true; - } - BOOST_ASSERT(t.list == nullptr); - if(! t.canceled) - { - // happens when operation completes before - // posted cancel handler is invoked. - t.completed = true; - return false; - } - if(t.completed) - { - // happens when the saved handler is - // invoked from the posted callback - t.expired = false; - t.canceled = false; - t.completed = false; - return true; - } - // happens when operation_aborted is delivered - t.expired = false; - t.canceled = false; - return true; -} - -void -timeout_service:: -on_cancel(timeout_handle h) -{ - std::lock_guard lock(m_); - auto& t = thunks_[h.id_]; - BOOST_ASSERT(t.expired); - t.canceled = true; -} - -//------------------------------------------------------------------------------ - -void -timeout_service:: -destroy(timeout_handle h) -{ - BOOST_ASSERT(h.id_ != 0); - std::lock_guard lock(m_); - thunks_[h.id_].pos = free_thunk_; - free_thunk_ = h.id_; -} - -// Precondition: caller holds the mutex -void -timeout_service:: -insert( - thunk& t, - thunk::list_type& list) -{ - BOOST_ASSERT(t.list == nullptr); - list.emplace_back(&t); // can throw - t.list = &list; - t.pos = list.size(); -} - -// Precondition: caller holds the mutex -void -timeout_service:: -remove(thunk& t) -{ - BOOST_ASSERT(t.list != nullptr); - BOOST_ASSERT( - t.list == stale_ || - t.list == fresh_); - BOOST_ASSERT(t.list->size() > 0); - auto& list = *t.list; - auto const n = list.size() - 1; - if(t.pos != n) - { - // move back element to t.pos - list[t.pos] = list[n]; - list[t.pos]->pos = t.pos; - } - t.list = nullptr; - list.resize(n); -} - -void -timeout_service:: -do_async_wait() -{ - timer_.expires_after(interval_); - timer_.async_wait( - [this](error_code ec) - { - this->on_timer(ec); - }); -} - -void -timeout_service:: -on_timer(error_code ec) -{ - if(ec == net::error::operation_aborted) - { - BOOST_ASSERT(fresh_->empty()); - BOOST_ASSERT(stale_->empty()); - return; - } - std::vector expired; - { - std::lock_guard lock(m_); - if(! stale_->empty()) - { - for(auto t : *stale_) - { - // remove from list - t->list = nullptr; - t->expired = true; - } - std::swap(expired, *stale_); - stale_->reserve(expired.size() / 2); - } - std::swap(fresh_, stale_); - } - for(auto p : expired) - p->callback(); - if( [this] - { - std::lock_guard lock(m_); - BOOST_ASSERT(pending_); - pending_ = - ! stale_->empty() || - ! fresh_->empty(); - return pending_; - }()) - { - do_async_wait(); - } -} - -void -timeout_service:: -shutdown() noexcept -{ - // The ExecutionContext is already in a stopped - // state, so no synchronization is required. - timer_.cancel(); -} - -} // detail -} // beast -} // boost - -#endif diff --git a/include/boost/beast/_experimental/core/detail/saved_handler.hpp b/include/boost/beast/_experimental/core/detail/saved_handler.hpp index d1d000c8..6990cb0e 100644 --- a/include/boost/beast/_experimental/core/detail/saved_handler.hpp +++ b/include/boost/beast/_experimental/core/detail/saved_handler.hpp @@ -59,11 +59,10 @@ public: std::forward(h))); } - template + template< + class Handler, class T0, class... TN> void - emplace(Handler&& h, - T0&& t0, TN&... tn) + emplace(Handler&& h, T0&& t0, TN&&... tn) { using type = decltype( beast::bind_front_handler( diff --git a/include/boost/beast/_experimental/core/detail/timeout_service.hpp b/include/boost/beast/_experimental/core/detail/timeout_service.hpp deleted file mode 100644 index 7637fb79..00000000 --- a/include/boost/beast/_experimental/core/detail/timeout_service.hpp +++ /dev/null @@ -1,133 +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_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 { - -class timeout_handle; - -namespace detail { - -class timeout_service - : public service_base -{ - template - struct callback - { - timeout_handle th; - Executor ex; - Handler h; - - void - operator()() - { - net::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(net::io_context& ctx); - - BOOST_BEAST_DECL - timeout_handle - make_handle(); - - BOOST_BEAST_DECL - void set_option(std::chrono::seconds n); - - // Undefined if work is active - template - void set_callback( - timeout_handle h, - Executor const& ex, - CancelHandler&& handler); - - 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 beast::timeout_handle; - - BOOST_BEAST_DECL - void destroy(timeout_handle h); - - 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; - - std::mutex m_; - thunk::list_type list_[2]; - thunk::list_type* fresh_ = &list_[0]; - thunk::list_type* stale_ = &list_[1]; - std::deque thunks_; - std::size_t free_thunk_ = 0; - net::steady_timer timer_; - std::chrono::seconds interval_{30ul}; - long pending_ = 0; -}; - -} // detail -} // beast -} // boost - -#include - -#endif diff --git a/include/boost/beast/_experimental/core/detail/timeout_service_base.hpp b/include/boost/beast/_experimental/core/detail/timeout_service_base.hpp deleted file mode 100644 index ded1cf2d..00000000 --- a/include/boost/beast/_experimental/core/detail/timeout_service_base.hpp +++ /dev/null @@ -1,38 +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_TIMEOUT_SERVICE_BASE_HPP -#define BOOST_BEAST_CORE_DETAIL_TIMEOUT_SERVICE_BASE_HPP - -#include - -#include - -namespace boost { -namespace beast { -namespace detail { - -struct thunk -{ - using list_type = - std::vector; - - saved_handler callback; - list_type* list = nullptr; - std::size_t pos = 0; // also: next in free list - bool expired = false; - bool canceled = false; - bool completed = false; -}; - -} // detail -} // beast -} // boost - -#endif diff --git a/include/boost/beast/_experimental/core/impl/timeout_service.hpp b/include/boost/beast/_experimental/core/impl/timeout_service.hpp deleted file mode 100644 index 61aeda06..00000000 --- a/include/boost/beast/_experimental/core/impl/timeout_service.hpp +++ /dev/null @@ -1,64 +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 -#include -#include - -namespace boost { -namespace beast { - -timeout_handle:: -timeout_handle(net::io_context& ioc) - : timeout_handle( - net::use_service< - detail::timeout_service>( - ioc).make_handle()) -{ -} - -void -timeout_handle:: -destroy() -{ - BOOST_ASSERT(svc_ != nullptr); - svc_->destroy(*this); - id_ = 0; - svc_ = nullptr; -} - -template -void -timeout_handle:: -set_callback( - Executor const& ex, CancelHandler&& handler) -{ - svc_->set_callback(*this, ex, - std::forward(handler)); -} - -//------------------------------------------------------------------------------ - -void -set_timeout_service_options( - net::io_context& ioc, - std::chrono::seconds interval) -{ - net::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 deleted file mode 100644 index d2ef676b..00000000 --- a/include/boost/beast/_experimental/core/impl/timeout_socket.hpp +++ /dev/null @@ -1,478 +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_SOCKET_HPP -#define BOOST_BEAST_CORE_IMPL_TIMEOUT_SOCKET_HPP - -#include -#include -#include -#include -#include - -namespace boost { -namespace beast { - -template -template -class basic_timeout_socket::async_op -{ -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_) - , wg0_(s_.get_executor()) - , wg1_(get_executor()) - , saved_(s_.rd_op_) - - { - 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_) - , wg0_(s_.get_executor()) - , wg1_(get_executor()) - , saved_(s_.wr_op_) - { - s_.sock_.async_write_some(b, std::move(*this)); - } - - void - operator()(error_code ec, std::size_t bytes_transferred) - { - if(! work_.try_complete()) - { - saved_.emplace( - std::move(*this), - ec, - bytes_transferred); - return; - } - h_(ec, bytes_transferred); - } - - // - - using allocator_type = - net::associated_allocator_t; - - using executor_type = - net::associated_executor_t&>().get_executor())>; - - allocator_type - get_allocator() const noexcept - { - return net::get_associated_allocator(h_); - } - - executor_type - get_executor() const noexcept - { - return net::get_associated_executor( - h_, s_.get_executor()); - } - - template - friend - void asio_handler_invoke(Function&& f, async_op* op) - { - using net::asio_handler_invoke; - asio_handler_invoke(f, std::addressof(op->h_)); - } - - friend - void* asio_handler_allocate( - std::size_t size, async_op* op) - { - using net::asio_handler_allocate; - return asio_handler_allocate( - size, std::addressof(op->h_)); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, async_op* op) - { - using net::asio_handler_deallocate; - asio_handler_deallocate( - p, size, std::addressof(op->h_)); - } - - friend - bool asio_handler_is_continuation(async_op* op) - { - using net::asio_handler_is_continuation; - return asio_handler_is_continuation( - std::addressof(op->h_)); - } - -private: - Handler h_; - basic_timeout_socket& s_; - timeout_work_guard work_; - net::executor_work_guard wg0_; - net::executor_work_guard wg1_; - detail::saved_handler& saved_; -}; - -//------------------------------------------------------------------------------ - -template -template -basic_timeout_socket:: -basic_timeout_socket(ExecutionContext& ctx) - : ex_(ctx.get_executor()) - , rd_timer_(ctx) - , wr_timer_(ctx) - , cn_timer_(ctx) - , sock_(ctx) -{ - rd_timer_.set_callback(ex_, - [this] - { - if(rd_op_.empty()) - sock_.cancel(); - else - rd_op_(); - }); - - wr_timer_.set_callback(ex_, - [this] - { - if(wr_op_.empty()) - sock_.cancel(); - else - wr_op_(); - }); - - cn_timer_.set_callback(ex_, - [this] - { - if(cn_op_.empty()) - sock_.cancel(); - else - cn_op_(); - }); -} - -template -basic_timeout_socket:: -~basic_timeout_socket() -{ - rd_timer_.destroy(); - wr_timer_.destroy(); -} - -template -template -BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, - void(error_code, std::size_t)) -basic_timeout_socket:: -async_read_some( - MutableBufferSequence const& buffers, - ReadHandler&& handler) -{ - static_assert(net::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(error_code, std::size_t)) -basic_timeout_socket:: -async_write_some( - ConstBufferSequence const& buffers, - WriteHandler&& handler) -{ - static_assert(net::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(); -} - -//------------------------------------------------------------------------------ - -namespace detail { - -template< - class Protocol, class Executor, - class Handler> -class connect_op -{ -public: - template< - class Endpoints, - class Condition, - class DeducedHandler> - connect_op( - basic_timeout_socket& s, - Endpoints const& eps, - Condition cond, - DeducedHandler&& h) - : h_(std::forward(h)) - , work_(s.cnd_timer_) - , s_(s) - , wg0_(s_.get_executor()) - , wg1_(get_executor()) - { - net::async_connect( - s_.next_layer(), eps, cond, - std::move(*this)); - } - - template - void - operator()(error_code ec, Arg&& arg) - { - if(! work_.try_complete()) - { - s_.cn_op_.emplace( - std::move(*this), - ec, - std::forward(arg)); - return; - } - h_(ec, std::forward(arg)); - } - - // - - using allocator_type = - net::associated_allocator_t; - - using executor_type = - net::associated_executor_t&>().get_executor())>; - - allocator_type - get_allocator() const noexcept - { - return net::get_associated_allocator(h_); - } - - executor_type - get_executor() const noexcept - { - return net::get_associated_executor( - h_, s_.get_executor()); - } - - template - friend - void asio_handler_invoke( - Function&& f, connect_op* op) - { - using net::asio_handler_invoke; - asio_handler_invoke( - f, std::addressof(op->h_)); - } - - friend - void* asio_handler_allocate( - std::size_t size, connect_op* op) - { - using net::asio_handler_allocate; - return asio_handler_allocate( - size, std::addressof(op->h_)); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, connect_op* op) - { - using net::asio_handler_deallocate; - asio_handler_deallocate( - p, size, std::addressof(op->h_)); - } - - friend - bool asio_handler_is_continuation(connect_op* op) - { - using net::asio_handler_is_continuation; - return asio_handler_is_continuation( - std::addressof(op->h_)); - } - -private: - Handler h_; - timeout_work_guard work_; - basic_timeout_socket& s_; - net::executor_work_guard wg0_; - net::executor_work_guard wg1_; -}; - -struct any_endpoint -{ - template - bool - operator()( - Error const&, Endpoint const&) const noexcept - { - return true; - } -}; - -template -struct endpoint_range_type -{ - using const_iterator = Iterator; - - Iterator begin_; - Iterator end_; - - Iterator begin() const noexcept - { - return begin_; - } - - Iterator end() const noexcept - { - return end_; - } -}; - -template -endpoint_range_type -endpoint_range(Iterator first, Iterator last) -{ - return endpoint_range_type< - Iterator>(first, last); -} - -} // detail - -template< - class Protocol, class Executor, - class EndpointSequence, - class RangeConnectHandler, class> -BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, - void(error_code, typename Protocol::endpoint)) -async_connect( - basic_timeout_socket& s, - EndpointSequence const& endpoints, - RangeConnectHandler&& handler) -{ - BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, - void(error_code, typename Protocol::endpoint)); - detail::connect_op( - s, endpoints, detail::any_endpoint{}, - std::forward(handler)); - return init.result.get(); -} - -template< - class Protocol, class Executor, - class EndpointSequence, - class ConnectCondition, - class RangeConnectHandler, class> -BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, - void (error_code, typename Protocol::endpoint)) -async_connect( - basic_timeout_socket& s, - EndpointSequence const& endpoints, - ConnectCondition connect_condition, - RangeConnectHandler&& handler) -{ - BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, - void(error_code, typename Protocol::endpoint)); - detail::connect_op( - s, endpoints, connect_condition, - std::forward(handler)); - return init.result.get(); -} - -template< - class Protocol, class Executor, - class Iterator, - class IteratorConnectHandler, class> -BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, - void (error_code, Iterator)) -async_connect( - basic_timeout_socket& s, - Iterator begin, Iterator end, - IteratorConnectHandler&& handler) -{ - BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, - void(error_code, Iterator)); - detail::connect_op( - s, detail::endpoint_range(begin, end), detail::any_endpoint{}, - std::forward(handler)); - return init.result.get(); -} - -template< - class Protocol, class Executor, - class Iterator, - class ConnectCondition, - class IteratorConnectHandler, class> -BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, - void (error_code, Iterator)) -async_connect( - basic_timeout_socket& s, - Iterator begin, Iterator end, - ConnectCondition connect_condition, - IteratorConnectHandler&& handler) -{ - BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, - void(error_code, Iterator)); - detail::connect_op( - s, detail::endpoint_range(begin, end), connect_condition, - std::forward(handler)); - 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 deleted file mode 100644 index 7251acdd..00000000 --- a/include/boost/beast/_experimental/core/timeout_service.hpp +++ /dev/null @@ -1,135 +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_TIMEOUT_SERVICE_HPP -#define BOOST_BEAST_CORE_TIMEOUT_SERVICE_HPP - -#include -#include // #include -#include - -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(net::io_context& ioc); - - BOOST_BEAST_DECL - void - destroy(); - - template - void - set_callback( - Executor const& ex, CancelHandler&& handler); - - explicit - operator bool() const noexcept - { - return svc_ != nullptr; - } - - friend bool operator==( - timeout_handle const& lhs, - std::nullptr_t) noexcept - { - return lhs.svc_ == nullptr; - } - - friend bool operator==( - std::nullptr_t, - timeout_handle const& rhs) noexcept - { - return rhs.svc_ == nullptr; - } - - friend bool operator!=( - timeout_handle const& lhs, - std::nullptr_t) noexcept - { - return lhs.svc_ != nullptr; - } - - friend bool operator!=( - std::nullptr_t, - timeout_handle const& rhs) noexcept - { - return rhs.svc_ != nullptr; - } -}; - -/** Set timeout service options in an execution context. - - This changes the time interval for all timeouts associated - 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. -*/ -BOOST_BEAST_DECL -void -set_timeout_service_options( - net::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 deleted file mode 100644 index 69308847..00000000 --- a/include/boost/beast/_experimental/core/timeout_socket.hpp +++ /dev/null @@ -1,632 +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_TIMEOUT_SOCKET_HPP -#define BOOST_BEAST_CORE_TIMEOUT_SOCKET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace asio { -namespace ip { -class tcp; -} // ip -} // asio -} // boost - -namespace boost { -namespace beast { - -namespace detail { -template -class connect_op; -} // detail - -/** A socket wrapper which automatically times out on asynchronous reads. - - This wraps a normal stream socket and implements a simple and efficient - timeout for asynchronous read operations. - - @note Meets the requirements of @b AsyncReadStream and @b AsyncWriteStream -*/ -template< - class Protocol, - class Executor = net::executor -> -class basic_timeout_socket -{ - template class async_op; - template - friend class detail::connect_op; - - Executor ex_; // must come first - timeout_handle rd_timer_; - timeout_handle wr_timer_; - timeout_handle cn_timer_; - detail::saved_handler rd_op_; - detail::saved_handler wr_op_; - detail::saved_handler cn_op_; - net::basic_stream_socket sock_; - -public: - /// The type of the next layer. - using next_layer_type = net::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; - - /** 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. - // - /** 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() noexcept - { - 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 noexcept - { - 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() noexcept - { - 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 noexcept - { - 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 - net::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 - net::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< - net::ip::tcp, - net::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 - // net::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 - `net::io_context::post()`. - - @par Example - - @code - net::tcp::resolver r(ioc); - net::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< - net::is_endpoint_sequence< - EndpointSequence>::value>::type -#endif -> -BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, - void (boost::system::error_code, class Protocol::endpoint)) -async_connect( - basic_timeout_socket& s, - EndpointSequence const& endpoints, - RangeConnectHandler&& handler); - -/** Asynchronously establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the underlying socket's - @c async_connect member function, once for each endpoint in the sequence, - until a connection is successfully established or a timeout occurs. - - @param s The @ref basic_timeout_socket to be connected. If the socket - is already open, it will be closed. - - @param endpoints A sequence of endpoints. - - @param connect_condition A function object that is called prior to each - connection attempt. The signature of the function object must be: - - @code - bool connect_condition( - boost::system::error_code const& ec, - typename Protocol::endpoint const& next); - @endcode - - The @c ec parameter contains the result from the most recent connect - operation. Before the first connection attempt, @c ec is always set to - indicate success. The @c next parameter is the next endpoint to be tried. - The function object should return true if the next endpoint should be tried, - and false if it should be skipped. - - @param handler The handler to be called when the connect operation - completes. Ownership of the handler may be transferred. The function - signature of the handler must be: - @code - void handler( - // Result of operation. if the sequence is empty, set to - // net::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 - `net::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, - net::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 - net::tcp::resolver r(ioc); - net::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< - net::is_endpoint_sequence< - EndpointSequence>::value>::type -#endif -> -BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, - void (boost::system::error_code, class Protocol::endpoint)) -async_connect( - basic_timeout_socket& s, - EndpointSequence const& endpoints, - ConnectCondition connect_condition, - RangeConnectHandler&& handler); - -/** Asynchronously establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the underlying socket's - @c async_connect member function, once for each endpoint in the sequence, - until a connection is successfully established or a timeout occurs. - - @param s The @ref timeout_socket to be connected. If the socket - is already open, it will be closed. - - @param begin An iterator pointing to the start of a sequence of endpoints. - - @param end An iterator pointing to the end of a sequence of endpoints. - - @param handler The handler to be called when the connect operation - completes. Ownership of the handler may be transferred. The function - signature of the handler must be: - @code - void handler( - // Result of operation. if the sequence is empty, set to - // net::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 - `net::io_context::post()`. - - @par Example - - @code - std::vector endpoints = ...; - timeout_socket s(ioc.get_executor()); - - async_connect(s, - endpoints.begin(), endpoints.end(), - connect_handler); - - void connect_handler( - boost::system::error_code const& ec, - std::vector::iterator) - { - // ... - } - @endcode -*/ -template< - class Protocol, class Executor, - class Iterator, - class IteratorConnectHandler -#if ! BOOST_BEAST_DOXYGEN - ,class = typename std::enable_if< - ! net::is_endpoint_sequence< - Iterator>::value>::type -#endif -> -BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, - void (boost::system::error_code, Iterator)) -async_connect( - basic_timeout_socket& s, - Iterator begin, Iterator end, - IteratorConnectHandler&& handler); - -/** Asynchronously establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the underlying socket's - @c async_connect member function, once for each endpoint in the sequence, - until a connection is successfully established or a timeout occurs. - - @param s The @ref basic_timeout_socket to be connected. If the socket - is already open, it will be closed. - - @param begin An iterator pointing to the start of a sequence of endpoints. - - @param end An iterator pointing to the end of a sequence of endpoints. - - @param connect_condition A function object that is called prior to each - connection attempt. The signature of the function object must be: - - @code - bool connect_condition( - boost::system::error_code const& ec, - Iterator next); - @endcode - - @param handler The handler to be called when the connect operation - completes. Ownership of the handler may be transferred. The function - signature of the handler must be: - @code - void handler( - // Result of operation. if the sequence is empty, set to - // net::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 - `net::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, - net::ip::tcp::endpoint const& next) - { - if (ec) std::cout << "Error: " << ec.message() << std::endl; - std::cout << "Trying: " << next << std::endl; - return true; - } - }; - @endcode - - It would be used with the @ref boost::beast::async_connect - function as follows: - - @code - std::vector endpoints = ...; - timeout_socket s(ioc.get_executor()); - - async_connect(s, endpoints.begin(), endpoints.end(), - my_connect_condition{}, connect_handler); - - void connect_handler( - boost::system::error_code const& ec, - std::vector::iterator) - { - // ... - } - @endcode -*/ -template< - class Protocol, class Executor, - class Iterator, - class ConnectCondition, - class IteratorConnectHandler -#if ! BOOST_BEAST_DOXYGEN - ,class = typename std::enable_if< - ! net::is_endpoint_sequence< - Iterator>::value>::type -#endif -> -BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, - void (boost::system::error_code, Iterator)) -async_connect( - basic_timeout_socket& s, - Iterator begin, Iterator end, - ConnectCondition connect_condition, - IteratorConnectHandler&& handler); -/* @} */ - -} // beast -} // boost - -#include - -#endif diff --git a/include/boost/beast/_experimental/core/timeout_work_guard.hpp b/include/boost/beast/_experimental/core/timeout_work_guard.hpp deleted file mode 100644 index 9112f1e5..00000000 --- a/include/boost/beast/_experimental/core/timeout_work_guard.hpp +++ /dev/null @@ -1,73 +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_TIMEOUT_WORK_GUARD_HPP -#define BOOST_BEAST_CORE_TIMEOUT_WORK_GUARD_HPP - -#include -#include -#include - -namespace boost { -namespace beast { - -class timeout_work_guard -{ - timeout_handle h_; - -public: - timeout_work_guard(timeout_work_guard const&) = delete; - timeout_work_guard& operator=(timeout_work_guard const&) = delete; - - ~timeout_work_guard() - { - reset(); - } - - timeout_work_guard(timeout_work_guard&& other) - : h_(other.h_) - { - other.h_ = nullptr; - } - - explicit - timeout_work_guard(timeout_handle h) - : h_(h) - { - h_.service().on_work_started(h_); - } - - bool - owns_work() const - { - return h_ != nullptr; - } - - void - reset() - { - if(h_) - h_.service().on_work_stopped(h_); - } - - bool - try_complete() - { - BOOST_ASSERT(h_ != nullptr); - auto result = - h_.service().on_try_work_complete(h_); - h_ = nullptr; - return result; - } -}; - -} // beast -} // boost - -#endif diff --git a/include/boost/beast/core.hpp b/include/boost/beast/core.hpp index 642d6eda..a932b997 100644 --- a/include/boost/beast/core.hpp +++ b/include/boost/beast/core.hpp @@ -12,6 +12,7 @@ #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/beast/core/basic_stream_socket.hpp b/include/boost/beast/core/basic_stream_socket.hpp new file mode 100644 index 00000000..a8801fe4 --- /dev/null +++ b/include/boost/beast/core/basic_stream_socket.hpp @@ -0,0 +1,1131 @@ +// +// 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_BASIC_STREAM_SOCKET_HPP +#define BOOST_BEAST_CORE_BASIC_STREAM_SOCKET_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +/** A stream socket wrapper with integrated timeout and bandwidth management. + + This wraps a normal stream socket to provide the following additional + features: + + @li The class template is parameterized on a user-defined executor + used for asynchronous operations. This achieves partial support for + "Networking TS enhancement to enable custom I/O executors" [P1322R0]. + + @li Optional timeouts may be specified for logical operations which + perform asynchronous reads, writes, and connects. + + @li Optional bytes-per-second rate limits may be set independently + on asynchronous reads and writes. + + @par Usage + + Objects of this type are designed to be used in places where a + regular networking TCP/IP socket is being used. In particular this + class template replaces `net::basic_stream_socket`. The constructors + used here are similar to those of networking sockets, subject to the + that either an executor or an execution context may be passed in + the first argument. + + @par Using Executors + + @par Using Timeouts + + To use this stream declare an instance of the class. Then, before + each logical operation for which a timeout is desired, call + @ref expires_after with a duration, or call @ref expires_at with a + time point. Alternatively, call @ref expires_never to disable the + timeout for subsequent logical operations. + + A logical operation is defined as one of the following: + + @li A call to @ref beast::async_connect where the stream is passed + as the first argument. + + @li One or more calls to either one or both of the stream's + @ref async_read_some and @ref async_write_some member functions. + This also includes indirect calls, for example when passing the + stream as the first argument to an initiating function such as + `net::async_read_until`. + + Each logical operation can be considered as peforming just reads, + just writes, or both reads and writes. Calls to @ref beast::async_connect + count as both a read and a write, although no actual reads or writes + are performed. While logical operations can include both reading + and writing, the usual restriction on having at most one read and + one write outstanding simultaneously applies. + + The implementation maintains two timers: one timer for reads, and + another timer for writes. When the expiration time is adjusted + (by calling @ref expires_after or @ref expires_at), the indiviudal + timer is only set if there is not currently an operation of that + type (read or write) outstanding. It is undefined behavior to set + an expiration when there is both a read and a write pending, since + there would be no available timer to apply the expiration to. + + @par Example + This code sets a timeout, and uses a generic networking stream algorithm + to read data from a timed stream until a particular delimiter is found + or until the stream times out: + @code + template + void async_read_line ( + basic_stream_socket& stream, + net::streambuf& buffer, ReadHandler&& handler) + { + stream.expires_after (std::chrono::seconds(30)); + + net::async_read_until (stream, buffer, "\r\n", std::forward(handler)); + } + @endcode + + When a timeout occurs the socket will be closed, canceling any + pending I/O operations. The completion handlers for these canceled + operations will be invoked with the error @ref beast::error::timeout. + + @par Using Rate Limits + + @tparam Protocol A type meeting the requirements of Protocol + representing the protocol the protocol to use for the basic stream socket. + A common choice is `net::ip::tcp`. + + @tparam Executor A type meeting the requirements of Executor to + be used for submitting all completion handlers which do not already have an + associated executor. + + @note A multi-stream object must not be moved or destroyed while there + are oustanding asynchronous operations associated with it. Objects of this + type meet the requirements of @b AsyncReadStream and @b AsyncWriteStream. + + @par Thread Safety + Distinct objects: Safe.@n + Shared objects: Unsafe. The application must also ensure + that all asynchronous operations are performed within the same + implicit or explicit strand. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html +*/ +template +class basic_stream_socket + : private detail::stream_socket_base +{ + using time_point = typename + std::chrono::steady_clock::time_point; + + // the number of seconds in each time slice + // for applying bandwidth rate limiting. + enum : std::size_t + { + rate_seconds = 3 + }; + + static constexpr time_point never() + { + return (time_point::max)(); + } + + static std::size_t constexpr no_limit = + (std::numeric_limits::max)(); + + struct impl_type + : std::enable_shared_from_this + { + Executor ex; // must come first + net::basic_stream_socket< + Protocol> socket; + net::steady_timer rate_timer; // rate-limit interval timer + net::steady_timer read_timer; // for read timeout + net::steady_timer write_timer; // for write/connect timeout + + // VFALCO these could be 32-bit unsigneds + std::size_t read_limit = no_limit; // read budget + std::size_t read_remain = no_limit; // read balance + std::size_t write_limit = no_limit; // write budget + std::size_t write_remain = no_limit;// write balance + + char waiting = 0; // number of waiters on rate timer + bool read_pending = false; // if read (or connect) is pending + bool read_closed = false; // if read timed out + bool write_pending = false; // if write (or connect) is pending + bool write_closed = false; // if write (or connect) timed out + + template + explicit + impl_type(Executor const&, Args&&...); + + impl_type(impl_type&&) = default; + impl_type& operator=(impl_type&&); + + void reset(); // set timeouts to never + void close(); // cancel everything + void maybe_kick(); // kick the rate timer if needed + void on_timer(); // rate timer completion + }; + + // We use shared ownership for the state so it can + // outlive the destruction of the stream_socket object, + // in the case where there is no outstanding read or + // write but the implementation is still waiting on + // the rate timer. + std::shared_ptr impl_; + + // Restricted until P1322R0 is incorporated into Boost.Asio. + static_assert( + std::is_convertible< + decltype(std::declval().context()), + net::io_context&>::value, + "Only net::io_context is currently supported for executor_type::context()"); + + template class read_op; + template class write_op; + + template + friend class detail::stream_socket_connect_op; + + template + friend class basic_stream_socket; + +public: + /// The type of the next layer. + using next_layer_type = net::basic_stream_socket; + + /// The type of the lowest layer. + using lowest_layer_type = get_lowest_layer; + + /// The type of the executor associated with the object. + using executor_type = Executor; + + /// The protocol type. + using protocol_type = Protocol; + + /// The endpoint type. + using endpoint_type = typename Protocol::endpoint; + + /** Destructor + + This function destroys the socket. + + @note The behavior of destruction while asynchronous + operations are outstanding is undefined. + */ + ~basic_stream_socket(); + + /** Construct a basic_stream_socket without opening it. + + This constructor creates a stream socket without opening it. The + socket needs to be opened and then connected or accepted before + data can be sent or received on it. + + @param ctx An object whose type meets the requirements of + ExecutionContext, which the stream socket will use + to dispatch handlers for any asynchronous operations performed + on the socket. Currently, the only supported execution context + which may be passed here is `net::io_context`. + + @note This function does not participate in overload resolution unless: + @li `std::is_convertible::value` is `true`, and + @li `std::is_constructible::value` is `true`. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + template< + class ExecutionContext + #if ! BOOST_BEAST_DOXYGEN + , class = typename std::enable_if< + std::is_convertible< + ExecutionContext&, + net::execution_context&>::value && + std::is_constructible< + executor_type, + typename ExecutionContext::executor_type>::value + >::type + #endif + > + explicit + basic_stream_socket(ExecutionContext& ctx); + + /** Construct a basic_stream_socket without opening it. + + This constructor creates a stream socket without opening it. The + socket needs to be opened and then connected or accepted before + data can be sent or received on it. + + @param ex The executor which the stream socket will use to dispatch + handlers for any asynchronous operations performed on the socket. + Currently, only executors that return `net::io_context&` from + `ex.context()` are supported. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + explicit + basic_stream_socket(executor_type const& ex); + + /** Construct and open a basic_stream_socket. + + This constructor creates and opens a stream socket. The socket + needs to be connected or accepted before data can be sent or + received on it. + + @param ctx An object whose type meets the requirements of + ExecutionContext, which the stream socket will use + to dispatch handlers for any asynchronous operations performed + on the socket. Currently, the only supported execution context + which may be passed here is `net::io_context`. + + @param protocol An object specifying protocol parameters to be + used. + + @note This function does not participate in overload resolution unless: + @li `std::is_convertible::value` is `true`, and + @li `std::is_constructible::value` is `true`. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + template< + class ExecutionContext + #if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + std::is_convertible< + ExecutionContext&, + net::execution_context&>::value && + std::is_constructible< + executor_type, + typename ExecutionContext::executor_type>::value + >::type + #endif + > + basic_stream_socket( + ExecutionContext& ctx, + protocol_type const& protocol); + + /** Construct and open a basic_stream_socket. + + This constructor creates and opens a stream socket. The socket + needs to be connected or accepted before data can be sent or + received on it. + + @param ex The executor which the stream socket will use to dispatch + handlers for any asynchronous operations performed on the socket. + Currently, only executors that return `net::io_context&` from + `ex.context()` are supported. + + @param protocol An object specifying protocol parameters to be + used. + + @note This function does not participate in overload resolution unless: + @li `std::is_convertible::value` is `true`, and + @li `std::is_constructible::value` is `true`. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + basic_stream_socket( + executor_type const& ex, + protocol_type const& protocol); + + /** Construct a basic_stream_socket, opening and binding it to the given local endpoint. + + This constructor creates a stream socket and automatically + opens it bound to the specified endpoint on the local machine. + The protocol used is the protocol associated with the given + endpoint. + + @param ctx An object whose type meets the requirements of + ExecutionContext, which the stream socket will use + to dispatch handlers for any asynchronous operations performed + on the socket. Currently, the only supported execution context + which may be passed here is `net::io_context`. + + @param endpoint An endpoint on the local machine to which the + stream socket will be bound. + + @note This function does not participate in overload resolution unless: + @li `std::is_convertible::value` is `true`, and + @li `std::is_constructible::value` is `true`. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + template< + class ExecutionContext + #if ! BOOST_BEAST_DOXYGEN + , class = typename std::enable_if< + std::is_convertible< + ExecutionContext&, + net::execution_context&>::value && + std::is_constructible< + executor_type, + typename ExecutionContext::executor_type>::value + >::type + #endif + > + basic_stream_socket( + ExecutionContext& ctx, + endpoint_type const& endpoint); + + /** Construct a basic_stream_socket, opening and binding it to the given local endpoint. + + This constructor creates a stream socket and automatically + opens it bound to the specified endpoint on the local machine. + The protocol used is the protocol associated with the given + endpoint. + + @param ex The executor which the stream socket will use to dispatch + handlers for any asynchronous operations performed on the socket. + Currently, only executors that return `net::io_context&` from + `ex.context()` are supported. + + @param endpoint An endpoint on the local machine to which the + stream socket will be bound. + + @note This function does not participate in overload resolution unless: + @li `std::is_convertible::value` is `true`, and + @li `std::is_constructible::value` is `true`. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + basic_stream_socket( + executor_type const& ex, + endpoint_type const& endpoint); + + /** Construct a basic_stream_socket, opening and binding it to the given local endpoint. + + This constructor creates a stream socket object to from an existing + next layer object. + + @param ctx An object whose type meets the requirements of + ExecutionContext, which the stream socket will use + to dispatch handlers for any asynchronous operations performed + on the socket. Currently, the only supported execution context + which may be passed here is `net::io_context`. + + @param socket The socket object to construct with. Ownership of + this object is transferred by move. + + @note This function does not participate in overload resolution unless: + @li `std::is_convertible::value` is `true`, and + @li `std::is_constructible::value` is `true`. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + template< + class ExecutionContext + #if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + std::is_convertible< + ExecutionContext&, + net::execution_context&>::value && + std::is_constructible< + executor_type, + typename ExecutionContext::executor_type>::value + >::type + #endif + > + basic_stream_socket( + ExecutionContext& ctx, + next_layer_type&& socket); + + /** Construct a basic_stream_socket, opening and binding it to the given local endpoint. + + This constructor creates a stream socket object to from an existing + next layer object. + + @param ex The executor which the stream socket will use to dispatch + handlers for any asynchronous operations performed on the socket. + Currently, only executors that return `net::io_context&` from + `ex.context()` are supported. + + @param socket The socket object to construct with. Ownership of + this object is transferred by move. + + @note This function does not participate in overload resolution unless: + @li `std::is_convertible::value` is `true`, and + @li `std::is_constructible::value` is `true`. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + basic_stream_socket( + executor_type const& ex, + next_layer_type&& socket); + + /** Move-construct a basic_stream_socket from another + + This constructor moves a stream socket from one object to another. + + The behavior of moving a stream socket while asynchronous operations + are outstanding is undefined. + + @param other The other basic_stream_socket object from which the + move will occur. + + @note Following the move, the moved-from object is in the same state + as if constructed using the @c basic_stream_socket(ExecutionContext&) + constructor. + */ + basic_stream_socket(basic_stream_socket&& other); + + /** Move-assign a basic_stream_socket from another. + + This assignment operator moves a stream socket from one object + to another. + + The behavior of move assignment while asynchronous operations + are pending is undefined. + + @param other The other basic_stream_socket object from which the + move will occur. + + @note Following the move, the moved-from object is in the same state + as if constructed using the @c basic_stream_socket(ExecutionContext&) + constructor. + */ + basic_stream_socket& operator=(basic_stream_socket&& other); + + /** Move-construct a basic_stream_socket from a socket of another protocol and executor type. + + This constructor moves a stream socket from one object to another. + + The behavior of moving a stream socket while asynchronous operations + are outstanding is undefined. + + @param other The other basic_stream_socket object from which the + move will occur. + + @note This constructor does not participate in overload resolution unless: + @li `std::is_convertible::value` is `true`, and + @li `std::is_convertible::value` is `true`. + */ + template< + class OtherProtocol, + class OtherExecutor + #if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + std::is_convertible< + OtherProtocol, protocol_type>::value && + std::is_convertible< + OtherExecutor, executor_type>::value>::type + #endif + > + basic_stream_socket( + basic_stream_socket&& other); + + /** Move-assign a basic_stream_socket from a socket of another protocol and executor type. + + This assignment operator a stream socket from one object to another. + + The behavior of moving a stream socket while asynchronous operations + are outstanding is undefined. + + @param other The other basic_stream_socket object from which the + move will occur. + + @note This constructor does not participate in overload resolution unless: + @li `std::is_convertible::value` is `true`, and + @li `std::is_convertible::value` is `true`. + */ + template< + class OtherProtocol, + class OtherExecutor + #if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + std::is_convertible< + OtherProtocol, protocol_type>::value && + std::is_convertible< + OtherExecutor, executor_type>::value>::type + #endif + > + basic_stream_socket& operator=(basic_stream_socket< + OtherProtocol, OtherExecutor>&& other); + + //-------------------------------------------------------------------------- + + /** 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 impl_->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() noexcept + { + return impl_->socket; + } + + /** 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 noexcept + { + return impl_->socket; + } + + /** 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() noexcept + { + return impl_->socket.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 noexcept + { + return impl_->socket.lowest_layer(); + } + + /** Set the number of bytes allowed to be read per second. + + The limit will take effect in the next measured time + interval (currently set to 3 seconds). + + @param bytes_per_second The maximum number of bytes the + implementation should attempt to read per second. A value + of zero indicates no limit. + */ + void + read_limit(std::size_t bytes_per_second); + + /** Set the number of bytes allowed to be written per second. + + The limit will take effect in the next measured time + interval (currently set to 3 seconds). + + @param bytes_per_second The maximum number of bytes the + implementation should attempt to write per second. A value + of zero indicates no limit. + */ + void + write_limit(std::size_t bytes_per_second); + + /** Set the timeout for the next logical operation. + + This sets either the read timer, the write timer, or + both timers to expire after the specified amount of time + has elapsed. If a timer expires when the corresponding + asynchronous operation is outstanding, the stream will be + closed and any outstanding operations will complete with the + error @ref beast::error::timeout. Otherwise, if the timer + expires while no operations are outstanding, and the expiraton + is not set again, the next operation will time out immediately. + + The timer applies collectively to any asynchronous reads + or writes initiated after the expiration is set, until the + expiration is set again. A call to @ref beast::async_connect + counts as both a read and a write. + + @param expiry_time The amount of time after which a logical + operation should be considered timed out. + */ + void + expires_after( + std::chrono::nanoseconds expiry_time); + + /** Set the timeout for the next logical operation. + + This sets either the read timer, the write timer, or both + timers to expire at the specified time point. If a timer + expires when the corresponding asynchronous operation is + outstanding, the stream will be closed and any outstanding + operations will complete with the error @ref beast::error::timeout. + Otherwise, if the timer expires while no operations are outstanding, + and the expiraton is not set again, the next operation will time out + immediately. + + The timer applies collectively to any asynchronous reads + or writes initiated after the expiration is set, until the + expiration is set again. A call to @ref beast::async_connect + counts as both a read and a write. + + @param expiry_time The time point after which a logical + operation should be considered timed out. + */ + void + expires_at(net::steady_timer::time_point expiry_time); + + /// Disable the timeout for the next logical operation. + void + expires_never(); + + /** Close the timed stream. + + This cancels all timers and pending I/O. The completion handlers + for any pending I/O will see an error code. + */ + void + close() + { + impl_->close(); + } + + //-------------------------------------------------------------------------- + + /** 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( + error_code const & 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 + `net::io_context::post()`. + */ + template + BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, + void(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( + error_code const & 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 + `net::io_context::post()`. + */ + template + BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, + void(error_code, std::size_t)) + async_write_some( + ConstBufferSequence const& buffers, + WriteHandler&& handler); +}; + +//------------------------------------------------------------------------------ + +/** + @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, and terminating if a timeout occurs. + + 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 beast::basic_stream_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 + // net::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 + `net::io_context::post()`. + + @par Example + @code + net::tcp::resolver r(ioc); + net::tcp::resolver::query q("host", "service"); + timed_stream s(ioc.get_executor()); + + // ... + r.async_resolve(q, resolve_handler); + + // ... + + void resolve_handler( + error_code const& ec, + tcp::resolver::results_type results) + { + if (!ec) + { + async_connect(s, results, connect_handler); + } + } + + // ... + void connect_handler( + 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< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, + void (error_code, typename Protocol::endpoint)) +async_connect( + basic_stream_socket& s, + EndpointSequence const& endpoints, + RangeConnectHandler&& handler); + +/** Asynchronously establishes a socket connection by trying each endpoint in a sequence, and terminating if a timeout occurs. + + 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 beast::basic_stream_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( + 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 + // net::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 + `net::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()( + error_code const& ec, + net::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 + net::tcp::resolver r(ioc); + net::tcp::resolver::query q("host", "service"); + timed_stream s(ioc.get_executor()); + + // ... + r.async_resolve(q, resolve_handler); + + // ... + + void resolve_handler( + error_code const& ec, + tcp::resolver::results_type results) + { + if (!ec) + { + async_connect(s, results, my_connect_condition{}, connect_handler); + } + } + + // ... + void connect_handler( + 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< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, + void (error_code, typename Protocol::endpoint)) +async_connect( + basic_stream_socket& s, + EndpointSequence const& endpoints, + ConnectCondition connect_condition, + RangeConnectHandler&& handler); + +/** Asynchronously establishes a socket connection by trying each endpoint in a sequence, and terminating if a timeout occurs. + + 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 beast::basic_stream_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 + // net::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 + `net::io_context::post()`. + + @par Example + @code + std::vector endpoints = ...; + timed_stream s(ioc.get_executor()); + + async_connect(s, + endpoints.begin(), endpoints.end(), + connect_handler); + void connect_handler( + error_code const& ec, + std::vector::iterator) + { + // ... + } + @endcode +*/ +template< + class Protocol, class Executor, + class Iterator, + class IteratorConnectHandler> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (error_code, Iterator)) +async_connect( + basic_stream_socket& s, + Iterator begin, Iterator end, + IteratorConnectHandler&& handler); + +/** Asynchronously establishes a socket connection by trying each endpoint in a sequence, and terminating if a timeout occurs. + + 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 beast::basic_stream_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( + 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 + // net::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 + `net::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()( + error_code const& ec, + net::ip::tcp::endpoint const& next) + { + if (ec) std::cout << "Error: " << ec.message() << std::endl; + std::cout << "Trying: " << next << std::endl; + return true; + } + }; + @endcode + It would be used with the @ref boost::beast::async_connect + function as follows: + @code + std::vector endpoints = ...; + timed_stream s(ioc.get_executor()); + + async_connect(s, endpoints.begin(), endpoints.end(), + my_connect_condition{}, connect_handler); + void connect_handler( + error_code const& ec, + std::vector::iterator) + { + // ... + } + @endcode +*/ +template< + class Protocol, class Executor, + class Iterator, + class ConnectCondition, + class IteratorConnectHandler> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (error_code, Iterator)) +async_connect( + basic_stream_socket& s, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + IteratorConnectHandler&& handler); +/* @} */ + +} // beast +} // boost + +#include + +#endif diff --git a/include/boost/beast/core/detail/operation_base.hpp b/include/boost/beast/core/detail/operation_base.hpp new file mode 100644 index 00000000..d85169ca --- /dev/null +++ b/include/boost/beast/core/detail/operation_base.hpp @@ -0,0 +1,241 @@ +// +// 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_OPERATION_BASE_HPP +#define BOOST_BEAST_CORE_DETAIL_OPERATION_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace detail { + +/** Base class which stores a handler and forwards handler associations. + + This mix-in assists bind wrappers, intermediate handlers, composed + operations, and similar types which store a handler, by acting as + a base class which holds the handler. Any networking customizations + on the handler will be propagated to the derived class. Specifically: + + @li Any allocator associated with the handler will be propagated to + this object. Otherwise, the allocator associated with this + object will be a default allocator which the caller may specify + through a template parameter and constructor parameter. + + @li Any executor associated with the handler will be propagated to + this object. Otherwise, the executor associated with this + object will be a default executor which the caller may specify + through a template parameter and constructor parameter. + + @li The legacy customization points + `asio_handler_invoke`, + `asio_handler_allocate`, + `asio_handler_deallocate`, and + `asio_handler_is_continuation`, + which use argument-dependent lookup, will be forwarded to the + legacy customization points associated with the handler. + + @par Example + + The following declaration produces a class which wraps a + handler and inherits all of the networking customizations + associated with that handler: + + @code + template + struct wrapped_handler : operation_base< + Handler, net::associated_executor_t> + { + template + explicit wrapped_handler (Handler_&& handler) + : operation_base>( + std::forward(handler), net::get_associated_executor(handler)) + { + } + + template + void operator()(Args&&... args) + { + this->handler_(std::forward(args)...); + } + }; + @endcode + + @tparam Handler The type of the completion handler to store. + This type must meet the requirements of CompletionHandler. + + @tparam Executor The executor type to use if the handler does not + have an associated executor. + + @tparam Allocator The allocator type to use if the handler does not + have an associated allocator. If this parameter is omitted, then + `std::allocator` will be used. +*/ +template< + class Handler, + class Executor, + class Allocator = std::allocator +> +class operation_base +#if ! BOOST_BEAST_DOXYGEN + : private boost::empty_value< + net::associated_allocator_t< + Handler, Allocator>, 0> + , private boost::empty_value< + net::associated_executor_t< + Handler, Executor>, 1> +#endif +{ +public: + /** The type of allocator associated with this object. + + If a class derived from @ref operation_base is a completion + handler, then the associated allocator of the derived class will + be this type. + */ + using allocator_type = + net::associated_allocator_t< + Handler, Allocator>; + + /** The type of executor associated with this object. + + If a class derived from @ref operation_base is a completion + handler, then the associated executor of the derived class will + be this type. + */ + using executor_type = + net::associated_executor_t< + Handler, Executor>; + + /** Returns the allocator associated with this object. + + If a class derived from @ref operation_base is a completion + handler, then the object returned from this function will be used + as the associated allocator of the derived class. + */ + allocator_type + get_allocator() const noexcept + { + return boost::empty_value< + allocator_type, 0>::get(); + } + + /** Returns the allocator associated with this object. + + If a class derived from @ref operation_base is a completion + handler, then the object returned from this function will be used + as the associated allocator of the derived class. + */ + executor_type + get_executor() const noexcept + { + return boost::empty_value< + executor_type, 1>::get(); + } + +protected: + Handler handler_; + + template< + class DeducedHandler +#if BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + ! std::is_same::type, + operation_base + >::value>::type +#endif + > + operation_base( + DeducedHandler&& handler, + executor_type ex = executor_type{}, + allocator_type alloc = allocator_type{}) + : boost::empty_value( + boost::empty_init_t{}, alloc) + , boost::empty_value( + boost::empty_init_t{}, ex) + , handler_(std::forward(handler)) + { + } + +public: +#if ! BOOST_BEAST_DOXYGEN + template< + class Handler_, + class Executor_, + class Allocator_, + class Function> + friend + void asio_handler_invoke( + Function&& f, + operation_base< + Handler_, Executor_, Allocator_>* p); + + friend + void* asio_handler_allocate( + std::size_t size, operation_base* p) + { + using net::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(p->handler_)); + } + + friend + void asio_handler_deallocate( + void* mem, std::size_t size, + operation_base* p) + { + using net::asio_handler_deallocate; + asio_handler_deallocate(mem, size, + std::addressof(p->handler_)); + } + + friend + bool asio_handler_is_continuation( + operation_base* p) + { + using net::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(p->handler_)); + } +#endif +}; + +#if ! BOOST_BEAST_DOXYGEN + +template< + class Handler, + class Executor, + class Allocator, + class Function> +void asio_handler_invoke( + Function&& f, + operation_base< + Handler, Executor, Allocator>* p) +{ + using net::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(p->handler_)); +} + +#endif + +} // detail +} // beast +} // boost + +#endif diff --git a/include/boost/beast/core/detail/stream_socket_base.hpp b/include/boost/beast/core/detail/stream_socket_base.hpp new file mode 100644 index 00000000..5ee9df2f --- /dev/null +++ b/include/boost/beast/core/detail/stream_socket_base.hpp @@ -0,0 +1,66 @@ +// +// 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_STREAM_SOCKET_BASE_HPP +#define BOOST_BEAST_CORE_DETAIL_STREAM_SOCKET_BASE_HPP + +#include +#include + +namespace boost { +namespace beast { +namespace detail { + +template +class stream_socket_connect_op; + +class stream_socket_base +{ +protected: + class pending_guard + { + bool& b_; + bool clear_ = true; + + public: + ~pending_guard() + { + if(clear_) + b_ = false; + } + + explicit + pending_guard(bool& b) + : b_(b) + { + BOOST_ASSERT(! b_); + b_ = true; + } + + pending_guard(pending_guard&& other) noexcept + : b_(other.b_) + , clear_(boost::exchange(other.clear_, false)) + { + } + + void + reset() + { + BOOST_ASSERT(clear_); + b_ = false; + clear_ = false; + } + }; +}; + +} // detail +} // beast +} // boost + +#endif diff --git a/include/boost/beast/core/impl/basic_stream_socket.hpp b/include/boost/beast/core/impl/basic_stream_socket.hpp new file mode 100644 index 00000000..2b95369a --- /dev/null +++ b/include/boost/beast/core/impl/basic_stream_socket.hpp @@ -0,0 +1,1073 @@ +// +// 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_BASIC_STREAM_SOCKET_HPP +#define BOOST_BEAST_CORE_IMPL_BASIC_STREAM_SOCKET_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +/* + The algorithm for implementing the timeout depends + on the executor providing ordered execution guarantee. + `net::strand` automatically provides this, and we assume + that an implicit strand (one thread calling io_context::run) + also provides this. +*/ + +template +template +class basic_stream_socket::read_op + : public detail::operation_base< + Handler, Executor> + , public boost::asio::coroutine +{ + basic_stream_socket& s_; + net::executor_work_guard wg0_; + net::executor_work_guard wg1_; + pending_guard pg_; + Buffers b_; + + struct timeout_handler + { + std::shared_ptr impl; + + void + operator()(error_code ec) + { + // timer canceled + if(ec == net::error::operation_aborted) + return; + + BOOST_ASSERT(! ec); + + if(! impl->read_closed) + { + // timeout + impl->close(); + impl->read_closed = true; + } + else + { + // late completion + impl->read_closed = false; + } + } + }; + +public: + template + read_op( + basic_stream_socket& s, + Buffers const& b, + Handler_&& h) + : detail::operation_base< + Handler, Executor>( + std::forward(h), + net::get_associated_executor( + h, s.get_executor())) + , s_(s) + , wg0_(s_.get_executor()) + , wg1_(net::get_associated_executor( + this->handler_, s_.get_executor())) + , pg_(s_.impl_->read_pending) + , b_(b) + { + (*this)(); + } + + void + operator()( + error_code ec = {}, + std::size_t bytes_transferred = 0) + { + BOOST_ASIO_CORO_REENTER(*this) + { + // must come first + // VFALCO TODO what about the handler's allocator? + s_.impl_->read_timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{s_.impl_})); + + s_.impl_->maybe_kick(); + + // check if the balance is zero + if(s_.impl_->read_remain == 0) + { + // wait until the next time slice + ++s_.impl_->waiting; + BOOST_ASIO_CORO_YIELD + s_.impl_->rate_timer.async_wait(std::move(*this)); + + if(ec) + { + // caused by impl::close on timeout + BOOST_ASSERT( + ec == net::error::operation_aborted); + goto upcall; + } + + // must call this + s_.impl_->on_timer(); + BOOST_ASSERT(s_.impl_->read_remain > 0); + } + + // we always use buffers_prefix, + // to reduce template instantiations. + BOOST_ASSERT(s_.impl_->read_remain > 0); + BOOST_ASIO_CORO_YIELD + s_.impl_->socket.async_read_some( + beast::buffers_prefix( + s_.impl_->read_remain, b_), + std::move(*this)); + + if(s_.impl_->read_remain != no_limit) + { + // adjust balance + BOOST_ASSERT( + bytes_transferred <= s_.impl_->read_remain); + s_.impl_->read_remain -= bytes_transferred; + } + + { + // try cancelling timer + auto const n = + s_.impl_->read_timer.cancel(); + + if(s_.impl_->read_closed) + { + // timeout handler already invoked + BOOST_ASSERT(n == 0); + ec = beast::error::timeout; + s_.impl_->read_closed = false; + } + else if(n == 0) + { + // timeout handler already queued + ec = beast::error::timeout; + + s_.impl_->close(); + s_.impl_->read_closed = true; + } + else + { + // timeout was canceled + BOOST_ASSERT(n == 1); + } + } + + upcall: + pg_.reset(); + this->handler_(ec, bytes_transferred); + } + } +}; + +//------------------------------------------------------------------------------ + +template +template +class basic_stream_socket::write_op + : public detail::operation_base< + Handler, Executor> + , public boost::asio::coroutine +{ + basic_stream_socket& s_; + net::executor_work_guard wg0_; + net::executor_work_guard wg1_; + pending_guard pg_; + Buffers b_; + + struct timeout_handler + { + std::shared_ptr impl; + + void + operator()(error_code ec) + { + // timer canceled + if(ec == net::error::operation_aborted) + return; + + BOOST_ASSERT(! ec); + + if(! impl->write_closed) + { + // timeout + impl->close(); + impl->write_closed = true; + } + else + { + // late completion + impl->write_closed = false; + } + } + }; + +public: + template + write_op( + basic_stream_socket& s, + Buffers const& b, + Handler_&& h) + : detail::operation_base< + Handler, Executor>( + std::forward(h), + net::get_associated_executor( + h, s.get_executor())) + , s_(s) + , wg0_(s_.get_executor()) + , wg1_(net::get_associated_executor( + this->handler_, s_.get_executor())) + , pg_(s_.impl_->write_pending) + , b_(b) + { + (*this)(); + } + + void + operator()( + error_code ec = {}, + std::size_t bytes_transferred = 0) + { + BOOST_ASIO_CORO_REENTER(*this) + { + // must come first + // VFALCO TODO what about the handler's allocator? + s_.impl_->write_timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{s_.impl_})); + + s_.impl_->maybe_kick(); + + // check if the balance is zero + if(s_.impl_->write_remain == 0) + { + // wait until the next time slice + ++s_.impl_->waiting; + BOOST_ASIO_CORO_YIELD + s_.impl_->rate_timer.async_wait(std::move(*this)); + + if(ec) + { + // caused by impl::close on timeout + BOOST_ASSERT( + ec == net::error::operation_aborted); + goto upcall; + } + + // must call this + s_.impl_->on_timer(); + BOOST_ASSERT(s_.impl_->write_remain > 0); + } + + // we always use buffers_prefix, + // to reduce template instantiations. + BOOST_ASSERT(s_.impl_->write_remain > 0); + BOOST_ASIO_CORO_YIELD + s_.impl_->socket.async_write_some( + beast::buffers_prefix( + s_.impl_->write_remain, b_), + std::move(*this)); + + if(s_.impl_->write_remain != no_limit) + { + // adjust balance + BOOST_ASSERT( + bytes_transferred <= s_.impl_->write_remain); + s_.impl_->write_remain -= bytes_transferred; + } + + { + // try cancelling timer + auto const n = + s_.impl_->write_timer.cancel(); + + if(s_.impl_->write_closed) + { + // timeout handler already invoked + BOOST_ASSERT(n == 0); + ec = beast::error::timeout; + s_.impl_->write_closed = false; + } + else if(n == 0) + { + // timeout handler already queued + ec = beast::error::timeout; + + s_.impl_->close(); + s_.impl_->write_closed = true; + } + else + { + // timeout was canceled + BOOST_ASSERT(n == 1); + } + } + + upcall: + pg_.reset(); + this->handler_(ec, bytes_transferred); + } + } +}; + +//------------------------------------------------------------------------------ + +namespace detail { + +template< + class Protocol, class Executor, class Handler> +class stream_socket_connect_op + : public detail::operation_base< + Handler, Executor> +{ + using stream_type = + beast::basic_stream_socket; + stream_type& s_; + net::executor_work_guard wg0_; + net::executor_work_guard wg1_; + typename stream_type::pending_guard pg0_; + typename stream_type::pending_guard pg1_; + + struct timeout_handler + { + std::shared_ptr impl; + + void + operator()(error_code ec) + { + // timer canceled + if(ec == net::error::operation_aborted) + return; + + BOOST_ASSERT(! ec); + + if(! impl->write_closed) + { + // timeout + impl->close(); + impl->write_closed = true; + } + else + { + // late completion + impl->write_closed = false; + } + } + }; + +public: + template< + class Endpoints, class Condition, + class Handler_> + stream_socket_connect_op( + stream_type& s, + Endpoints const& eps, + Condition cond, + Handler_&& h) + : detail::operation_base< + Handler, Executor>( + std::forward(h), + net::get_associated_executor( + h, s.get_executor())) + , s_(s) + , wg0_(s_.get_executor()) + , wg1_(net::get_associated_executor( + this->handler_, s_.get_executor())) + , pg0_(s_.impl_->read_pending) + , pg1_(s_.impl_->write_pending) + { + // must come first + // VFALCO TODO what about the handler's allocator? + s_.impl_->write_timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{s_.impl_})); + + net::async_connect(s_.impl_->socket, + eps, cond, std::move(*this)); + // *this is now moved-from + } + + template< + class Iterator, class Condition, + class Handler_> + stream_socket_connect_op( + stream_type& s, + Iterator begin, Iterator end, + Condition cond, + Handler_&& h) + : detail::operation_base< + Handler, Executor>( + std::forward(h), + net::get_associated_executor( + h, s.get_executor())) + , s_(s) + , wg0_(s_.get_executor()) + , wg1_(net::get_associated_executor( + this->handler_, s_.get_executor())) + , pg0_(s_.impl_->read_pending) + , pg1_(s_.impl_->write_pending) + { + // must come first + s_.impl_->write_timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{s_.impl_})); + + net::async_connect(s_.impl_->socket, + begin, end, cond, std::move(*this)); + // *this is now moved-from + } + + template + void + operator()(error_code ec, Arg&& arg) + { + // try to cancel the timer + auto const n = + s_.impl_->write_timer.cancel(); + + if(s_.impl_->write_closed) + { + // timeout handler already invoked + BOOST_ASSERT(n == 0); + ec = beast::error::timeout; + s_.impl_->write_closed = false; + } + else if(n == 0) + { + // timeout handler already queued + ec = beast::error::timeout; + + s_.impl_->close(); + s_.impl_->write_closed = true; + } + else + { + // timeout was canceled + BOOST_ASSERT(n == 1); + } + + pg0_.reset(); + pg1_.reset(); + this->handler_(ec, std::forward(arg)); + } +}; + +} // detail + +//------------------------------------------------------------------------------ + +template +template +basic_stream_socket:: +impl_type:: +impl_type( + Executor const& ex_, + Args&&... args) + : ex(ex_) + , socket(std::forward(args)...) + , rate_timer(ex.context()) + , read_timer(ex.context()) + , write_timer(ex.context()) +{ + reset(); +} + +template +auto +basic_stream_socket:: +impl_type:: +operator=(impl_type&& other) -> impl_type& +{ + // VFALCO This hack is because legacy io_context::strand + // doesn't support operator=. Don't worry, constructing + // an executor cannot throw. + ex.~Executor(); + ::new(&ex) Executor(other.ex); + + socket = std::move(other.socket); + rate_timer = std::move(other.rate_timer); + read_timer = std::move(other.read_timer); + write_timer = std::move(other.write_timer); + + read_limit = other.read_limit; + read_remain = other.read_remain; + write_limit = other.write_limit; + write_remain = other.write_remain; + + waiting = other.waiting; + read_pending = other.read_pending; + read_closed = other.read_closed; + write_pending = other.write_pending; + write_closed = other.write_closed; + + return *this; +} + +template +void +basic_stream_socket:: +impl_type:: +reset() +{ + // If assert goes off, it means that there are + // already read or write (or connect) operations + // outstanding, so there is nothing to apply + // the expiration time to! + // + BOOST_ASSERT(! read_pending || ! write_pending); + + if(! read_pending) + BOOST_VERIFY( + read_timer.expires_at(never()) == 0); + + if(! write_pending) + BOOST_VERIFY( + write_timer.expires_at(never()) == 0); +} + +template +void +basic_stream_socket:: +impl_type:: +close() +{ + socket.close(); + rate_timer.cancel(); + read_timer.cancel(); + write_timer.cancel(); +} + +template +void +basic_stream_socket:: +impl_type:: +maybe_kick() +{ + // see if the timer needs a kick + if(waiting > 0) + { + BOOST_ASSERT( + rate_timer.expiry() != never()); + return; + } + + // are both limits disabled? + if( read_limit == no_limit && + write_limit == no_limit) + return; + + BOOST_ASSERT( + read_pending || write_pending); + + // update budget + read_remain = read_limit; + write_remain = write_limit; + + // start the clock + ++waiting; + on_timer(); +} + +template +void +basic_stream_socket:: +impl_type:: +on_timer() +{ + BOOST_ASSERT(waiting > 0); + + // the last waiter starts the new slice + if(--waiting > 0) + return; + + // update the expiration time + BOOST_VERIFY(rate_timer.expires_after( + std::chrono::seconds(rate_seconds)) == 0); + + // update budget + read_remain = read_limit; + write_remain = write_limit; + + // wait again + ++waiting; + auto const this_ = this->shared_from_this(); + rate_timer.async_wait( + net::bind_executor(ex, + [this_](error_code ec) + { + if(ec == net::error::operation_aborted) + return; + BOOST_ASSERT(! ec); + if(ec) + return; + this_->on_timer(); + } + )); +} + +//------------------------------------------------------------------------------ + +template +basic_stream_socket:: +~basic_stream_socket() +{ + // the shared object can outlive *this, + // cancel any operations so the shared + // object is destroyed as soon as possible. + impl_->close(); +} + +template +template +basic_stream_socket:: +basic_stream_socket(ExecutionContext& ctx) + : impl_(std::make_shared( + ctx.get_executor(), + ctx)) +{ + static_assert( + std::is_same::value, + "Only net::io_context is currently supported for ExecutionContext"); +} + +template +basic_stream_socket:: +basic_stream_socket(executor_type const& ex) + : impl_(std::make_shared( + ex, + ex.context())) +{ +} + +template +template +basic_stream_socket:: +basic_stream_socket( + ExecutionContext& ctx, + protocol_type const& protocol) + : impl_(std::make_shared( + ctx.get_executor(), + ctx, + protocol)) +{ + static_assert( + std::is_same::value, + "Only net::io_context is currently supported for ExecutionContext"); +} + +template +basic_stream_socket:: +basic_stream_socket( + executor_type const& ex, + protocol_type const& protocol) + : impl_(std::make_shared( + ex, + ex.context(), + protocol)) +{ +} + +template +template +basic_stream_socket:: +basic_stream_socket( + ExecutionContext& ctx, + endpoint_type const& endpoint) + : impl_(std::make_shared( + ctx.get_executor(), + ctx, + endpoint)) +{ + static_assert( + std::is_same::value, + "Only net::io_context is currently supported for ExecutionContext"); +} + +template +basic_stream_socket:: +basic_stream_socket( + executor_type const& ex, + endpoint_type const& endpoint) + : impl_(std::make_shared( + ex, + ex.context(), + endpoint)) +{ +} + +template +template +basic_stream_socket:: +basic_stream_socket( + ExecutionContext& ctx, + next_layer_type&& socket) + : impl_(std::make_shared( + ctx.get_executor(), + std::move(socket))) +{ + static_assert( + std::is_same::value, + "Only net::io_context is currently supported for ExecutionContext"); +} + +template +basic_stream_socket:: +basic_stream_socket( + executor_type const& ex, + next_layer_type&& socket) + : impl_(std::make_shared( + ex, + std::move(socket))) +{ +} + +template +basic_stream_socket:: +basic_stream_socket(basic_stream_socket&& other) + : impl_(std::make_shared( + std::move(*other.impl_))) +{ + // Can't move while operations are pending! + BOOST_ASSERT(! impl_->read_pending); + BOOST_ASSERT(! impl_->write_pending); +} + +template +auto +basic_stream_socket:: +operator=(basic_stream_socket&& other) -> + basic_stream_socket& +{ + // Can't move while operations are pending! + BOOST_ASSERT(! impl_->read_pending); + BOOST_ASSERT(! impl_->write_pending); + BOOST_ASSERT(! other.impl_->read_pending); + BOOST_ASSERT(! other.impl_->write_pending); + *impl_ = std::move(*other.impl_); + return *this; +} + +template +template +basic_stream_socket:: +basic_stream_socket( + basic_stream_socket&& other) + : impl_(std::make_shared( + other.get_executor(), + std::move(other.impl_->socket))) +{ + static_assert(std::is_same< + net::io_context, + typename std::decay::type>::value, + "Only net::io_context& is currently supported for other.get_executor().context()"); +} + +template +template +auto +basic_stream_socket:: +operator=( + basic_stream_socket&& other) -> + basic_stream_socket& +{ + static_assert(std::is_same< + net::io_context, + typename std::decay::type>::value, + "Only net::io_context& is currently supported for other.get_executor().context()"); + + // Can't move while operations are pending! + BOOST_ASSERT(! impl_->read_pending); + BOOST_ASSERT(! impl_->write_pending); + BOOST_ASSERT(! other.impl_->read_pending); + BOOST_ASSERT(! other.impl_->write_pending); + + impl_ = std::make_shared( + other.get_executor(), + std::move(other.impl_->socket)); + return *this; +} + +//------------------------------------------------------------------------------ + +template +void +basic_stream_socket:: +read_limit(std::size_t bytes_per_second) +{ + if(bytes_per_second == 0) + { + impl_->read_limit = no_limit; + } + else if(bytes_per_second < no_limit) + { + impl_->read_limit = + bytes_per_second * rate_seconds; + } + else + { + impl_->read_limit = no_limit - 1; + } + BOOST_ASSERT(impl_->read_limit > 0); +} + +template +void +basic_stream_socket:: +write_limit(std::size_t bytes_per_second) +{ + if(bytes_per_second == 0) + { + impl_->write_limit = no_limit; + } + else if(bytes_per_second < no_limit) + { + impl_->write_limit = + bytes_per_second * rate_seconds; + } + else + { + impl_->write_limit = no_limit - 1; + } + BOOST_ASSERT(impl_->write_limit > 0); +} + +template +void +basic_stream_socket:: +expires_after(std::chrono::nanoseconds expiry_time) +{ + // If assert goes off, it means that there are + // already read or write (or connect) operations + // outstanding, so there is nothing to apply + // the expiration time to! + // + BOOST_ASSERT( + ! impl_->read_pending || + ! impl_->write_pending); + + if(! impl_->read_pending) + BOOST_VERIFY( + impl_->read_timer.expires_after( + expiry_time) == 0); + + if(! impl_->write_pending) + BOOST_VERIFY( + impl_->write_timer.expires_after( + expiry_time) == 0); +} + +template +void +basic_stream_socket:: +expires_at( + net::steady_timer::time_point expiry_time) +{ + // If assert goes off, it means that there are + // already read or write (or connect) operations + // outstanding, so there is nothing to apply + // the expiration time to! + // + BOOST_ASSERT( + ! impl_->read_pending || + ! impl_->write_pending); + + if(! impl_->read_pending) + BOOST_VERIFY( + impl_->read_timer.expires_at( + expiry_time) == 0); + + if(! impl_->write_pending) + BOOST_VERIFY( + impl_->write_timer.expires_at( + expiry_time) == 0); +} + +template +void +basic_stream_socket:: +expires_never() +{ + impl_->reset(); +} + +template +template +BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, + void(error_code, std::size_t)) +basic_stream_socket:: +async_read_some( + MutableBufferSequence const& buffers, + ReadHandler&& handler) +{ + static_assert(net::is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + BOOST_BEAST_HANDLER_INIT( + ReadHandler, void(error_code, std::size_t)); + read_op( + *this, buffers, std::forward(handler)); + return init.result.get(); +} + +template +template +BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, + void(error_code, std::size_t)) +basic_stream_socket:: +async_write_some( + ConstBufferSequence const& buffers, + WriteHandler&& handler) +{ + static_assert(net::is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + BOOST_BEAST_HANDLER_INIT( + WriteHandler, void(error_code, std::size_t)); + write_op( + *this, buffers, std::forward(handler)); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +namespace detail { + +struct any_endpoint +{ + template + bool + operator()( + Error const&, Endpoint const&) const noexcept + { + return true; + } +}; + +} // detail + +template< + class Protocol, class Executor, + class EndpointSequence, + class RangeConnectHandler, + class> +BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, + void(error_code, typename Protocol::endpoint)) +async_connect( + basic_stream_socket& s, + EndpointSequence const& endpoints, + RangeConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, + void(error_code, typename Protocol::endpoint)); + detail::stream_socket_connect_op( + s, endpoints, detail::any_endpoint{}, + std::forward(handler)); + return init.result.get(); +} + +template< + class Protocol, class Executor, + class EndpointSequence, + class ConnectCondition, + class RangeConnectHandler, + class> +BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, + void (error_code, typename Protocol::endpoint)) +async_connect( + basic_stream_socket& s, + EndpointSequence const& endpoints, + ConnectCondition connect_condition, + RangeConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, + void(error_code, typename Protocol::endpoint)); + detail::stream_socket_connect_op( + s, endpoints, connect_condition, + std::forward(handler)); + return init.result.get(); +} + +template< + class Protocol, class Executor, + class Iterator, + class IteratorConnectHandler> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (error_code, Iterator)) +async_connect( + basic_stream_socket& s, + Iterator begin, Iterator end, + IteratorConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, + void(error_code, Iterator)); + detail::stream_socket_connect_op( + s, begin, end, detail::any_endpoint{}, + std::forward(handler)); + return init.result.get(); +} + +template< + class Protocol, class Executor, + class Iterator, + class ConnectCondition, + class IteratorConnectHandler> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (error_code, Iterator)) +async_connect( + basic_stream_socket& s, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + IteratorConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, + void(error_code, Iterator)); + detail::stream_socket_connect_op( + s, begin, end, connect_condition, + std::forward(handler)); + return init.result.get(); +} + +} // beast +} // boost + +#endif diff --git a/include/boost/beast/core/stream_socket.hpp b/include/boost/beast/core/stream_socket.hpp new file mode 100644 index 00000000..e0484a09 --- /dev/null +++ b/include/boost/beast/core/stream_socket.hpp @@ -0,0 +1,30 @@ +// +// 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_STREAM_SOCKET_HPP +#define BOOST_BEAST_CORE_STREAM_SOCKET_HPP + +#include +#include +#include +#include + +namespace boost { +namespace beast { + +/** A TCP/IP stream socket which supports timeouts and rate limits +*/ +using stream_socket = basic_stream_socket< + net::ip::tcp, + net::io_context::executor_type>; + +} // beast +} // boost + +#endif diff --git a/include/boost/beast/websocket/detail/saved_handler.hpp b/include/boost/beast/websocket/detail/saved_handler.hpp index 02fadfbb..0de240f6 100644 --- a/include/boost/beast/websocket/detail/saved_handler.hpp +++ b/include/boost/beast/websocket/detail/saved_handler.hpp @@ -42,7 +42,7 @@ class saved_handler }; template - class impl : public base + class impl final : public base { Handler h_; net::executor_work_guard< diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt index 7f3914d2..88804079 100644 --- a/test/beast/core/CMakeLists.txt +++ b/test/beast/core/CMakeLists.txt @@ -16,16 +16,18 @@ add_executable (tests-beast-core ${EXTRAS_FILES} ${TEST_MAIN} Jamfile - buffer_test.hpp - file_test.hpp _detail_base64.cpp _detail_buffer.cpp _detail_clamp.cpp + _detail_operation_base.cpp _detail_read.cpp _detail_sha1.cpp _detail_tuple.cpp _detail_variant.cpp _detail_varint.cpp + buffer_test.hpp + file_test.hpp + basic_stream_socket.cpp bind_handler.cpp buffer_traits.cpp buffered_read_stream.cpp @@ -51,6 +53,7 @@ add_executable (tests-beast-core span.cpp static_buffer.cpp static_string.cpp + stream_socket.cpp string.cpp string_param.cpp type_traits.cpp diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile index a5bdeabd..60ab672e 100644 --- a/test/beast/core/Jamfile +++ b/test/beast/core/Jamfile @@ -11,11 +11,13 @@ local SOURCES = _detail_base64.cpp _detail_buffer.cpp _detail_clamp.cpp + _detail_operation_base.cpp _detail_read.cpp _detail_sha1.cpp _detail_tuple.cpp _detail_variant.cpp _detail_varint.cpp + basic_stream_socket.cpp bind_handler.cpp buffer_traits.cpp buffered_read_stream.cpp @@ -41,6 +43,7 @@ local SOURCES = span.cpp static_buffer.cpp static_string.cpp + stream_socket.cpp string.cpp string_param.cpp type_traits.cpp diff --git a/test/beast/core/_detail_operation_base.cpp b/test/beast/core/_detail_operation_base.cpp new file mode 100644 index 00000000..e134852a --- /dev/null +++ b/test/beast/core/_detail_operation_base.cpp @@ -0,0 +1,500 @@ +// +// 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 +#include + +//------------------------------------------------------------------------------ + +namespace boost { +namespace beast { +namespace detail { + +namespace { + +struct specialized_handler +{ + bool invoked = false; + struct executor + { + void* context() { return nullptr; } + void on_work_started() {} + void on_work_finished() {} + template void dispatch(F&&) {} + template void post(F&&) {} + template void defer(F&&) {} + }; +}; + +template +static +void +asio_handler_invoke( + Function&& f, + specialized_handler* p) +{ + p->invoked = true; + f(); +} + +static +void* +asio_handler_allocate( + std::size_t, + specialized_handler* p) +{ + p->invoked = true; + return nullptr; +} + +static +void +asio_handler_deallocate( + void*, std::size_t, + specialized_handler* p) +{ + p->invoked = true; +} + +static bool +asio_handler_is_continuation( + specialized_handler* p) +{ + p->invoked = true; + return false; +} + +} // + +} // detail +} // beast +} // boost + +//------------------------------------------------------------------------------ + +namespace boost { +namespace asio { + +template +struct associated_allocator< + boost::beast::detail::specialized_handler, Allocator> +{ + using type = std::allocator; + + static type get( + boost::beast::detail::specialized_handler const& h, + Allocator const& a = Allocator()) noexcept + { + return type{}; + } +}; + +template +struct associated_executor< + boost::beast::detail::specialized_handler, Executor> +{ + using type = typename + boost::beast::detail::specialized_handler::executor; + + static type get( + boost::beast::detail::specialized_handler const& h, + Executor const& ex = Executor()) noexcept + { + return type{}; + } +}; + +} // asio +} // boost + +//------------------------------------------------------------------------------ + +namespace boost { +namespace beast { +namespace detail { + +class operation_base_test : public beast::unit_test::suite +{ +public: + using default_alloc = std::allocator; + using default_exec = net::system_executor; + + struct U {}; + struct V {}; + + template + struct executor + { + void* context() { return nullptr; } + void on_work_started() {} + void on_work_finished() {} + template void dispatch(F&&) {} + template void post(F&&) {} + template void defer(F&&) {} + }; + + struct none + { + void operator()() const + { + } + }; + + struct with_alloc + { + using allocator_type = std::allocator; + }; + + struct with_exec + { + using executor_type = executor; + }; + + struct move_only + { + move_only() = default; + move_only(move_only&&) = default; + move_only(move_only const&) = delete; + void operator()() const{}; + }; + + template< + class H, + class E = default_exec, + class A = default_alloc> + using tested_base = + operation_base; + + struct movable_handler : tested_base + { + movable_handler() + : tested_base(move_only{}) + { + } + }; + + struct test_handler : + tested_base + { + test_handler() + : tested_base< + specialized_handler>( + specialized_handler{}) + { + } + + bool invoked() const noexcept + { + return this->handler_.invoked; + } + }; + + + // no nested allocator type + + BOOST_STATIC_ASSERT( + std::is_same< + default_alloc, + net::associated_allocator_t< + tested_base + >>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + default_alloc, + decltype(net::get_associated_allocator( + std::declval>() + ))>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + default_alloc, + decltype(net::get_associated_allocator( + std::declval>() + ))>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + std::allocator, + decltype(net::get_associated_allocator(std::declval< + tested_base>>() + ))>::value); + + // shouldn't work due to net.ts limitations + BOOST_STATIC_ASSERT( + ! std::is_same< + std::allocator, + decltype(net::get_associated_allocator( + std::declval>(), + std::declval>() + ))>::value); + BOOST_STATIC_ASSERT( + std::is_same< + default_alloc, + decltype(net::get_associated_allocator( + std::declval>(), + std::declval>() + ))>::value); + + // nested allocator_type + + BOOST_STATIC_ASSERT( + std::is_same< + std::allocator, + net::associated_allocator_t< + tested_base + >>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + std::allocator, + decltype(net::get_associated_allocator( + std::declval>() + ))>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + std::allocator, + decltype(net::get_associated_allocator( + std::declval>(), + std::declval>() + ))>::value); + + // specialization of associated_allocator + + BOOST_STATIC_ASSERT( + std::is_same< + std::allocator, + net::associated_allocator_t< + tested_base< + specialized_handler> + >>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + std::allocator, + decltype(net::get_associated_allocator( + std::declval>() + ))>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + std::allocator, + decltype(net::get_associated_allocator( + std::declval>(), + std::declval>() + ))>::value); + + // no nested executor type + + BOOST_STATIC_ASSERT( + std::is_same< + default_exec, + net::associated_executor_t< + tested_base + >>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + executor, + net::associated_executor_t< + tested_base> + >>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + default_exec, + decltype(net::get_associated_executor( + std::declval>() + ))>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + executor, + decltype(net::get_associated_executor(std::declval< + tested_base>>() + ))>::value); + + // shouldn't work due to net.ts limitations + BOOST_STATIC_ASSERT( + ! std::is_same< + executor, + decltype(net::get_associated_executor( + std::declval>(), + std::declval>() + ))>::value); + BOOST_STATIC_ASSERT( + std::is_same< + default_exec, + decltype(net::get_associated_executor( + std::declval>(), + std::declval>() + ))>::value); + + // nested executor_type + + BOOST_STATIC_ASSERT( + std::is_same< + executor, + net::associated_executor_t< + tested_base + >>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + executor, + decltype(net::get_associated_executor( + std::declval>() + ))>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + executor, + decltype(net::get_associated_executor( + std::declval>(), + std::declval>() + ))>::value); + + // specialization of associated_executor + + BOOST_STATIC_ASSERT( + std::is_same< + specialized_handler::executor, + net::associated_executor_t< + tested_base< + specialized_handler> + >>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + specialized_handler::executor, + decltype(net::get_associated_executor(std::declval< + tested_base< + specialized_handler>>() + ))>::value); + + BOOST_STATIC_ASSERT( + std::is_same< + specialized_handler::executor, + decltype(net::get_associated_executor(std::declval< + tested_base< + specialized_handler>>(), + std::declval>() + ))>::value); + + //-------------------------------------------------------------------------- + + template + struct wrapped_handler : operation_base< + Handler, net::associated_executor_t> + { + template + explicit wrapped_handler (Handler_&& handler) + : operation_base>( + std::forward(handler), net::get_associated_executor(handler)) + { + } + + template + void operator()(Args&&... args) + { + this->handler_(std::forward(args)...); + } + }; + + void + testJavadocs() + { + wrapped_handler{none{}}(); + } + + //-------------------------------------------------------------------------- + + void + testLegacyHooks() + { + // asio_handler_invoke + { + test_handler h; + BEAST_EXPECT(! h.invoked()); + bool invoked = false; + using net::asio_handler_invoke; + asio_handler_invoke( + [&invoked] + { + invoked =true; + }, &h); + BEAST_EXPECT(invoked); + BEAST_EXPECT(h.invoked()); + } + + // asio_handler_allocate + { + test_handler h; + BEAST_EXPECT(! h.invoked()); + using net::asio_handler_allocate; + asio_handler_allocate(0, &h); + BEAST_EXPECT(h.invoked()); + } + + // asio_handler_deallocate + { + test_handler h; + BEAST_EXPECT(! h.invoked()); + using net::asio_handler_deallocate; + asio_handler_deallocate(nullptr, 0, &h); + BEAST_EXPECT(h.invoked()); + } + + // asio_handler_deallocate + { + test_handler h; + BEAST_EXPECT(! h.invoked()); + using net::asio_handler_is_continuation; + asio_handler_is_continuation(&h); + BEAST_EXPECT(h.invoked()); + } + } + + void + testSpecialMembers() + { + { + test_handler h1; + test_handler h2(std::move(h1)); + test_handler h3(h2); + boost::ignore_unused(h3); + } + + { + movable_handler h1; + movable_handler h2(std::move(h1)); + boost::ignore_unused(h2); + } + } + + void + run() override + { + testJavadocs(); + testLegacyHooks(); + testSpecialMembers(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,core,operation_base); + +} // detail +} // beast +} // boost diff --git a/test/beast/core/basic_stream_socket.cpp b/test/beast/core/basic_stream_socket.cpp new file mode 100644 index 00000000..7da6e648 --- /dev/null +++ b/test/beast/core/basic_stream_socket.cpp @@ -0,0 +1,132 @@ +// +// 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 +#include + +namespace boost { +namespace beast { + +class basic_stream_socket_test + : public beast::unit_test::suite +{ +public: + struct read_handler + { + template + void operator()(Args&&...) + { + } + }; + + template + void async_read_line ( + basic_stream_socket& stream, + net::streambuf& buffer, ReadHandler&& handler) + { + stream.expires_after(std::chrono::seconds(30)); + + net::async_read_until(stream, buffer, "\r\n", std::forward(handler)); + } + + void + testJavadocs() + { + BEAST_EXPECT((&basic_stream_socket_test::async_read_line< + net::ip::tcp, net::io_context::executor_type, read_handler>)); + } + + struct other_t + { + }; + + void + testMembers() + { + using tcp = net::ip::tcp; + using ep_t = tcp::endpoint; + using ioc_t = net::io_context; + using ex_t = ioc_t::executor_type; + using stream_t = basic_stream_socket; + + net::io_context ioc; + auto ex = ioc.get_executor(); + + // construction + + { + stream_t{ioc}; + stream_t{ex}; + BOOST_STATIC_ASSERT(! std::is_constructible< + stream_t, other_t>::value); + } + { + stream_t{ioc, tcp::v4()}; + stream_t{ex, tcp::v4()}; + BOOST_STATIC_ASSERT(! std::is_constructible< + stream_t, other_t, tcp>::value); + } + { + stream_t{ioc, ep_t{}}; + stream_t{ex, ep_t{}}; + BOOST_STATIC_ASSERT(! std::is_constructible< + stream_t, other_t, ep_t>::value); + } + { + tcp::socket sock(ioc); + stream_t{ioc, std::move(sock)}; + stream_t{ex, std::move(sock)}; + BOOST_STATIC_ASSERT(! std::is_constructible< + stream_t, other_t, tcp::socket>::value); + } + + // move + + { + stream_t s1(ioc); + stream_t s2(std::move(s1)); + } + { + stream_t s1(ioc); + stream_t s2(ioc); + s2 = std::move(s1); + } + + // converting move + + { + // We don't have any convertible protocol types +#if 0 + basic_stream_socket s1(ioc); + stream_t s2(std::move(s1)); + + stream_t s3 = std::move(s1); +#endif + } + } + + void + run() + { + testJavadocs(); + testMembers(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,core,basic_stream_socket); + +} // beast +} // boost diff --git a/test/beast/experimental/timeout_socket.cpp b/test/beast/core/stream_socket.cpp similarity index 57% rename from test/beast/experimental/timeout_socket.cpp rename to test/beast/core/stream_socket.cpp index 21d6be92..a39d2ba7 100644 --- a/test/beast/experimental/timeout_socket.cpp +++ b/test/beast/core/stream_socket.cpp @@ -8,19 +8,18 @@ // // Test that header file is self-contained. -#include +#include -#include - -#include #include +#include #include +#include #include namespace boost { namespace beast { -class timeout_socket_test +class stream_socket_test : public beast::unit_test::suite , public test::enable_yield_to { @@ -152,18 +151,117 @@ public: server srv(ep, log); { net::io_context ioc; - set_timeout_service_options( - ioc, std::chrono::seconds(1)); - timeout_socket s(ioc); + stream_socket s(ioc); s.next_layer().connect(ep); char buf[32]; + error_code ec; + s.expires_after(std::chrono::seconds(1)); s.async_read_some(net::buffer(buf), - [&](error_code ec, std::size_t n) + [&ec](error_code ec_, std::size_t) { - log << "read_some: " << ec.message() << "\n"; - boost::ignore_unused(ec, n); + ec = ec_; }); ioc.run(); + BEAST_EXPECTS( + ec == error::timeout, ec.message()); + } + } + + void + testConnect() + { + using es_t = + std::array; + { + net::io_context ioc; + stream_socket s(ioc); + beast::async_connect(s, es_t{}, + [](error_code, es_t::value_type) + { + }); + } + { + net::io_context ioc; + stream_socket s(ioc); + beast::async_connect(s, es_t{}, + [](error_code, es_t::value_type) + { + return true; + }, + [](error_code, es_t::value_type) + { + }); + } + { + es_t es; + net::io_context ioc; + stream_socket s(ioc); + beast::async_connect(s, es.begin(), es.end(), + [](error_code, es_t::iterator) + { + }); + } + { + es_t es; + net::io_context ioc; + stream_socket s(ioc); + beast::async_connect(s, es.begin(), es.end(), + [](error_code, es_t::value_type) + { + return true; + }, + [](error_code, es_t::iterator) + { + }); + } + pass(); + } + + void callConnects() + { + using es_t = + std::array; + { + net::io_context ioc; + stream_socket s(ioc); + async_connect(s, es_t{}, + [](error_code, es_t::value_type) + { + }); + } + { + net::io_context ioc; + stream_socket s(ioc); + async_connect(s, es_t{}, + [](error_code, es_t::value_type) + { + return true; + }, + [](error_code, es_t::value_type) + { + }); + } + { + es_t es; + net::io_context ioc; + stream_socket s(ioc); + async_connect(s, es.begin(), es.end(), + [](error_code, es_t::iterator) + { + }); + } + { + es_t es; + net::io_context ioc; + stream_socket s(ioc); + async_connect(s, es.begin(), es.end(), + [](error_code, es_t::value_type) + { + return true; + }, + [](error_code, es_t::iterator) + { + }); } } @@ -171,12 +269,11 @@ public: run() { testAsync(); - - pass(); + testConnect(); } }; -BEAST_DEFINE_TESTSUITE(beast,core,timeout_socket); +BEAST_DEFINE_TESTSUITE(beast,core,stream_socket); } // beast } // boost diff --git a/test/beast/experimental/CMakeLists.txt b/test/beast/experimental/CMakeLists.txt index 14595e19..2397c684 100644 --- a/test/beast/experimental/CMakeLists.txt +++ b/test/beast/experimental/CMakeLists.txt @@ -21,9 +21,6 @@ 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 f73157df..a4d05381 100644 --- a/test/beast/experimental/Jamfile +++ b/test/beast/experimental/Jamfile @@ -13,9 +13,6 @@ 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 deleted file mode 100644 index f27d2939..00000000 --- a/test/beast/experimental/timeout_service.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// 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 - { - net::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_work_guard.cpp b/test/beast/experimental/timeout_work_guard.cpp deleted file mode 100644 index e4d0e21b..00000000 --- a/test/beast/experimental/timeout_work_guard.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// -// 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