diff --git a/CHANGELOG.md b/CHANGELOG.md index c6e87f67..7ead8398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Version 212: * dynamic_buffer_ref tests and tidy * flat_stream tests and tidy +* stranded_socket tests and tidy -------------------------------------------------------------------------------- diff --git a/doc/qbk/03_core/2_streams.qbk b/doc/qbk/03_core/2_streams.qbk index 6221b4af..fc489178 100644 --- a/doc/qbk/03_core/2_streams.qbk +++ b/doc/qbk/03_core/2_streams.qbk @@ -113,30 +113,4 @@ synchronous stream may check its argument: [snippet_core_3] - - -[heading Stream Interfaces] - -To facilitiate stream algorithms, these types provide enhanced functionality -above what is offered by networking: - -[table Stream Interfaces -[[Name][Description]] -[[ - [link beast.ref.boost__beast__basic_timeout_stream `basic_timeout_stream`] - [link beast.ref.boost__beast__timeout_stream `timeout_stream`] -][ - Objects of this type are designed to act as a replacement for the - traditional networking socket. They -]] -[[ - [link beast.ref.boost__beast__flat_stream `flat_stream`] -][ - This stream wrapper improves performance by working around a limitation - of networking's `ssl::stream` to improve performance. -]] -] - - - [endsect] diff --git a/doc/qbk/03_core/3_layers.qbk b/doc/qbk/03_core/3_layers.qbk index d90669b6..7bac24f8 100644 --- a/doc/qbk/03_core/3_layers.qbk +++ b/doc/qbk/03_core/3_layers.qbk @@ -110,7 +110,7 @@ facilities for authoring and working with layered streams: a work-around for a performance limitation in the original SSL stream. ]] [[ - [link beast.ref.boost__beast__stranded_stream `stranded_stream`] + [link beast.ref.boost__beast__stranded_socket `stranded_socket`] ][ A timeout stream meets the requirements for synchronous and asynchronous read and write streams by passing I/O through to an underlying @@ -131,4 +131,3 @@ streams: [code_core_3_layers_5] [endsect] - diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 0548c6bd..88d63bf4 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -46,7 +46,7 @@ span static_string stable_async_op_base 🞲 - stranded_stream 🞲 + stranded_socket 🞲 string_param string_view timeout_stream 🞲 @@ -69,6 +69,7 @@ bind_handler buffer_size 🞲 close_socket 🞲 + connect 🞲 generic_category get_lowest_layer 🞲 iequals diff --git a/doc/xsl/class_detail.xsl b/doc/xsl/class_detail.xsl index d2562cfc..70cff029 100644 --- a/doc/xsl/class_detail.xsl +++ b/doc/xsl/class_detail.xsl @@ -39,6 +39,9 @@ class __EndpointSequence__ + + class __ExecutionContext__ + class __Executor__ diff --git a/include/boost/beast/core.hpp b/include/boost/beast/core.hpp index 5bf8d46c..f24800cb 100644 --- a/include/boost/beast/core.hpp +++ b/include/boost/beast/core.hpp @@ -41,7 +41,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/include/boost/beast/core/basic_timeout_stream.hpp b/include/boost/beast/core/basic_timeout_stream.hpp index fc03141b..ab6d8e91 100644 --- a/include/boost/beast/core/basic_timeout_stream.hpp +++ b/include/boost/beast/core/basic_timeout_stream.hpp @@ -682,6 +682,7 @@ async_connect( 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: @@ -701,7 +702,7 @@ async_connect( 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: diff --git a/include/boost/beast/core/detail/stranded_stream.hpp b/include/boost/beast/core/detail/stranded_socket.hpp similarity index 70% rename from include/boost/beast/core/detail/stranded_stream.hpp rename to include/boost/beast/core/detail/stranded_socket.hpp index f8448b00..ce2d401a 100644 --- a/include/boost/beast/core/detail/stranded_stream.hpp +++ b/include/boost/beast/core/detail/stranded_socket.hpp @@ -7,8 +7,8 @@ // Official repository: https://github.com/boostorg/beast // -#ifndef BOOST_BEAST_CORE_DETAIL_STRANDED_STREAM_HPP -#define BOOST_BEAST_CORE_DETAIL_STRANDED_STREAM_HPP +#ifndef BOOST_BEAST_CORE_DETAIL_STRANDED_SOCKET_HPP +#define BOOST_BEAST_CORE_DETAIL_STRANDED_SOCKET_HPP #include #include @@ -19,20 +19,20 @@ namespace beast { namespace detail { template -class stranded_stream_base +class stranded_socket_base { protected: net::basic_stream_socket socket_; template explicit - stranded_stream_base(Args&&... args) + stranded_socket_base(Args&&... args) : socket_(std::forward(args)...) { } - stranded_stream_base(stranded_stream_base&&) = default; - stranded_stream_base& operator=(stranded_stream_base&&) = default; + stranded_socket_base(stranded_socket_base&&) = default; + stranded_socket_base& operator=(stranded_socket_base&&) = delete; }; } // detail diff --git a/include/boost/beast/core/stranded_socket.hpp b/include/boost/beast/core/stranded_socket.hpp new file mode 100644 index 00000000..09c5c6d4 --- /dev/null +++ b/include/boost/beast/core/stranded_socket.hpp @@ -0,0 +1,1102 @@ +// +// 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_STRANDED_SOCKET_HPP +#define BOOST_BEAST_CORE_STRANDED_SOCKET_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +//------------------------------------------------------------------------------ + +/** A stream-oriented socket using a custom executor, defaulting to a strand + + This class template provides asynchronous and blocking stream-oriented + socket functionality. It is designed as a replacement for + `net::basic_stream_socket`. + + This class template is parameterized on the executor type to be + used for all asynchronous operations. This achieves partial support for + [P1322]. The default template parameter uses a strand for the next + layer's executor. + + Unlike other stream wrappers, the underlying socket is accessed + through the @ref socket member function instead of `next_layer`. + This causes @ref stranded_socket to be returned in calls to + @ref get_lowest_layer. + + @tparam Protocol The protocol to use. + + @tparam Executor The executor to use. + + @par Thread Safety + @e Distinct @e objects: Safe.@n + @e Shared @e objects: Unsafe. + + @par Concepts: + @li SyncReadStream, SyncWriteStream + @li AsyncReadStream, AsyncWriteStream, + @li Protocol + @li Executor + + @see [P1322R0] + Networking TS enhancement to enable custom I/O executors +*/ +template< + class Protocol, + class Executor = net::io_context::strand +> +class stranded_socket +#ifndef BOOST_BEAST_DOXYGEN + : private detail::stranded_socket_base + , private boost::empty_value +#endif +{ + // Restricted until P1322R0 is incorporated into Boost.Asio. + static_assert( + std::is_convertible().context()), + net::io_context&>::value, + "Only net::io_context is currently supported for executor_type::context()"); + +public: + /// The type of the executor associated with the object. + using executor_type = Executor; + + /// The type of the underlying socket. + using socket_type = net::basic_stream_socket; + + /// The protocol type. + using protocol_type = Protocol; + + /// The endpoint type. + using endpoint_type = typename Protocol::endpoint; + + /** Construct the stream without opening it. + + This constructor creates a 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`. + + @param args A list of parameters forwarded to the constructor of + the underlying socket. + + @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, + class... Args + #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 + stranded_socket(ExecutionContext& ctx, Args&&... args) + : detail::stranded_socket_base( + ctx, std::forward(args)...) + , boost::empty_value( + boost::empty_init_t{}, ctx.get_executor()) + { + // Restriction is necessary until Asio fully supports P1322R0 + static_assert( + std::is_same::value, + "Only net::io_context is currently supported for ExecutionContext"); + } + + /** Construct the stream without opening it. + + This constructor creates a 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. + + @param args A list of parameters forwarded to the constructor of + the underlying socket. + + @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html + */ + template + explicit + stranded_socket(executor_type const& ex, Args&&... args) + : detail::stranded_socket_base( + ex.context(), std::forward(args)...) + , boost::empty_value( + boost::empty_init_t{}, ex) + { + } + + /** 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. + */ + stranded_socket(stranded_socket&& other) = default; + + /// Move assignment (deleted) + stranded_socket& + operator=(stranded_socket&& other) = delete; + + /** Get a reference to the underlying socket. + + This function returns a reference to the underlying + socket used by the stream. + */ + socket_type& + socket() noexcept + { + return this->socket_; + } + + /** Get a reference to the underlying socket. + + This function returns a reference to the underlying + socket used by the stream. + */ + socket_type const& + socket() const noexcept + { + return this->socket_; + } + + //-------------------------------------------------------------------------- + + /** Return the executor associated with the object. + + @return A copy of the executor that stream will use to dispatch handlers. + */ + executor_type + get_executor() const noexcept + { + return this->get(); + } + + /** Connect the socket to the specified endpoint. + + This function is used to connect a socket to the specified remote endpoint. + The function call will block until the connection is successfully made or + an error occurs. + + The 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 socket will be + connected. + + @throws system_error Thrown on failure. + */ + void + connect(endpoint_type const& ep) + { + this->socket_.connect(ep); + } + + /** Connect the socket to the specified endpoint. + + This function is used to connect a socket to the specified remote endpoint. + The function call will block until the connection is successfully made or + an error occurs. + + The 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 socket will be + connected. + + @param ec Set to indicate what error occurred, if any. + */ + void + connect(endpoint_type const& ep, error_code& ec) + { + this->socket_.connect(ep, ec); + } + + /** 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 const& ep, + ConnectHandler&& handler) + { + BOOST_BEAST_HANDLER_INIT(ConnectHandler, + void(error_code)); + this->socket_.async_connect( + ep, + detail::bind_default_executor( + this->get(), + std::move(init.completion_handler))); + return init.result.get(); + } + + /** Read some data from the stream. + + This function is used to read data from the stream. The function call will + block until one or more bytes of data has been read successfully, or until + an error occurs. + + @param buffers The buffers into which the data will be read. + + @returns The number of bytes read. + + @throws boost::system::system_error Thrown on failure. + + @note The `read_some` operation may not read all of the requested number of + bytes. Consider using the function `net::read` if you need to ensure + that the requested amount of data is read before the blocking operation + completes. + */ + template + std::size_t + read_some(MutableBufferSequence const& buffers) + { + return this->socket_.read_some(buffers); + } + + /** Read some data from the stream. + + This function is used to read data from the stream. The function call will + block until one or more bytes of data has been read successfully, or until + an error occurs. + + @param buffers The buffers into which the data will be read. + + @param ec Set to indicate what error occurred, if any. + + @returns The number of bytes read. + + @note The `read_some` operation may not read all of the requested number of + bytes. Consider using the function `net::read` if you need to ensure + that the requested amount of data is read before the blocking operation + completes. + */ + template + std::size_t + read_some( + MutableBufferSequence const& buffers, + error_code& ec) + { + return this->socket_.read_some(buffers, ec); + } + + /** 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) + { + BOOST_BEAST_HANDLER_INIT(ReadHandler, + void(error_code, std::size_t)); + this->socket_.async_read_some( + buffers, + detail::bind_default_executor( + this->get(), + std::move(init.completion_handler))); + return init.result.get(); + } + + /** Write some data to the stream. + + This function is used to write data on the stream. The function call will + block until one or more bytes of data has been written successfully, or + until an error occurs. + + @param buffers The data to be written. + + @returns The number of bytes written. + + @throws boost::system::system_error Thrown on failure. + + @note The `write_some` operation may not transmit all of the data to the + peer. Consider using the function `net::write` if you need to + ensure that all data is written before the blocking operation completes. + */ + template + std::size_t + write_some(ConstBufferSequence const& buffers) + { + return this->socket_.write_some(buffers); + } + + /** Write some data to the stream. + + This function is used to write data on the stream. The function call will + block until one or more bytes of data has been written successfully, or + until an error occurs. + + @param buffers The data to be written. + + @param ec Set to indicate what error occurred, if any. + + @returns The number of bytes written. + + @note The `write_some` operation may not transmit all of the data to the + peer. Consider using the function `net::write` if you need to + ensure that all data is written before the blocking operation completes. + */ + template + std::size_t + write_some( + ConstBufferSequence const& buffers, + error_code& ec) + { + return this->socket_.write_some(buffers, ec); + } + + /** 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) + { + BOOST_BEAST_HANDLER_INIT(WriteHandler, + void(error_code, std::size_t)); + this->socket_.async_write_some( + buffers, + detail::bind_default_executor( + this->get(), + std::move(init.completion_handler))); + return init.result.get(); + } +}; + +//------------------------------------------------------------------------------ + +/** Establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the socket's @c connect member + function, once for each endpoint in the sequence, until a connection is + successfully established. + + @param socket The socket to be connected. + If the underlying socket is already open, it will be closed. + + @param endpoints A sequence of endpoints. + + @returns The successfully connected endpoint. + + @throws system_error Thrown on failure. If the sequence is + empty, the associated error code is `net::error::not_found`. + Otherwise, contains the error from the last connection attempt. +*/ +template< + class Protocol, class Executor, + class EndpointSequence +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +typename Protocol::endpoint +connect( + stranded_socket& socket, + EndpointSequence const & endpoints +) +{ + return net::connect(socket.socket(), endpoints); +} + +/** Establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the socket's @c connect member + function, once for each endpoint in the sequence, until a connection is + successfully established. + + @param socket The socket to be connected. + If the underlying socket is already open, it will be closed. + + @param endpoints A sequence of endpoints. + + @param ec Set to indicate what error occurred, if any. If the sequence is + empty, set to `net::error::not_found`. Otherwise, contains the error + from the last connection attempt. + + @returns On success, the successfully connected endpoint. Otherwise, a + default-constructed endpoint. +*/ +template< + class Protocol, class Executor, + class EndpointSequence +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +typename Protocol::endpoint +connect( + stranded_socket& socket, + EndpointSequence const& endpoints, + error_code& ec +) +{ + return net::connect(socket.socket(), endpoints, ec); +} + +/** Establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the socket's @c connect member + function, once for each endpoint in the sequence, until a connection is + successfully established. + + @param socket The socket 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. + + @returns An iterator denoting the successfully connected endpoint. + + @throws system_error Thrown on failure. If the sequence is + empty, the associated error code is `net::error::not_found`. + Otherwise, contains the error from the last connection attempt. +*/ +template< + class Protocol, class Executor, + class Iterator> +Iterator +connect( + stranded_socket& socket, + Iterator begin, Iterator end) +{ + return net::connect(socket.socket(), begin, end); +} + +/** Establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the socket's @c connect member + function, once for each endpoint in the sequence, until a connection is + successfully established. + + @param socket The socket 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 ec Set to indicate what error occurred, if any. If the sequence is + empty, set to boost::asio::error::not_found. Otherwise, contains the error + from the last connection attempt. + + @returns On success, an iterator denoting the successfully connected + endpoint. Otherwise, the end iterator. +*/ +template< + class Protocol, class Executor, + class Iterator> +Iterator +connect( + stranded_socket& socket, + Iterator begin, Iterator end, + error_code& ec) +{ + return net::connect(socket.socket(), begin, end, ec); +} + +/** Establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the socket's @c connect member + function, once for each endpoint in the sequence, until a connection is + successfully established. + + @param socket The socket to be connected. + If the underlying 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. + + @returns The successfully connected endpoint. + + @throws boost::system::system_error Thrown on failure. If the sequence is + empty, the associated error code is `net::error::not_found`. + Otherwise, contains the error from the last connection attempt. +*/ +template< + class Protocol, class Executor, + class EndpointSequence, class ConnectCondition +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +typename Protocol::endpoint +connect( + stranded_socket& socket, + EndpointSequence const& endpoints, + ConnectCondition connect_condition +) +{ + return net::connect(socket.socket(), endpoints, connect_condition); +} + +/** Establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the socket's @c connect member + function, once for each endpoint in the sequence, until a connection is + successfully established. + + @param socket The socket to be connected. + If the underlying 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 ec Set to indicate what error occurred, if any. If the sequence is + empty, set to `net::error::not_found`. Otherwise, contains the error + from the last connection attempt. + + @returns On success, the successfully connected endpoint. Otherwise, a + default-constructed endpoint. +*/ +template< + class Protocol, class Executor, + class EndpointSequence, class ConnectCondition +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +typename Protocol::endpoint +connect( + stranded_socket& socket, + EndpointSequence const& endpoints, + ConnectCondition connect_condition, + error_code& ec) +{ + return net::connect(socket.socket(), endpoints, connect_condition, ec); +} + +/** Establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the socket's @c connect member + function, once for each endpoint in the sequence, until a connection is + successfully established. + + @param socket The socket 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, + 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. + + @returns An iterator denoting the successfully connected endpoint. + + @throws boost::system::system_error Thrown on failure. If the sequence is + empty, the associated @c error_code is `net::error::not_found`. + Otherwise, contains the error from the last connection attempt. +*/ +template< + class Protocol, class Executor, + class Iterator, class ConnectCondition> +Iterator +connect( + stranded_socket& socket, + Iterator begin, Iterator end, + ConnectCondition connect_condition) +{ + return net::connect(socket.socket(), begin, end, connect_condition); +} + +/** Establishes a socket connection by trying each endpoint in a sequence. + + This function attempts to connect a socket to one of a sequence of + endpoints. It does this by repeated calls to the socket's @c connect member + function, once for each endpoint in the sequence, until a connection is + successfully established. + + @param socket The socket 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, + 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 ec Set to indicate what error occurred, if any. If the sequence is + empty, set to `net::error::not_found`. Otherwise, contains the error + from the last connection attempt. + + @returns On success, an iterator denoting the successfully connected + endpoint. Otherwise, the end iterator. +*/ +template< + class Protocol, class Executor, + class Iterator, class ConnectCondition> +Iterator +connect( + stranded_socket& socket, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + error_code& ec) +{ + return net::connect(socket.socket(), begin, end, connect_condition, ec); +} + +/** 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 socket The socket 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( + stranded_socket& socket, + EndpointSequence const& endpoints, + RangeConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, + void(error_code, typename Protocol::endpoint)); + net::async_connect(socket.socket(), + endpoints, + detail::bind_default_executor( + socket.get_executor(), + std::move(init.completion_handler))); + return init.result.get(); +} + +/** 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 socket The socket 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( + stranded_socket& socket, + EndpointSequence const& endpoints, + ConnectCondition connect_condition, + RangeConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, + void(error_code, typename Protocol::endpoint)); + net::async_connect(socket.socket(), + endpoints, + connect_condition, + detail::bind_default_executor( + socket.get_executor(), + std::move(init.completion_handler))); + return init.result.get(); +} + +/** 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 socket The socket 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( + stranded_socket& socket, + Iterator begin, Iterator end, + IteratorConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, + void(error_code, Iterator)); + net::async_connect(socket.socket(), + begin, end, + detail::bind_default_executor( + socket.get_executor(), + std::move(init.completion_handler))); + return init.result.get(); +} + +/** 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 socket The socket 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, + 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, 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( + stranded_socket& socket, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + IteratorConnectHandler&& handler) +{ + BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, + void(error_code, Iterator)); + net::async_connect(socket.socket(), + begin, end, + connect_condition, + detail::bind_default_executor( + socket.get_executor(), + std::move(init.completion_handler))); + return init.result.get(); +} + +} // beast +} // boost + +#endif diff --git a/include/boost/beast/core/stranded_stream.hpp b/include/boost/beast/core/stranded_stream.hpp deleted file mode 100644 index 583fba2e..00000000 --- a/include/boost/beast/core/stranded_stream.hpp +++ /dev/null @@ -1,433 +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_STRANDED_STREAM_HPP -#define BOOST_BEAST_CORE_STRANDED_STREAM_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast { - -//------------------------------------------------------------------------------ - -/** A stream socket using a custom executor, defaulting to a strand - - This class template is parameterized on the executor type to be - used for all asynchronous operations. This achieves partial support for - [P1322]. The default template parameter uses a strand for the next - layer's executor. - - @see [P1322R0] - Networking TS enhancement to enable custom I/O executors -*/ -template< - class Protocol, - class Executor = beast::executor_type> -> -class stranded_stream -#ifndef BOOST_BEAST_DOXYGEN - : private detail::stranded_stream_base - , private boost::empty_value -#endif -{ - // Restricted until P1322R0 is incorporated into Boost.Asio. - static_assert( - std::is_convertible().context()), - net::io_context&>::value, - "Only net::io_context is currently supported for executor_type::context()"); - -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; - - /** Construct the stream without opening it. - - This constructor creates a 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`. - - @param args A list of parameters forwarded to the constructor of - the underlying socket. - - @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, - class... Args - #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 - stranded_stream(ExecutionContext& ctx, Args&&... args) - : detail::stranded_stream_base( - ctx, std::forward(args)...) - , boost::empty_value( - boost::empty_init_t{}, ctx.get_executor()) - { - // Restriction is necessary until Asio fully supports P1322R0 - static_assert( - std::is_same::value, - "Only net::io_context is currently supported for ExecutionContext"); - } - - /** Construct the stream without opening it. - - This constructor creates a 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. - - @param args A list of parameters forwarded to the constructor of - the underlying socket. - - @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html - */ - template - explicit - stranded_stream(executor_type const& ex, Args&&... args) - : detail::stranded_stream_base( - ex.context(), std::forward(args)...) - , boost::empty_value( - boost::empty_init_t{}, ex) - { - // Restriction is necessary until Asio fully supports P1322R0 - if(ex.context().get_executor() != this->socket_.get_executor()) - throw std::invalid_argument( - "ctx.get_executor() != socket.get_executor()"); - } - - /** 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. - */ - stranded_stream(stranded_stream&& other) = default; - - /** 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. - */ - stranded_stream& - operator=(stranded_stream&& other) = default; - - //-------------------------------------------------------------------------- - - /** Return the executor associated with the object. - - @return A copy of the executor that stream will use to dispatch handlers. - */ - executor_type - get_executor() const noexcept - { - return this->get(); - } - - /** 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 this->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 this->socket_; - } - - /** 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); - - /** Read some data from the stream. - - This function is used to read data from the stream. The function call will - block until one or more bytes of data has been read successfully, or until - an error occurs. - - @param buffers The buffers into which the data will be read. - - @returns The number of bytes read. - - @throws boost::system::system_error Thrown on failure. - - @note The `read_some` operation may not read all of the requested number of - bytes. Consider using the function `net::read` if you need to ensure - that the requested amount of data is read before the blocking operation - completes. - */ - template - std::size_t - read_some(MutableBufferSequence const& buffers) - { - return this->socket_.read_some(buffers); - } - - /** Read some data from the stream. - - This function is used to read data from the stream. The function call will - block until one or more bytes of data has been read successfully, or until - an error occurs. - - @param buffers The buffers into which the data will be read. - - @param ec Set to indicate what error occurred, if any. - - @returns The number of bytes read. - - @note The `read_some` operation may not read all of the requested number of - bytes. Consider using the function `net::read` if you need to ensure - that the requested amount of data is read before the blocking operation - completes. - */ - template - std::size_t - read_some( - MutableBufferSequence const& buffers, - error_code& ec) - { - return this->socket_.read_some(buffers, ec); - } - - /** 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) - { - BOOST_BEAST_HANDLER_INIT(ReadHandler, - void(error_code, std::size_t)); - this->socket_.async_read_some( - buffers, - detail::bind_default_executor( - this->get(), - std::move(init.completion_handler))); - return init.result.get(); - } - - /** Write some data to the stream. - - This function is used to write data on the stream. The function call will - block until one or more bytes of data has been written successfully, or - until an error occurs. - - @param buffers The data to be written. - - @returns The number of bytes written. - - @throws boost::system::system_error Thrown on failure. - - @note The `write_some` operation may not transmit all of the data to the - peer. Consider using the function `net::write` if you need to - ensure that all data is written before the blocking operation completes. - */ - template - std::size_t - write_some(ConstBufferSequence const& buffers) - { - return this->socket_.write_some(buffers); - } - - /** Write some data to the stream. - - This function is used to write data on the stream. The function call will - block until one or more bytes of data has been written successfully, or - until an error occurs. - - @param buffers The data to be written. - - @param ec Set to indicate what error occurred, if any. - - @returns The number of bytes written. - - @note The `write_some` operation may not transmit all of the data to the - peer. Consider using the function `net::write` if you need to - ensure that all data is written before the blocking operation completes. - */ - template - std::size_t - write_some( - ConstBufferSequence const& buffers, - error_code& ec) - { - return this->socket_.write_some(buffers, ec); - } - - /** 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) - { - BOOST_BEAST_HANDLER_INIT(WriteHandler, - void(error_code, std::size_t)); - this->socket_.async_write_some( - buffers, - detail::bind_default_executor( - this->get(), - std::move(init.completion_handler))); - return init.result.get(); - } -}; - -} // beast -} // boost - -#endif diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt index bc43c50d..34dfadcd 100644 --- a/test/beast/core/CMakeLists.txt +++ b/test/beast/core/CMakeLists.txt @@ -61,7 +61,7 @@ add_executable (tests-beast-core span.cpp static_buffer.cpp static_string.cpp - stranded_stream.cpp + stranded_socket.cpp stream_traits.cpp string.cpp string_param.cpp diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile index c63b3ff8..073cc723 100644 --- a/test/beast/core/Jamfile +++ b/test/beast/core/Jamfile @@ -49,7 +49,7 @@ local SOURCES = span.cpp static_buffer.cpp static_string.cpp - stranded_stream.cpp + stranded_socket.cpp stream_traits.cpp string.cpp string_param.cpp diff --git a/test/beast/core/bind_handler.cpp b/test/beast/core/bind_handler.cpp index 7af2e6dd..ac9e934e 100644 --- a/test/beast/core/bind_handler.cpp +++ b/test/beast/core/bind_handler.cpp @@ -134,7 +134,7 @@ public: { // shouldn't be called since the enclosing // networking wrapper only uses dispatch - s_.fail("unexpected post", __FILE__, __LINE__); + BEAST_FAIL(); } template @@ -142,7 +142,7 @@ public: { // shouldn't be called since the enclosing // networking wrapper only uses dispatch - s_.fail("unexpected defer", __FILE__, __LINE__); + BEAST_FAIL(); } }; diff --git a/test/beast/core/stranded_socket.cpp b/test/beast/core/stranded_socket.cpp new file mode 100644 index 00000000..f9ab9a9b --- /dev/null +++ b/test/beast/core/stranded_socket.cpp @@ -0,0 +1,519 @@ +// +// 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 "stream_tests.hpp" + +#include +#include + +#include + +namespace boost { +namespace beast { + +namespace { + +template +class test_executor +{ +public: + // VFALCO These need to be atomic or something + struct info + { + int dispatch = 0; + int post = 0; + int defer = 0; + int work = 0; + int total = 0; + }; + +private: + struct state + { + Executor ex; + info info_; + + state(Executor const& ex_) + : ex(ex_) + { + } + }; + + std::shared_ptr sp_; + +public: + test_executor(test_executor const&) = default; + test_executor& operator=(test_executor const&) = default; + + explicit + test_executor(Executor const& ex) + : sp_(std::make_shared(ex)) + { + } + + decltype(sp_->ex.context()) + context() const noexcept + { + return sp_->ex.context(); + } + + info& + operator*() noexcept + { + return sp_->info_; + } + + info* + operator->() noexcept + { + return &sp_->info_; + } + + void + on_work_started() const noexcept + { + ++sp_->info_.work; + } + + void + on_work_finished() const noexcept + { + } + + template + void + dispatch(F&& f, A const& a) + { + ++sp_->info_.dispatch; + ++sp_->info_.total; + sp_->ex.dispatch( + std::forward(f), a); + } + + template + void + post(F&& f, A const& a) + { + ++sp_->info_.post; + ++sp_->info_.total; + sp_->ex.post( + std::forward(f), a); + } + + template + void + defer(F&& f, A const& a) + { + ++sp_->info_.defer; + ++sp_->info_.total; + sp_->ex.defer( + std::forward(f), a); + } +}; + +struct test_handler +{ + int& flags; + + void + operator()() + { + flags |= 1; + } + + template + friend + void + asio_handler_invoke(F&& f, test_handler* p) + { + p->flags |= 2; + std::move(f)(); + } +}; + +struct test_acceptor +{ + net::io_context ioc; + net::ip::tcp::acceptor a; + net::ip::tcp::endpoint ep; + + test_acceptor() + : a(ioc) + , 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); + ep = a.local_endpoint(); + a.async_accept( + [](error_code, net::ip::tcp::socket) + { + }); + } +}; + +} // (anon) + +//------------------------------------------------------------------------------ + +class stranded_socket_test + : public beast::unit_test::suite +{ +public: + using tcp = net::ip::tcp; + using strand = net::io_context::strand; + using executor = net::io_context::executor_type; + + void + testStream() + { + net::io_context ioc; + + // default Executor + + { + stranded_socket s1{strand(ioc)}; + stranded_socket s2{strand{ioc}}; + //stranded_socket s3{strand{ioc}}; // ambiguous parse + } + + // explicit Executor + + { + auto ex = ioc.get_executor(); + stranded_socket s1(ioc); + stranded_socket s2(ex); + stranded_socket s3(ioc, tcp::v4()); + stranded_socket s4(std::move(s1)); + s2.socket() = tcp::socket(ioc); + BEAST_EXPECT(s1.get_executor() == ex); + BEAST_EXPECT(s2.get_executor() == ex); + BEAST_EXPECT(s3.get_executor() == ex); + BEAST_EXPECT(s4.get_executor() == ex); + + BEAST_EXPECT((! static_cast< + stranded_socket const&>( + s2).socket().is_open())); + } + + { + auto ex = strand{ioc}; + stranded_socket s1(ex); + stranded_socket s2(ex, tcp::v4()); + stranded_socket s3(std::move(s1)); + s2.socket() = tcp::socket(ioc); + BEAST_EXPECT(s1.get_executor() == ex); + BEAST_EXPECT(s2.get_executor() == ex); + BEAST_EXPECT(s3.get_executor() == ex); + + BEAST_EXPECT((! static_cast< + stranded_socket const&>( + s2).socket().is_open())); + } + + { + test_sync_stream>(); + test_async_stream>(); + test_sync_stream>(); + test_async_stream>(); + } + } + + void + testMembers() + { + net::io_context ioc; + + // connect (member) + + auto const cond = + [](error_code, tcp::endpoint) + { + return true; + }; + + { + stranded_socket s(ioc); + error_code ec; + test_acceptor a; + try + { + s.connect(a.ep); + BEAST_PASS(); + } + catch(std::exception const&) + { + BEAST_FAIL(); + } + } + + { + stranded_socket s(ioc); + error_code ec; + test_acceptor a; + s.connect(a.ep, ec); + BEAST_EXPECT(! ec); + } + + // connect + + { + test_acceptor a; + std::array epa; + epa[0] = a.ep; + stranded_socket s(ioc); + error_code ec; + connect(s, epa); + connect(s, epa, ec); + } + + { + test_acceptor a; + std::array epa; + epa[0] = a.ep; + stranded_socket s(ioc); + error_code ec; + connect(s, epa, cond); + connect(s, epa, cond, ec); + } + + { + test_acceptor a; + std::array epa; + epa[0] = a.ep; + stranded_socket s(ioc); + error_code ec; + connect(s, epa.begin(), epa.end()); + connect(s, epa.begin(), epa.end(), ec); + } + + { + test_acceptor a; + std::array epa; + epa[0] = a.ep; + stranded_socket s(ioc); + error_code ec; + connect(s, epa.begin(), epa.end(), cond); + connect(s, epa.begin(), epa.end(), cond, ec); + } + + // async_connect + + { + stranded_socket s(ioc); + test_acceptor a; + error_code ec; + s.async_connect(a. ep, + [](error_code ec) + { + BEAST_EXPECT(! ec); + }); + ioc.run(); + ioc.restart(); + } + + { + std::array epa; + epa[0] = tcp::endpoint( + net::ip::make_address_v4("127.0.0.1"), 0); + stranded_socket s(ioc); + async_connect(s, epa, + [](error_code, tcp::endpoint) + { + }); + } + + { + std::array epa; + epa[0] = tcp::endpoint( + net::ip::make_address_v4("127.0.0.1"), 0); + stranded_socket s(ioc); + async_connect(s, epa, cond, + [](error_code, tcp::endpoint) + { + }); + } + + { + std::array epa; + epa[0] = tcp::endpoint( + net::ip::make_address_v4("127.0.0.1"), 0); + using iter_type = decltype(epa)::const_iterator; + stranded_socket s(ioc); + async_connect(s, epa.begin(), epa.end(), + [](error_code, iter_type) + { + }); + } + + { + std::array epa; + epa[0] = tcp::endpoint( + net::ip::make_address_v4("127.0.0.1"), 0); + using iter_type = decltype(epa)::const_iterator; + stranded_socket s(ioc); + async_connect(s, epa.begin(), epa.end(), cond, + [](error_code, iter_type) + { + }); + } + + // read/write + + { + error_code ec; + stranded_socket s(ioc, tcp::v4()); + + BEAST_EXPECT(s.read_some(net::mutable_buffer{}) == 0); + BEAST_EXPECT(s.read_some(net::mutable_buffer{}, ec) == 0); + BEAST_EXPECTS(! ec, ec.message()); + + BEAST_EXPECT(s.write_some(net::const_buffer{}) == 0); + BEAST_EXPECT(s.write_some(net::const_buffer{}, ec) == 0); + BEAST_EXPECTS(! ec, ec.message()); + + bool invoked; + + invoked = false; + s.async_read_some(net::mutable_buffer{}, + [&](error_code ec, std::size_t) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(invoked); + + invoked = false; + s.async_write_some(net::const_buffer{}, + [&](error_code ec, std::size_t) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(invoked); + } + + // stranded + + { + error_code ec; + stranded_socket s(strand(ioc), tcp::v4()); + + bool invoked; + + invoked = false; + s.async_read_some(net::mutable_buffer{}, + [&](error_code ec, std::size_t) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(invoked); + + invoked = false; + s.async_write_some(net::const_buffer{}, + [&](error_code ec, std::size_t) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(invoked); + } + + // test_executor + + { + error_code ec; + stranded_socket> s( + test_executor<>(ioc.get_executor()), tcp::v4()); + + bool invoked; + + invoked = false; + s.async_read_some(net::mutable_buffer{}, + [&](error_code ec, std::size_t) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(invoked); + BEAST_EXPECT(s.get_executor()->total > 0); + s.get_executor()->total = 0; + + invoked = false; + s.async_write_some(net::const_buffer{}, + [&](error_code ec, std::size_t) + { + invoked = true; + BEAST_EXPECTS(! ec, ec.message()); + }); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(invoked); + BEAST_EXPECT(s.get_executor()->total > 0); + s.get_executor()->total = 0; + } + + // bind_default_executor::asio_handler_invoke + +#if 0 + // VFALCO This test fails, because it is unclear how + // asio_handler_invoke interacts with the wrapper. + // Need to ask Chris Kohlhoff about this one. + { + int flags = 0; + net::post( + ioc, + detail::bind_default_executor( + strand(ioc), + test_handler{flags})); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(flags == 3); + } +#endif + } + + //-------------------------------------------------------------------------- + + void + testJavadocs() + { + } + + //-------------------------------------------------------------------------- + + void + run() + { + testStream(); + testJavadocs(); + testMembers(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,core,stranded_socket); + +} // beast +} // boost diff --git a/test/beast/core/stranded_stream.cpp b/test/beast/core/stranded_stream.cpp deleted file mode 100644 index b282bcec..00000000 --- a/test/beast/core/stranded_stream.cpp +++ /dev/null @@ -1,81 +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 "stream_tests.hpp" - -#include -#include - -namespace boost { -namespace beast { - -class stranded_stream_test - : public beast::unit_test::suite -{ -public: - using tcp = net::ip::tcp; - - void - testStream() - { - net::io_context ioc; - { - using ex_t = net::io_context::executor_type; - auto ex = ioc.get_executor(); - stranded_stream s1(ioc); - stranded_stream s2(ex); - stranded_stream s3(ioc, tcp::v4()); - stranded_stream s4(std::move(s1)); - s2.next_layer() = tcp::socket(ioc); - BEAST_EXPECT(s1.get_executor() == ex); - BEAST_EXPECT(s2.get_executor() == ex); - BEAST_EXPECT(s3.get_executor() == ex); - BEAST_EXPECT(s4.get_executor() == ex); - } - { - using ex_t = net::io_context::strand; - auto ex = ex_t{ioc}; - stranded_stream s1(ex); - stranded_stream s2(ex, tcp::v4()); - stranded_stream s3(std::move(s1)); - s2.next_layer() = tcp::socket(ioc); - BEAST_EXPECT(s1.get_executor() == ex); - BEAST_EXPECT(s2.get_executor() == ex); - BEAST_EXPECT(s3.get_executor() == ex); - } - { - using ex_t = net::io_context::executor_type; - test_sync_stream>(); - test_async_stream>(); - } - } - - void - testJavadocs() - { - } - - //-------------------------------------------------------------------------- - - void - run() - { - testStream(); - testJavadocs(); - pass(); - } -}; - -BEAST_DEFINE_TESTSUITE(beast,core,stranded_stream); - -} // beast -} // boost