diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ed00ba4..7abfe78c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Version 204 + +* Add basic_timeout_stream + +-------------------------------------------------------------------------------- + Version 203 * Update networking refresher doc diff --git a/doc/qbk/00_main.qbk b/doc/qbk/00_main.qbk index eba055ff..3fabfb9a 100644 --- a/doc/qbk/00_main.qbk +++ b/doc/qbk/00_main.qbk @@ -41,6 +41,7 @@ [def __asio_handler_allocate__ [@boost:/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`]] [def __asio_handler_invoke__ [@boost:/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]] +[def __basic_stream_socket__ [@boost:/doc/html/boost_asio/reference/basic_stream_socket.html `basic_stream_socket`]] [def __const_buffer__ [@boost:/doc/html/boost_asio/reference/const_buffer.html `const_buffer`]] [def __deduced__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.automatic_deduction_of_initiating_function_return_type ['DEDUCED]]] [def __executor_work_guard__ [@boost:/doc/html/boost_asio/reference/executor_work_guard.html `net::executor_work_guard`]] @@ -64,6 +65,7 @@ [def __CompletionHandler__ [@boost:/doc/html/boost_asio/reference/CompletionHandler.html ['CompletionHandler]]] [def __CompletionCondition__ [@boost:/doc/html/boost_asio/reference/CompletionCondition.html ['CompletionCondition]]] [def __ConnectCondition__ [@boost:/doc/html/boost_asio/reference/ConnectCondition.html ['ConnectCondition]]] +[def __ConnectHandler__ [@boost:/doc/html/boost_asio/reference/ConnectHandler.html ['ConnectHandler]]] [def __ConstBufferSequence__ [@boost:/doc/html/boost_asio/reference/ConstBufferSequence.html ['ConstBufferSequence]]] [def __EndpointSequence__ [@boost:/doc/html/boost_asio/reference/EndpointSequence.html ['EndpointSequence]]] [def __Executor__ [@boost:/doc/html/boost_asio/reference/Executor1.html ['Executor]]] diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index c6119a11..e6f110ae 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -6,7 +6,7 @@ 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 --> @@ -180,7 +180,7 @@ async_op_base basic_flat_buffer basic_multi_buffer - basic_stream_socket + basic_timeout_stream buffered_read_stream buffers_adaptor buffers_cat_view @@ -209,9 +209,9 @@ static_buffer_base static_string stable_async_op_base - stream_socket string_param string_view + timeout_stream Constants diff --git a/doc/xsl/class_detail.xsl b/doc/xsl/class_detail.xsl index dab37942..d2562cfc 100644 --- a/doc/xsl/class_detail.xsl +++ b/doc/xsl/class_detail.xsl @@ -27,6 +27,9 @@ class __ConnectCondition__ + + class __ConnectHandler__ + class __ConstBufferSequence__ diff --git a/include/boost/beast/core.hpp b/include/boost/beast/core.hpp index a932b997..21b4134a 100644 --- a/include/boost/beast/core.hpp +++ b/include/boost/beast/core.hpp @@ -12,7 +12,7 @@ #include -#include +#include #include #include #include @@ -38,9 +38,9 @@ #include #include #include -#include #include #include +#include #include #endif diff --git a/include/boost/beast/core/basic_stream_socket.hpp b/include/boost/beast/core/basic_stream_socket.hpp deleted file mode 100644 index 4b2cb63b..00000000 --- a/include/boost/beast/core/basic_stream_socket.hpp +++ /dev/null @@ -1,1175 +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_BASIC_STREAM_SOCKET_HPP -#define BOOST_BEAST_CORE_BASIC_STREAM_SOCKET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast { - -/** A stream socket with integrated timeout and bandwidth management. - - This stream socket adapts a `net::basic_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, but with the - ability to use either an executor or an execution context when - constructing the socket. - - The caller is responsible for ensuring that all stream operations, - including the internal timer operations, are running from the same - implicit or explicit strand. When there are multiple threads calling - `net::io_context::run`, the `Executor` template parameter, and the - instance of that executor type passed to the constructor must provide - the following guarantees: - - @li Serial Execution: - Function objects submitted to the executor shall never run - concurrently. - - @li Ordering: - Function objects submitted to the executor from the same thread shall - execute in the order they were submitted. - - If only one thread is calling `net::io_context::run`, the executor - may be the executor used by the I/O context (`net::io_context::executor_type`). - Otherwise, a `net::strand` may be used to meet the requirements. - Note that when using a strand to instantiate the socket, it is not - necessary to also bind each submitted completion handler used in - subsequent operations to the strand, this is taken care of automatically. - - @par Associated Executor - - - @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 "Networking TS enhancement to enable custom I/O executors" - (P1322R0). -*/ -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)(); - -// friend class template declaration in a class template is ignored -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88672 -#if BOOST_WORKAROUND(BOOST_GCC, > 0) -public: -#endif - 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 - - using executor_type = Executor; - Executor - get_executor() const noexcept - { - return ex; - } - }; -#if BOOST_WORKAROUND(BOOST_GCC, > 0) -private: -#endif - - // 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; - - struct read_timeout_handler; - struct write_timeout_handler; - -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/basic_timeout_stream.hpp b/include/boost/beast/core/basic_timeout_stream.hpp new file mode 100644 index 00000000..6084eafb --- /dev/null +++ b/include/boost/beast/core/basic_timeout_stream.hpp @@ -0,0 +1,844 @@ +// +// 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_TIMEOUT_SOCKET_HPP +#define BOOST_BEAST_CORE_BASIC_TIMEOUT_SOCKET_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +//------------------------------------------------------------------------------ + +/** A stream socket with an integrated timeout on reading, writing, and connecting. + + This layered stream wrapper manages a contained `net::basic_stream_socket` + object to provide the following additional features: + + @li A timeout may be specified for each logical asynchronous + operation that performs reading, writing, and/or connecting. + + @li The class template is parameterized on the executor type to be + used for all asynchronous operations. This achieves partial support for + "Networking TS enhancement to enable custom I/O executors" + (P1322R0). + + Objects of this type are used in place of a regular networking socket, + where timeouts on operations are desired. In particular this class + template is designed to be used in place of `net::basic_stream_socket` + or more typically, `net:ip::tcp::socket`. Constructors are provided to + use a particular execution context or executor, subject to temporary + restrictions based on the current implementation of networking. Additional + constructors allow the timeout stream to be constructed from a networking + socket that already exists. + + Although the stream supports multiple concurrent outstanding asynchronous + operations, the stream object itself is not thread-safe. The caller is + responsible for ensuring that the stream is accessed from only one thread + at a time. This includes the times when the stream, and its underlying + socket, is accessed by the networking implementation. To meet these + thread safety requirements, all asynchronous operations must be performed + by the stream within the same implicit strand (only one thread calling `run()` + on the corresponding `net::io_context`) or within the same explicit strand + such as an instance of `net::strand`. + + When using explicit strands, calls to initiating functions may use + `net::bind_handler` with a suitable executor on the completion handler. + Alternatively, the executor may be specified once by passing it as a stream + class template parameter, and providing an instance of the executor upon + construction (if the executor type is not DefaultConstructible). + Regardless of the method chosen, the executor used with the stream must + provide the following guarantees: + + @li Ordering: + Function objects submitted to the executor from the same thread shall + execute in the order they were submitted. + + @li Concurrency: + Function objects submitted to the executor shall never run concurrently + with each other. + + The executor type `net::strand` meets these requirements. Use of a + strand as the executor in the stream class template offers an additional + notational convenience: the strand does not need to be specified in + each individual initiating function call. + + @par Usage + + 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 any series of one or more direct or indirect calls to the timeout + stream's read, write, or connect functions. + + When a timeout is set and a mixed operation is performed (one that + includes both reads and writes, for example) the timeout applies + to all of the intermediate asynchronous operations used in the + enclosing operation. This allows timeouts to be applied to stream + algorithms which were not written specifically to allow for timeouts, + when those algorithms are passed a timeout stream with a timeout set. + + 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 Examples + + This function reads an HTTP request with a timeout, then sends the + HTTP response with a different timeout. + + @code + void process_http_1 (timeout_stream& stream, net::yield_context yield) + { + flat_buffer buffer; + http::request req; + + // Read the request, with a 15 second timeout + stream.expires_after(std::chrono::seconds(15)); + http::async_read(stream, buffer, req, yield); + + // Calculate the response + http::response res = make_response(req); + + // Send the response, with a 30 second timeout. + stream.expires_after (std::chrono::seconds(30)); + http::async_write (stream, res, yield); + } + @endcode + + The example above could be expressed using a single timeout with a + simple modification. The function that follows first reads an HTTP + request then sends the HTTP response, with a single timeout that + applies to the entire combined operation of reading and writing: + + @code + void process_http_2 (timeout_stream& stream, net::yield_context yield) + { + flat_buffer buffer; + http::request req; + + // Require that the read and write combined take no longer than 30 seconds + stream.expires_after(std::chrono::seconds(30)); + + http::async_read(stream, buffer, req, yield); + + http::response res = make_response(req); + http::async_write (stream, res, yield); + } + @endcode + + Some stream algorithms, such as `ssl::stream::async_handshake` perform + both reads and writes. A timeout set before calling the initiating function + of such composite stream algorithms will apply to the entire composite + operation. For example, a timeout may be set on performing the SSL handshake + thusly: + + @code + void do_ssl_handshake (net::ssl::stream& stream, net::yield_context yield) + { + // Require that the SSL handshake take no longer than 10 seconds + stream.expires_after(std::chrono::seconds(10)); + + stream.async_handshake(net::ssl::stream_base::client, yield); + } + @endcode + + @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. This type defaults to `net::io_context::executor_type`. + + @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 "Networking TS enhancement to enable custom I/O executors" + (P1322R0). +*/ +template< + class Protocol, + class Executor = typename + net::basic_stream_socket::executor_type> +class basic_timeout_stream +#if ! BOOST_BEAST_DOXYGEN + : private detail::timeout_stream_base +#endif +{ + using time_point = typename + std::chrono::steady_clock::time_point; + + static constexpr time_point never() + { + return (time_point::max)(); + } + + static std::size_t constexpr no_limit = + (std::numeric_limits::max)(); + + struct op_state + { + net::steady_timer timer; // for timing out + bool pending = false; // if op is pending + bool closed = false; // if timed out + + explicit + op_state(net::io_context& ioc) + : timer(ioc) + { + } + }; + +// friend class template declaration in a class template is ignored +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88672 +#if BOOST_WORKAROUND(BOOST_GCC, > 0) +public: +#endif + struct impl_type + : std::enable_shared_from_this + { + Executor ex; // must come first + op_state read; + op_state write; + + net::basic_stream_socket socket; + + 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 + }; +#if BOOST_WORKAROUND(BOOST_GCC, > 0) +private: +#endif + + // 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 async_op; + + template + friend class detail::timeout_stream_connect_op; + + template + friend class basic_timeout_stream; + + struct timeout_handler; + +public: + /// The type of the executor associated with the object. + using executor_type = Executor; + + /// The type of the next layer. + using next_layer_type = net::basic_stream_socket; + + /// The protocol type. + using protocol_type = Protocol; + + /// The endpoint type. + using endpoint_type = typename Protocol::endpoint; + + /** Destructor + + This function destroys the socket, cancelling any outstanding + asynchronous operations associated with the socket as if by + calling cancel. + */ + ~basic_timeout_stream(); + + /** Construct the stream without opening it. + + This constructor creates a timeout stream. The underlying 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 will use to dispatch + handlers for any asynchronous operations performed on the socket. + Currently, the only supported type for `ctx` 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_timeout_stream(ExecutionContext& ctx); + + /** Construct the stream without opening it. + + This constructor creates a timeout stream. The underlying socket needs + to be opened and then connected or accepted before data can be sent or + received on it. + + @param ex The executor which stream will use to dispatch handlers for + any asynchronous operations performed on the underlying socket. + Currently, only executors that return a `net::io_context&` from + `ex.context()` are supported. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + explicit + basic_timeout_stream(executor_type const& ex); + + /** Construct the stream with an existing socket. + + This constructor creates a timeout stream by taking ownership of an + already existing socket. The executor will be the same as the executor + of the provided socket. + + @param socket The socket object to construct with, which becomes the + next layer of the timeout stream. Ownership of this socket is + transferred by move. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + explicit + basic_timeout_stream( + net::basic_stream_socket&& socket); + + /** Construct the stream with an executor and existing socket. + + This constructor creates a timeout stream by taking ownership of an + already existing socket. + + @param ex The executor which stream will use to dispatch handlers for + any asynchronous operations performed on the underlying socket. + Currently, only executors that return a `net::io_context&` from + `ex.context()` are supported. + + @param socket The socket object to construct with, which becomes the + next layer of the timeout stream. Ownership of this socket is + transferred by move. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + basic_timeout_stream( + executor_type const& ex, + net::basic_stream_socket&& socket); + + /** Move-construct a stream from another stream + + This constructor moves a stream from one object to another. + + The behavior of moving a stream while asynchronous operations + are outstanding is undefined. + + @param other The other object from which the move will occur. + + @note Following the move, the moved-from object is in a newly + constructed state. + */ + basic_timeout_stream(basic_timeout_stream&& other); + + /** Move-assign a stream from another stream + + 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_timeout_stream object from which the + move will occur. + + @note Following the move, the moved-from object is a newly + constructed state. + */ + basic_timeout_stream& operator=(basic_timeout_stream&& 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 underlying socket. + + 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 underlying socket. + + 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; + } + + /** 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(); + + /// Cancel all asynchronous operations associated with the socket. + void + cancel(); + + /** 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(); + + //-------------------------------------------------------------------------- + + /** Start an asynchronous connect. + + This function is used to asynchronously connect a socket to the + specified remote endpoint. The function call always returns immediately. + + The underlying socket is automatically opened if it is not already open. + If the connect fails, and the socket was automatically opened, the socket + is not returned to the closed state. + + @param ep The remote endpoint to which the underlying socket will be + connected. Copies will be made of the endpoint object as required. + + @param handler The handler to be called when the operation completes. + The implementation will take ownership of the handler by move construction. + The handler must be invocable with this signature: + @code + void handler( + error_code ec // Result of operation + ); + @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::post()`. + */ + template + BOOST_ASIO_INITFN_RESULT_TYPE(ConnectHandler, + void(error_code)) + async_connect( + endpoint_type ep, + ConnectHandler&& handler); + + /** Start an asynchronous read. + + This function is used to asynchronously read data from the stream. + The function call always returns immediately. + + @param buffers A range of zero or more buffers to read stream data into. + 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 operation completes. + The implementation will take ownership of the handler by move construction. + The handler must be invocable with this signature: + @code + void handler( + 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::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. + The function call always returns immediately. + + @param buffers A range of zero or more buffers to be written to the stream. + 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 operation completes. + The implementation will take ownership of the handler by move construction. + The handler must be invocable with this signature: + @code + void handler( + 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::post()`. + */ + template + BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, + void(error_code, std::size_t)) + async_write_some( + ConstBufferSequence const& buffers, + WriteHandler&& 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 stream The @ref beast::basic_timeout_stream to be connected. If the + underlying socket is already open, it will be closed. + + @param endpoints A sequence of endpoints. This this object must meet + the requirements of EndpointSequence. + + @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()`. +*/ +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_timeout_stream& stream, + 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 stream The @ref beast::basic_timeout_stream to be connected. If the + underlying socket is already open, it will be closed. + + @param endpoints A sequence of endpoints. This this object must meet + the requirements of EndpointSequence. + + @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 +*/ +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_timeout_stream& stream, + 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 stream The @ref beast::basic_timeout_stream to be connected. If the + underlying 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()`. +*/ +template< + class Protocol, class Executor, + class Iterator, + class IteratorConnectHandler> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (error_code, Iterator)) +async_connect( + basic_timeout_stream& stream, + 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 stream The @ref beast::basic_timeout_stream to be connected. If the + underlying 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()`. +*/ +template< + class Protocol, class Executor, + class Iterator, + class ConnectCondition, + class IteratorConnectHandler> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (error_code, Iterator)) +async_connect( + basic_timeout_stream& stream, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + IteratorConnectHandler&& handler); + +} // beast +} // boost + +#include + +#endif diff --git a/include/boost/beast/core/detail/stream_socket_base.hpp b/include/boost/beast/core/detail/timeout_stream_base.hpp similarity index 67% rename from include/boost/beast/core/detail/stream_socket_base.hpp rename to include/boost/beast/core/detail/timeout_stream_base.hpp index 5ee9df2f..9e2b22d2 100644 --- a/include/boost/beast/core/detail/stream_socket_base.hpp +++ b/include/boost/beast/core/detail/timeout_stream_base.hpp @@ -7,8 +7,8 @@ // 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 +#ifndef BOOST_BEAST_CORE_DETAIL_TIMEOUT_STREAM_BASE_HPP +#define BOOST_BEAST_CORE_DETAIL_TIMEOUT_STREAM_BASE_HPP #include #include @@ -18,9 +18,20 @@ namespace beast { namespace detail { template -class stream_socket_connect_op; +class timeout_stream_connect_op; -class stream_socket_base +struct any_endpoint +{ + template + bool + operator()( + Error const&, Endpoint const&) const noexcept + { + return true; + } +}; + +class timeout_stream_base { protected: class pending_guard @@ -43,9 +54,11 @@ protected: b_ = true; } - pending_guard(pending_guard&& other) noexcept + pending_guard( + pending_guard&& other) noexcept : b_(other.b_) - , clear_(boost::exchange(other.clear_, false)) + , clear_(boost::exchange( + other.clear_, false)) { } diff --git a/include/boost/beast/core/impl/basic_stream_socket.hpp b/include/boost/beast/core/impl/basic_stream_socket.hpp deleted file mode 100644 index 806de4bd..00000000 --- a/include/boost/beast/core/impl/basic_stream_socket.hpp +++ /dev/null @@ -1,1024 +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_BASIC_STREAM_SOCKET_HPP -#define BOOST_BEAST_CORE_IMPL_BASIC_STREAM_SOCKET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast { - -template -struct basic_stream_socket< - Protocol, Executor>::read_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; - } - } -}; - -template -struct basic_stream_socket< - Protocol, Executor>::write_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; - } - } -}; - -/* - 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 async_op_base - , public boost::asio::coroutine -{ - typename basic_stream_socket< - Protocol, Executor>::impl_type& impl_; - pending_guard pg_; - Buffers b_; - -public: - template - read_op( - basic_stream_socket& s, - Buffers const& b, - Handler_&& h) - : async_op_base( - std::forward(h), s.get_executor()) - , impl_(*s.impl_) - , pg_(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? - impl_.read_timer.async_wait( - net::bind_executor( - this->get_executor(), - read_timeout_handler{ - impl_.shared_from_this()})); - - impl_.maybe_kick(); - - // check if the balance is zero - if(impl_.read_remain == 0) - { - // wait until the next time slice - ++impl_.waiting; - BOOST_ASIO_CORO_YIELD - 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 - impl_.on_timer(); - BOOST_ASSERT(impl_.read_remain > 0); - } - - // we always use buffers_prefix, - // to reduce template instantiations. - BOOST_ASSERT(impl_.read_remain > 0); - BOOST_ASIO_CORO_YIELD - impl_.socket.async_read_some( - beast::buffers_prefix( - impl_.read_remain, b_), - std::move(*this)); - - if(impl_.read_remain != no_limit) - { - // adjust balance - BOOST_ASSERT( - bytes_transferred <= impl_.read_remain); - impl_.read_remain -= bytes_transferred; - } - - { - // try cancelling timer - auto const n = - impl_.read_timer.cancel(); - - if(impl_.read_closed) - { - // timeout handler already invoked - BOOST_ASSERT(n == 0); - ec = beast::error::timeout; - impl_.read_closed = false; - } - else if(n == 0) - { - // timeout handler already queued - ec = beast::error::timeout; - - impl_.close(); - impl_.read_closed = true; - } - else - { - // timeout was canceled - BOOST_ASSERT(n == 1); - } - } - - upcall: - pg_.reset(); - this->invoke(ec, bytes_transferred); - } - } -}; - -//------------------------------------------------------------------------------ - -template -template -class basic_stream_socket::write_op - : public async_op_base - , public boost::asio::coroutine -{ - typename basic_stream_socket< - Protocol, Executor>::impl_type& impl_; - pending_guard pg_; - Buffers b_; - -public: - template - write_op( - basic_stream_socket& s, - Buffers const& b, - Handler_&& h) - : async_op_base( - std::forward(h), s.get_executor()) - , impl_(*s.impl_) - , pg_(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? - impl_.write_timer.async_wait( - net::bind_executor( - this->get_executor(), - write_timeout_handler{ - impl_.shared_from_this()})); - - impl_.maybe_kick(); - - // check if the balance is zero - if(impl_.write_remain == 0) - { - // wait until the next time slice - ++impl_.waiting; - BOOST_ASIO_CORO_YIELD - 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 - impl_.on_timer(); - BOOST_ASSERT(impl_.write_remain > 0); - } - - // we always use buffers_prefix, - // to reduce template instantiations. - BOOST_ASSERT(impl_.write_remain > 0); - BOOST_ASIO_CORO_YIELD - impl_.socket.async_write_some( - beast::buffers_prefix( - impl_.write_remain, b_), - std::move(*this)); - - if(impl_.write_remain != no_limit) - { - // adjust balance - BOOST_ASSERT( - bytes_transferred <= impl_.write_remain); - impl_.write_remain -= bytes_transferred; - } - - { - // try cancelling timer - auto const n = - impl_.write_timer.cancel(); - - if(impl_.write_closed) - { - // timeout handler already invoked - BOOST_ASSERT(n == 0); - ec = beast::error::timeout; - impl_.write_closed = false; - } - else if(n == 0) - { - // timeout handler already queued - ec = beast::error::timeout; - - impl_.close(); - impl_.write_closed = true; - } - else - { - // timeout was canceled - BOOST_ASSERT(n == 1); - } - } - - upcall: - pg_.reset(); - this->invoke(ec, bytes_transferred); - } - } -}; - -//------------------------------------------------------------------------------ - -namespace detail { - -template< - class Protocol, class Executor, class Handler> -class stream_socket_connect_op - : public async_op_base -{ - using stream_type = - beast::basic_stream_socket; - using timeout_handler = - typename stream_type::write_timeout_handler; - - typename basic_stream_socket< - Protocol, Executor>::impl_type& impl_; - typename stream_type::pending_guard pg0_; - typename stream_type::pending_guard pg1_; - -public: - template< - class Endpoints, class Condition, - class Handler_> - stream_socket_connect_op( - stream_type& s, - Endpoints const& eps, - Condition cond, - Handler_&& h) - : async_op_base( - std::forward(h), s.get_executor()) - , impl_(*s.impl_) - , pg0_(impl_.read_pending) - , pg1_(impl_.write_pending) - { - // must come first - // VFALCO TODO what about the handler's allocator? - impl_.write_timer.async_wait( - net::bind_executor( - this->get_executor(), - timeout_handler{ - impl_.shared_from_this()})); - - net::async_connect(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) - : async_op_base( - std::forward(h), s.get_executor()) - , impl_(*s.impl_) - , pg0_(impl_.read_pending) - , pg1_(impl_.write_pending) - { - // must come first - impl_.write_timer.async_wait( - net::bind_executor( - this->get_executor(), - timeout_handler{ - impl_.shared_from_this()})); - - net::async_connect(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 = - impl_.write_timer.cancel(); - - if(impl_.write_closed) - { - // timeout handler already invoked - BOOST_ASSERT(n == 0); - ec = beast::error::timeout; - impl_.write_closed = false; - } - else if(n == 0) - { - // timeout handler already queued - ec = beast::error::timeout; - - impl_.close(); - impl_.write_closed = true; - } - else - { - // timeout was canceled - BOOST_ASSERT(n == 1); - } - - pg0_.reset(); - pg1_.reset(); - this->invoke(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/impl/basic_timeout_stream.hpp b/include/boost/beast/core/impl/basic_timeout_stream.hpp new file mode 100644 index 00000000..e7028b92 --- /dev/null +++ b/include/boost/beast/core/impl/basic_timeout_stream.hpp @@ -0,0 +1,699 @@ +// +// 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_TIMEOUT_STREAM_HPP +#define BOOST_BEAST_CORE_IMPL_BASIC_TIMEOUT_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +template +struct basic_timeout_stream< + Protocol, Executor>::timeout_handler +{ + op_state& state; + std::shared_ptr impl; + + void + operator()(error_code ec) + { + // timer canceled + if(ec == net::error::operation_aborted) + return; + + BOOST_ASSERT(! ec); + + if(! state.closed) + { + // timeout + impl->close(); + state.closed = true; + } + else + { + // late completion + state.closed = false; + } + } +}; + +//------------------------------------------------------------------------------ + +/* + 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_timeout_stream::async_op + : public async_op_base + , public boost::asio::coroutine +{ + std::shared_ptr impl_; + pending_guard pg_; + Buffers b_; + + op_state& + state(std::true_type) + { + return impl_->read; + } + + op_state& + state(std::false_type) + { + return impl_->write; + } + + op_state& + state() + { + return state( + std::integral_constant{}); + } + + void + async_perform(std::true_type) + { + impl_->socket.async_read_some( + b_, std::move(*this)); + } + + void + async_perform(std::false_type) + { + impl_->socket.async_write_some( + b_, std::move(*this)); + } + +public: + template + async_op( + basic_timeout_stream& s, + Buffers const& b, + Handler_&& h) + : async_op_base( + std::forward(h), s.get_executor()) + , impl_(s.impl_) + , pg_(state().pending) + , b_(b) + { + (*this)({}); + } + + void + operator()( + error_code ec, + std::size_t bytes_transferred = 0) + { + BOOST_ASIO_CORO_REENTER(*this) + { + // VFALCO TODO handle buffer size == 0 + + // must come first + state().timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{state(), + impl_->shared_from_this()})); + + BOOST_ASIO_CORO_YIELD + async_perform( + std::integral_constant{}); + + // try cancelling timer + auto const n = + state().timer.cancel(); + + if(state().closed) + { + // timeout handler already invoked + BOOST_ASSERT(n == 0); + ec = beast::error::timeout; + state().closed = false; + } + else if(n == 0) + { + // timeout handler already queued + ec = beast::error::timeout; + + impl_->close(); + state().closed = true; + } + else + { + // timeout was canceled + BOOST_ASSERT(n == 1); + } + + pg_.reset(); + this->invoke(ec, bytes_transferred); + } + } +}; + +//------------------------------------------------------------------------------ + +namespace detail { + +template< + class Protocol, class Executor, class Handler> +class timeout_stream_connect_op + : public async_op_base +{ + using stream_type = + beast::basic_timeout_stream; + + using timeout_handler = + typename stream_type::timeout_handler; + + std::shared_ptr::impl_type> impl_; + typename stream_type::pending_guard pg0_; + typename stream_type::pending_guard pg1_; + +public: + template< + class Endpoints, class Condition, + class Handler_> + timeout_stream_connect_op( + stream_type& s, + Endpoints const& eps, + Condition cond, + Handler_&& h) + : async_op_base( + std::forward(h), s.get_executor()) + , impl_(s.impl_) + , pg0_(impl_->read.pending) + , pg1_(impl_->write.pending) + { + // must come first + // VFALCO TODO what about the handler's allocator? + impl_->write.timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{impl_->write, + impl_->shared_from_this()})); + + net::async_connect(impl_->socket, + eps, cond, std::move(*this)); + // *this is now moved-from + } + + template< + class Iterator, class Condition, + class Handler_> + timeout_stream_connect_op( + stream_type& s, + Iterator begin, Iterator end, + Condition cond, + Handler_&& h) + : async_op_base( + std::forward(h), s.get_executor()) + , impl_(s.impl_) + , pg0_(impl_->read.pending) + , pg1_(impl_->write.pending) + { + // must come first + impl_->write.timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{impl_->write, + impl_->shared_from_this()})); + + net::async_connect(impl_->socket, + begin, end, cond, std::move(*this)); + // *this is now moved-from + } + + template + timeout_stream_connect_op( + stream_type& s, + typename stream_type::endpoint_type ep, + Handler_&& h) + : async_op_base( + std::forward(h), s.get_executor()) + , impl_(s.impl_) + , pg0_(impl_->read.pending) + , pg1_(impl_->write.pending) + { + // must come first + impl_->write.timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{impl_->write, + impl_->shared_from_this()})); + + impl_->socket.async_connect( + ep, std::move(*this)); + // *this is now moved-from + } + + template + void + operator()(error_code ec, Args&&... args) + { + // try to cancel the timer + auto const n = + impl_->write.timer.cancel(); + + if(impl_->write.closed) + { + // timeout handler already invoked + BOOST_ASSERT(n == 0); + ec = beast::error::timeout; + impl_->write.closed = false; + } + else if(n == 0) + { + // timeout handler already queued + ec = beast::error::timeout; + + impl_->close(); + impl_->write.closed = true; + } + else + { + // timeout was canceled + BOOST_ASSERT(n == 1); + } + + pg0_.reset(); + pg1_.reset(); + this->invoke(ec, std::forward(args)...); + } +}; + +} // detail + +//------------------------------------------------------------------------------ + +template +template +basic_timeout_stream:: +impl_type:: +impl_type( + Executor const& ex_, + Args&&... args) + : ex(ex_) + , read(ex_.context()) + , write(ex_.context()) + , socket(std::forward(args)...) +{ + reset(); +} + +template +auto +basic_timeout_stream:: +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); + read = std::move(other.read); + write = std::move(other.write); + + return *this; +} + +template +void +basic_timeout_stream:: +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_timeout_stream:: +impl_type:: +close() +{ + socket.close(); + + // have to let the read/write ops cancel the timer, + // otherwise we will get error::timeout on close when + // we actually want net::error::operation_aborted. + // + //read.timer.cancel(); + //write.timer.cancel(); +} + +//------------------------------------------------------------------------------ + +template +basic_timeout_stream:: +~basic_timeout_stream() +{ + // the shared object can outlive *this, + // cancel any operations so the shared + // object is destroyed as soon as possible. + impl_->close(); +} + +template +template +basic_timeout_stream:: +basic_timeout_stream(ExecutionContext& ctx) + : impl_(std::make_shared( + ctx.get_executor(), + ctx)) +{ + // Restriction is necessary until Asio fully supports P1322R0 + static_assert( + std::is_same::value, + "Only net::io_context is currently supported for ExecutionContext"); +} + +template +basic_timeout_stream:: +basic_timeout_stream(executor_type const& ex) + : impl_(std::make_shared( + ex, ex.context())) +{ +} + +template +basic_timeout_stream:: +basic_timeout_stream( + net::basic_stream_socket&& socket) + : impl_(std::make_shared( + socket.get_executor(), std::move(socket))) +{ +} + +template +basic_timeout_stream:: +basic_timeout_stream( + executor_type const& ex, + net::basic_stream_socket&& socket) + : impl_(std::make_shared( + ex, std::move(socket))) +{ + // Restriction is necessary until Asio fully supports P1322R0 + if(ex.context().get_executor() != socket.get_executor()) + throw std::invalid_argument( + "basic_timeout_stream currently requires ctx.get_executor() == socket.get_executor()"); +} + +template +basic_timeout_stream:: +basic_timeout_stream(basic_timeout_stream&& 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_timeout_stream:: +operator=(basic_timeout_stream&& other) -> + basic_timeout_stream& +{ + // 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 +void +basic_timeout_stream:: +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_timeout_stream:: +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_timeout_stream:: +expires_never() +{ + impl_->reset(); +} + +template +void +basic_timeout_stream:: +cancel() +{ + error_code ec; + impl_->socket.cancel(ec); +} + +template +void +basic_timeout_stream:: +close() +{ + impl_->close(); +} + +template +template +BOOST_ASIO_INITFN_RESULT_TYPE(ConnectHandler, + void(error_code)) +basic_timeout_stream:: +async_connect( + endpoint_type ep, + ConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT( + ConnectHandler, void(error_code)); + detail::timeout_stream_connect_op< + Protocol, Executor, BOOST_ASIO_HANDLER_TYPE( + ConnectHandler, void(error_code))>(*this, + ep, std::forward(handler)); + return init.result.get(); +} + +template +template +BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, + void(error_code, std::size_t)) +basic_timeout_stream:: +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( + *this, buffers, std::forward(handler)); + return init.result.get(); +} + +template +template +BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, + void(error_code, std::size_t)) +basic_timeout_stream:: +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( + *this, buffers, std::forward(handler)); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +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_stream& stream, + EndpointSequence const& endpoints, + RangeConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, + void(error_code, typename Protocol::endpoint)); + detail::timeout_stream_connect_op( + stream, 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_stream& stream, + EndpointSequence const& endpoints, + ConnectCondition connect_condition, + RangeConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, + void(error_code, typename Protocol::endpoint)); + detail::timeout_stream_connect_op( + stream, 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_timeout_stream& stream, + Iterator begin, Iterator end, + IteratorConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, + void(error_code, Iterator)); + detail::timeout_stream_connect_op( + stream, 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_timeout_stream& stream, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + IteratorConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, + void(error_code, Iterator)); + detail::timeout_stream_connect_op( + stream, 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/timeout_stream.hpp similarity index 76% rename from include/boost/beast/core/stream_socket.hpp rename to include/boost/beast/core/timeout_stream.hpp index e0484a09..859b1aa8 100644 --- a/include/boost/beast/core/stream_socket.hpp +++ b/include/boost/beast/core/timeout_stream.hpp @@ -7,11 +7,11 @@ // Official repository: https://github.com/boostorg/beast // -#ifndef BOOST_BEAST_CORE_STREAM_SOCKET_HPP -#define BOOST_BEAST_CORE_STREAM_SOCKET_HPP +#ifndef BOOST_BEAST_CORE_TIMEOUT_STREAM_HPP +#define BOOST_BEAST_CORE_TIMEOUT_STREAM_HPP #include -#include +#include #include #include @@ -20,7 +20,7 @@ namespace beast { /** A TCP/IP stream socket which supports timeouts and rate limits */ -using stream_socket = basic_stream_socket< +using timeout_stream = basic_timeout_stream< net::ip::tcp, net::io_context::executor_type>; diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt index 73708202..28bf4e0f 100644 --- a/test/beast/core/CMakeLists.txt +++ b/test/beast/core/CMakeLists.txt @@ -28,7 +28,7 @@ add_executable (tests-beast-core _detail_variant.cpp _detail_varint.cpp async_op_base.cpp - basic_stream_socket.cpp + basic_timeout_stream.cpp bind_handler.cpp buffer_traits.cpp buffered_read_stream.cpp @@ -55,9 +55,9 @@ add_executable (tests-beast-core span.cpp static_buffer.cpp static_string.cpp - stream_socket.cpp string.cpp string_param.cpp + timeout_stream.cpp type_traits.cpp ) diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile index f7b927fc..801f060f 100644 --- a/test/beast/core/Jamfile +++ b/test/beast/core/Jamfile @@ -17,7 +17,7 @@ local SOURCES = _detail_variant.cpp _detail_varint.cpp async_op_base.cpp - basic_stream_socket.cpp + basic_timeout_stream.cpp bind_handler.cpp buffer_traits.cpp buffered_read_stream.cpp @@ -44,9 +44,9 @@ local SOURCES = span.cpp static_buffer.cpp static_string.cpp - stream_socket.cpp string.cpp string_param.cpp + timeout_stream.cpp type_traits.cpp ; diff --git a/test/beast/core/basic_stream_socket.cpp b/test/beast/core/basic_stream_socket.cpp deleted file mode 100644 index 7da6e648..00000000 --- a/test/beast/core/basic_stream_socket.cpp +++ /dev/null @@ -1,132 +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 -// - -// 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/core/basic_timeout_stream.cpp b/test/beast/core/basic_timeout_stream.cpp new file mode 100644 index 00000000..034fdcef --- /dev/null +++ b/test/beast/core/basic_timeout_stream.cpp @@ -0,0 +1,932 @@ +// +// 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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +class basic_timeout_stream_test + : public beast::unit_test::suite +{ +public: + //-------------------------------------------------------------------------- + + struct socket_pair + { + net::io_context ioc1; + net::ip::tcp::socket s1; + + net::io_context ioc2; + net::ip::tcp::socket s2; + + socket_pair() + : s1(ioc1) + , s2(ioc2) + { + net::io_context ioc; + net::ip::tcp::acceptor a(ioc); + net::ip::tcp::endpoint ep( + net::ip::make_address_v4("127.0.0.1"), 0); + a.open(ep.protocol()); + a.set_option( + net::socket_base::reuse_address(true)); + a.bind(ep); + a.listen(1); + a.async_accept(s2, + [](error_code ec) + { + #if 0 + if(ec == net::error::operation_aborted) + return; + if(ec) + BOOST_THROW_EXCEPTION( + system_error{ec}); + #endif + }); + s1.async_connect(a.local_endpoint(), + [](error_code ec) + { + if(ec) + BOOST_THROW_EXCEPTION( + system_error{ec}); + }); + for(;;) + if( + ioc.poll() + + ioc1.poll() + + ioc2.poll() == 0) + break; + BOOST_ASSERT(s1.is_open()); + #if 0 + BOOST_ASSERT(s2.is_open()); // VFALCO Fails on Travis for some reason + BOOST_ASSERT( + s1.remote_endpoint() == + s2.local_endpoint()); + BOOST_ASSERT( + s2.remote_endpoint() == + s1.local_endpoint()); + #endif + } + }; + + //-------------------------------------------------------------------------- + + class server + { + string_view s_; + std::ostream& log_; + net::io_context ioc_; + net::ip::tcp::acceptor acceptor_; + net::ip::tcp::socket socket_; + std::thread t_; + + void + fail(error_code ec, string_view what) + { + if(ec != net::error::operation_aborted) + log_ << what << ": " << ec.message() << "\n"; + } + + public: + server( + string_view s, + net::ip::tcp::endpoint ep, + std::ostream& log) + : s_(s) + , log_(log) + , ioc_(1) + , acceptor_(ioc_) + , socket_(ioc_) + { + boost::system::error_code ec; + + acceptor_.open(ep.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + acceptor_.set_option( + net::socket_base::reuse_address(true), ec); + if(ec) + { + fail(ec, "set_option"); + return; + } + + acceptor_.bind(ep, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + acceptor_.listen( + net::socket_base::max_listen_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + + acceptor_.async_accept(socket_, + [this](error_code ec) + { + this->on_accept(ec); + }); + + t_ = std::thread( + [this] + { + ioc_.run(); + }); + } + + ~server() + { + ioc_.stop(); + t_.join(); + } + + net::ip::tcp::endpoint + local_endpoint() const noexcept + { + return acceptor_.local_endpoint(); + } + + private: + class session + : public std::enable_shared_from_this + { + string_view s_; + net::ip::tcp::socket socket_; + + public: + session( + string_view s, + net::ip::tcp::socket sock, + std::ostream&) + : s_(s) + , socket_(std::move(sock)) + { + } + + void + run() + { + if(s_.empty()) + socket_.async_wait( + net::socket_base::wait_read, + std::bind( + &session::on_read, + shared_from_this(), + std::placeholders::_1)); + else + net::async_write( + socket_, + net::const_buffer(s_.data(), s_.size()), + std::bind( + &session::on_write, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } + + protected: + void + on_read(error_code ec) + { + boost::ignore_unused(ec); + } + + void + on_write(error_code, std::size_t) + { + } + }; + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + if(ec) + fail(ec, "accept"); + else + std::make_shared( + s_, std::move(socket_), log_)->run(); + acceptor_.async_accept(socket_, + [this](error_code ec) + { + this->on_accept(ec); + }); + } + }; + + //-------------------------------------------------------------------------- + + void + testStrand() + { + { + using strand_type = net::io_context::strand; + net::io_context ioc; + strand_type st(ioc); + basic_timeout_stream< + net::ip::tcp, strand_type> s(st); + BEAST_EXPECT(s.get_executor() == st); + } + + #if 0 + // VFALCO This is disallowed until Asio implements P1322R0 + { + using strand_type = net::strand< + net::io_context::executor_type>; + net::io_context ioc; + strand_type st(ioc.get_executor()); + basic_timeout_stream< + net::ip::tcp, strand_type> s(st); + BEAST_EXPECT(s.get_executor() == st); + } + #endif + } + + struct other_t + { + }; + + void + testMembers() + { + using tcp = net::ip::tcp; + using stream_t = basic_timeout_stream; + + net::io_context ioc; + auto ex = ioc.get_executor(); + + // construction + + BOOST_STATIC_ASSERT(! std::is_constructible< + stream_t, other_t>::value); + + BOOST_STATIC_ASSERT(! std::is_constructible< + stream_t, other_t, tcp::socket>::value); + + { + stream_t s(ioc); + } + + { + stream_t s(ex); + } + + { + stream_t s((tcp::socket(ioc))); + } + + { + stream_t s(ex, tcp::socket(ioc)); + } + + { + net::io_context ioc2; + try + { + // mismatched execution contexts + stream_t s( + ioc2.get_executor(), + tcp::socket(ioc)); + fail("mismatched execution context", __FILE__, __LINE__); + } + catch(std::invalid_argument const&) + { + pass(); + } + } + + // move + + { + stream_t s1(ioc); + stream_t s2(std::move(s1)); + } + + // assign + + { + stream_t s1(ioc); + stream_t s2(ioc); + s2 = std::move(s1); + } + + // get_executor + + { + stream_t s(ioc); + BEAST_EXPECT( + s.get_executor() == ioc.get_executor()); + } + + // layers + + { + net::socket_base::keep_alive opt; + tcp::socket sock(ioc); + sock.open(tcp::v4()); + sock.get_option(opt); + BEAST_EXPECT(! opt.value()); + stream_t s(ioc); + s.next_layer().open(tcp::v4()); + s.next_layer().get_option(opt); + BEAST_EXPECT(! opt.value()); + opt = true; + sock.set_option(opt); + opt = false; + BEAST_EXPECT(! opt.value()); + s = stream_t(std::move(sock)); + static_cast(s).next_layer().get_option(opt); + BEAST_EXPECT(opt.value()); + } + } + + //-------------------------------------------------------------------------- + + struct match + { + suite& suite_; + error_code ec_; + std::size_t n_; + + match(suite& s, error_code ec, std::size_t n) + : suite_(s) + , ec_(ec) + , n_(n) + { + } + + match(match&& other) + : suite_(other.suite_) + , ec_(other.ec_) + , n_(boost::exchange(other.n_, + (std::numeric_limits::max)())) + { + } + + ~match() + { + suite_.BEAST_EXPECT( + n_ == (std::numeric_limits::max)()); + } + + void + operator()(error_code ec, std::size_t n) + { + suite_.expect(ec == ec_, ec.message(), __FILE__, __LINE__); + suite_.BEAST_EXPECT(n == n_); + n_ = (std::numeric_limits::max)(); + } + }; + + void + testRead() + { + using tcp = net::ip::tcp; + using stream_t = basic_timeout_stream; + + char buf[4]; + std::memset(buf, 0, 4); + net::mutable_buffer mb(buf, sizeof(buf)); + auto const ep = net::ip::tcp::endpoint( + net::ip::make_address("127.0.0.1"), 0); + + // success + { + server srv("*", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.async_read_some(mb, match{*this, {}, 1}); + ioc.run_for(std::chrono::seconds(1)); + } + + // success, with timeout + { + server srv("*", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.expires_after(std::chrono::milliseconds(100)); + s.async_read_some(mb, match{*this, {}, 1}); + ioc.run_for(std::chrono::seconds(1)); + s.expires_never(); + ioc.run(); + } + + // close + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.async_read_some(mb, match{*this, + net::error::operation_aborted, 0}); + { + error_code ec; + s.next_layer().shutdown( + net::socket_base::shutdown_both, + ec); + } + s.close(); + ioc.run_for(std::chrono::seconds(1)); + } + + // cancel + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.async_read_some(mb, match{*this, + net::error::operation_aborted, 0}); + ioc.run_for(std::chrono::milliseconds(100)); + s.cancel(); + ioc.run_for(std::chrono::seconds(1)); + } + + // immediate timeout + { + server srv("*", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.expires_after(std::chrono::seconds(-1)); + s.async_read_some(mb, + [&](error_code ec, std::size_t n) + { + #if 0 + // Unreliable on epoll impls + BEAST_EXPECT( + (ec == error::timeout && n == 0) || + (! ec && n == 1)); + #else + boost::ignore_unused(ec, n); + pass(); + #endif + }); + ioc.run_for(std::chrono::seconds(1)); + } + + // fail, with timeout + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.expires_after(std::chrono::milliseconds(100)); + s.async_read_some(mb, + match{*this, error::timeout, 0}); + ioc.run_for(std::chrono::seconds(1)); + } + + // success, with timeout + { + server srv("*", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.expires_at( + std::chrono::steady_clock::now() + + std::chrono::milliseconds(100)); + s.async_read_some(mb, + match{*this, {}, 1}); + ioc.run_for(std::chrono::seconds(1)); + } + + // abandoned ops + { + server srv("*", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.async_read_some(mb, [&](error_code, std::size_t){}); + } + { + server srv("*", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.expires_after(std::chrono::seconds(1)); + s.async_read_some(mb, [&](error_code, std::size_t){}); + } + + // edge case: + // timer completion becomes queued before + // the I/O completion handler is invoked + // VFALCO Fails on OSX Travis +#if 0 + { + socket_pair p; + bool invoked = false; + stream_t s(std::move(p.s1)); + s.expires_after(std::chrono::seconds(0)); + s.async_read_some(mb, + [&](error_code ec, std::size_t) + { + invoked = true; + BEAST_EXPECTS(ec == error::timeout, + ec.message()); + }); + p.s2.async_write_some( + net::const_buffer("*", 1), + [&](error_code ec, std::size_t n) + { + boost::ignore_unused(ec, n); + }); + p.ioc1.run(); + p.ioc1.restart(); + p.ioc2.run(); + p.ioc2.restart(); + p.ioc1.run(); + BEAST_EXPECT(invoked); + } +#endif + } + + void + testWrite() + { + using tcp = net::ip::tcp; + using stream_t = basic_timeout_stream; + + char buf[4]; + std::memset(buf, 0, 4); + net::mutable_buffer mb(buf, sizeof(buf)); + auto const ep = net::ip::tcp::endpoint( + net::ip::make_address("127.0.0.1"), 0); + + // write + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.async_write_some(mb, + match{*this, {}, mb.size()}); + { + error_code ec; + s.next_layer().shutdown( + net::socket_base::shutdown_both, + ec); + } + s.close(); + ioc.run(); + } + + // write abandoned + { + server srv("*", ep, log); + net::io_context ioc; + stream_t s(ioc); + s.next_layer().connect(srv.local_endpoint()); + s.async_write_some(mb, [&](error_code, std::size_t){}); + } + } + + void + testConnect() + { + using tcp = net::ip::tcp; + using stream_t = basic_timeout_stream; + + auto const ep = net::ip::tcp::endpoint( + net::ip::make_address("127.0.0.1"), 0); + + // overload 1 + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + bool invoked = false; + std::array epa{{ + srv.local_endpoint()}}; + beast::async_connect(s, epa, + [&](error_code ec, tcp::endpoint) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run_for(std::chrono::seconds(1)); + BEAST_EXPECT(invoked); + } + + // overload 2 + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + bool invoked = false; + std::array epa{{ + srv.local_endpoint()}}; + beast::async_connect(s, epa, + [](error_code, tcp::endpoint) + { + return true; + }, + [&](error_code ec, tcp::endpoint) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run_for(std::chrono::seconds(1)); + BEAST_EXPECT(invoked); + } + + // overload 3 + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + bool invoked = false; + std::array epa{{ + srv.local_endpoint()}}; + using iter_type = + std::array::const_iterator; + beast::async_connect(s, epa.begin(), epa.end(), + [&](error_code ec, iter_type) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run_for(std::chrono::seconds(1)); + BEAST_EXPECT(invoked); + } + + // overload 4 + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + bool invoked = false; + std::array epa{{ + srv.local_endpoint()}}; + using iter_type = + std::array::const_iterator; + beast::async_connect(s, epa.begin(), epa.end(), + [](error_code, tcp::endpoint) + { + return true; + }, + [&](error_code ec, iter_type) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run_for(std::chrono::seconds(1)); + BEAST_EXPECT(invoked); + } + + // success + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + bool invoked = false; + std::array epa{{ + srv.local_endpoint()}}; + beast::async_connect(s, epa, + [&](error_code ec, tcp::endpoint) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run_for(std::chrono::seconds(1)); + BEAST_EXPECT(invoked); + } + + // success, with timeout + { + server srv("", ep, log); + net::io_context ioc; + stream_t s(ioc); + bool invoked = false; + std::array epa{{ + srv.local_endpoint()}}; + s.expires_after(std::chrono::milliseconds(100)); + beast::async_connect(s, epa, + [&](error_code ec, tcp::endpoint) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run_for(std::chrono::seconds(1)); + BEAST_EXPECT(invoked); + } + + // immediate timeout + { + net::io_context ioc; + stream_t s(tcp::socket(ioc, tcp::v6())); + bool invoked = false; + std::array epa{{ + net::ip::tcp::endpoint( + net::ip::make_address("192.168.0.254"), 1)}}; + s.expires_after(std::chrono::seconds(-1)); + beast::async_connect(s, epa, + [&](error_code ec, tcp::endpoint) + { + invoked = true; + BEAST_EXPECTS( + ec == error::timeout, ec.message()); + }); + ioc.run_for(std::chrono::seconds(1)); + BEAST_EXPECT(invoked); + } + + // edge case: + // timer completion becomes queued before + // the I/O completion handler is invoked + // VFALCO Seems to hang on OSX Travis +#if 0 + { + net::io_context ioc1; + stream_t s1(ioc1); + net::io_context ioc2; + net::ip::tcp::acceptor a(ioc2); + a.open(ep.protocol()); + a.set_option( + net::socket_base::reuse_address(true)); + a.bind(ep); + a.listen(1); + a.async_accept([](error_code, tcp::socket){}); + bool invoked = false; + s1.expires_after(std::chrono::seconds(0)); + s1.async_connect( + a.local_endpoint(), + [&](error_code ec) + { + invoked = true; + BEAST_EXPECTS(ec == error::timeout, + ec.message()); + }); + ioc1.run(); + ioc1.restart(); + ioc2.run(); + ioc2.restart(); + ioc1.run(); + BEAST_EXPECT(invoked); + } +#endif + + /* VFALCO + We need a reliable way of causing a real + timeout, for example a stable IP address + for which connections are never established, + but that also do not cause immediate failure. + */ +#if 0 + // timeout (unreachable ipv4 host) + { + net::io_context ioc; + stream_t s(tcp::socket(ioc, tcp::v6())); + bool invoked = false; + std::array epa{{ + net::ip::tcp::endpoint( + net::ip::make_address("192.168.0.254"), 1)}}; + s.expires_after(std::chrono::milliseconds(100)); + beast::async_connect(s, epa, + [&](error_code ec, tcp::endpoint) + { + invoked = true; + BEAST_EXPECTS( + ec == error::timeout, ec.message()); + }); + ioc.run_for(std::chrono::seconds(1)); + BEAST_EXPECT(invoked); + } + + // timeout (ipv6 black hole) + { + net::io_context ioc; + stream_t s(tcp::socket(ioc, tcp::v6())); + bool invoked = false; + std::array epa{{ + tcp::endpoint( + net::ip::address( + net::ip::make_address_v6("100::")), + 1) + }}; + s.expires_after(std::chrono::milliseconds(100)); + beast::async_connect(s, epa, + [&](error_code ec, tcp::endpoint) + { + invoked = true; + BEAST_EXPECTS( + ec == error::timeout, ec.message()); + }); + ioc.run_for(std::chrono::seconds(1)); + BEAST_EXPECT(invoked); + } +#endif + } + + //-------------------------------------------------------------------------- + + http::response + make_response(http::request) + { + return {}; + } + + void process_http_1 (timeout_stream& stream, net::yield_context yield) + { + flat_buffer buffer; + http::request req; + + // Read the request, with a 15 second timeout + stream.expires_after(std::chrono::seconds(15)); + http::async_read(stream, buffer, req, yield); + + // Calculate the response + http::response res = make_response(req); + + // Send the response, with a 30 second timeout. + stream.expires_after (std::chrono::seconds(30)); + http::async_write (stream, res, yield); + } + + void process_http_2 (timeout_stream& stream, net::yield_context yield) + { + flat_buffer buffer; + http::request req; + + // Require that the read and write combined take no longer than 30 seconds + stream.expires_after(std::chrono::seconds(30)); + + http::async_read(stream, buffer, req, yield); + + http::response res = make_response(req); + http::async_write (stream, res, yield); + } + + websocket::stream + process_websocket(timeout_stream&& stream, net::yield_context yield) + { + websocket::stream ws(std::move(stream)); + + // Require that the entire websocket handshake take no longer than 10 seconds + ws.next_layer().expires_after(std::chrono::seconds(10)); + ws.async_accept(yield); + + return ws; + } + + void + testJavadocs() + { + BEAST_EXPECT(&basic_timeout_stream_test::process_http_1); + BEAST_EXPECT(&basic_timeout_stream_test::process_http_2); + BEAST_EXPECT(&basic_timeout_stream_test::process_websocket); + } + + //-------------------------------------------------------------------------- + + void + run() + { + testStrand(); + testMembers(); + testRead(); + testWrite(); + testConnect(); + testJavadocs(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,core,basic_timeout_stream); + +} // beast +} // boost diff --git a/test/beast/core/stream_socket.cpp b/test/beast/core/stream_socket.cpp deleted file mode 100644 index a39d2ba7..00000000 --- a/test/beast/core/stream_socket.cpp +++ /dev/null @@ -1,279 +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 -// - -// Test that header file is self-contained. -#include - -#include -#include -#include -#include -#include - -namespace boost { -namespace beast { - -class stream_socket_test - : public beast::unit_test::suite - , public test::enable_yield_to -{ -public: - class server - { - std::ostream& log_; - net::io_context ioc_; - net::ip::tcp::acceptor acceptor_; - net::ip::tcp::socket socket_; - std::thread t_; - - void - fail(error_code ec, string_view what) - { - if(ec != net::error::operation_aborted) - log_ << what << ": " << ec.message() << "\n"; - } - - public: - server( - net::ip::tcp::endpoint ep, - std::ostream& log) - : log_(log) - , ioc_(1) - , acceptor_(ioc_) - , socket_(ioc_) - { - boost::system::error_code ec; - - acceptor_.open(ep.protocol(), ec); - if(ec) - { - fail(ec, "open"); - return; - } - - acceptor_.set_option( - net::socket_base::reuse_address(true), ec); - if(ec) - { - fail(ec, "set_option"); - return; - } - - acceptor_.bind(ep, ec); - if(ec) - { - fail(ec, "bind"); - return; - } - - acceptor_.listen( - net::socket_base::max_listen_connections, ec); - if(ec) - { - fail(ec, "listen"); - return; - } - - acceptor_.async_accept(socket_, - [this](error_code ec){ this->on_accept(ec); }); - - t_ = std::thread([this]{ ioc_.run(); }); - } - - ~server() - { - ioc_.stop(); - t_.join(); - } - - private: - class session - : public std::enable_shared_from_this - { - net::ip::tcp::socket socket_; - - public: - session( - net::ip::tcp::socket sock, - std::ostream&) - : socket_(std::move(sock)) - { - } - - void - run() - { - socket_.async_wait( - net::socket_base::wait_read, - std::bind( - &session::on_read, - shared_from_this(), - std::placeholders::_1)); - } - - protected: - void - on_read(error_code ec) - { - boost::ignore_unused(ec); - } - }; - - void - on_accept(error_code ec) - { - if(! acceptor_.is_open()) - return; - if(ec) - fail(ec, "accept"); - else - std::make_shared( - std::move(socket_), log_)->run(); - acceptor_.async_accept(socket_, - [this](error_code ec) - { - this->on_accept(ec); - }); - } - }; - - void - testAsync() - { - net::ip::tcp::endpoint ep( - net::ip::make_address("127.0.0.1"), 8080); - server srv(ep, log); - { - net::io_context 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), - [&ec](error_code ec_, std::size_t) - { - 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) - { - }); - } - } - - void - run() - { - testAsync(); - testConnect(); - } -}; - -BEAST_DEFINE_TESTSUITE(beast,core,stream_socket); - -} // beast -} // boost diff --git a/test/beast/core/test_handler.hpp b/test/beast/core/test_handler.hpp index 7770276b..c5c99144 100644 --- a/test/beast/core/test_handler.hpp +++ b/test/beast/core/test_handler.hpp @@ -215,8 +215,7 @@ template struct associated_executor< boost::beast::legacy_handler, Executor> { - using type = typename - boost::beast::simple_executor; + using type = boost::beast::simple_executor; static type get( boost::beast::legacy_handler const&, diff --git a/test/beast/core/timeout_stream.cpp b/test/beast/core/timeout_stream.cpp new file mode 100644 index 00000000..ac8f616d --- /dev/null +++ b/test/beast/core/timeout_stream.cpp @@ -0,0 +1,11 @@ +// +// 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