From 23a7bcc67edaa326664d67949dbfeede9e5a3514 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 10 Feb 2019 12:27:28 -0800 Subject: [PATCH] Add RatePolicy to basic_stream --- CHANGELOG.md | 1 + doc/qbk/quickref.xml | 3 + include/boost/beast/core/basic_stream.hpp | 63 +++- .../boost/beast/core/detail/stream_base.hpp | 2 +- .../boost/beast/core/impl/basic_stream.hpp | 289 ++++++++++++++---- include/boost/beast/core/rate_policy.hpp | 222 ++++++++++++++ test/beast/core/CMakeLists.txt | 1 + test/beast/core/Jamfile | 1 + test/beast/core/basic_stream.cpp | 12 + test/beast/core/rate_policy.cpp | 34 +++ 10 files changed, 546 insertions(+), 82 deletions(-) create mode 100644 include/boost/beast/core/rate_policy.hpp create mode 100644 test/beast/core/rate_policy.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d2179672..193ddaf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Version 216: * Add websocket::stream timeouts * Use suggested timeouts in Websocket examples * Add make_strand +* Add RatePolicy to basic_stream -------------------------------------------------------------------------------- diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 2fa9a50f..ed537b4a 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -37,6 +37,7 @@ handler_ptr iequal iless + rate_policy_access 🞲 @@ -44,11 +45,13 @@ saved_handler 🞲 span + simple_rate_policy 🞲 static_string stable_async_op_base 🞲 string_param string_view tcp_stream 🞲 + unlimited_rate_policy 🞲 Constants diff --git a/include/boost/beast/core/basic_stream.hpp b/include/boost/beast/core/basic_stream.hpp index 94939358..271bc2af 100644 --- a/include/boost/beast/core/basic_stream.hpp +++ b/include/boost/beast/core/basic_stream.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include // VFALCO This is unfortunate #include @@ -25,6 +26,8 @@ #include #include #include +#include +#include namespace boost { namespace asio { @@ -37,9 +40,7 @@ template class stream; namespace boost { namespace beast { -//------------------------------------------------------------------------------ - -/** A stream socket wrapper with timeouts and associated executor. +/** A stream socket wrapper with timeouts, bandwidth limits, and associated executor. This stream wraps a `net::basic_stream_socket` to provide the following features: @@ -193,7 +194,9 @@ namespace beast { */ template< class Protocol, - class Executor = net::executor> + class Executor = net::executor, + class RatePolicy = unlimited_rate_policy +> class basic_stream #if ! BOOST_BEAST_DOXYGEN : private detail::stream_base @@ -228,6 +231,7 @@ public: #endif struct impl_type : boost::enable_shared_from_this + , boost::empty_value { // must come first net::basic_stream_socket< @@ -235,6 +239,8 @@ public: op_state read; op_state write; + net::steady_timer timer; // rate timer + int waiting = 0; impl_type(impl_type&&) = default; @@ -250,6 +256,21 @@ public: return this->socket.get_executor(); } + RatePolicy& + policy() noexcept + { + return this->boost::empty_value::get(); + } + + RatePolicy const& + policy() const noexcept + { + return this->boost::empty_value::get(); + } + + template + void on_timer(Executor2 const& ex2); + void reset(); // set timeouts to never void close(); // cancel everything }; @@ -265,10 +286,10 @@ private: template class async_op; - template + template friend class detail::basic_stream_connect_op; - template + template friend class basic_stream; struct timeout_handler; @@ -354,6 +375,20 @@ public: //-------------------------------------------------------------------------- + /// Returns the rate policy associated with the object + RatePolicy& + rate_policy() noexcept + { + return impl_->policy(); + } + + /// Returns the rate policy associated with the object + RatePolicy const& + rate_policy() const noexcept + { + return impl_->policy(); + } + /** Set the timeout for the next logical operation. This sets either the read timer, the write timer, or @@ -1148,7 +1183,7 @@ connect( to using `net::post`. */ template< - class Protocol, class Executor, + class Protocol, class Executor, class RatePolicy, class EndpointSequence, class RangeConnectHandler #if ! BOOST_BEAST_DOXYGEN @@ -1160,7 +1195,7 @@ template< BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, void (error_code, typename Protocol::endpoint)) async_connect( - basic_stream& stream, + basic_stream& stream, EndpointSequence const& endpoints, RangeConnectHandler&& handler); @@ -1236,7 +1271,7 @@ async_connect( @endcode */ template< - class Protocol, class Executor, + class Protocol, class Executor, class RatePolicy, class EndpointSequence, class ConnectCondition, class RangeConnectHandler @@ -1249,7 +1284,7 @@ template< BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, void (error_code, typename Protocol::endpoint)) async_connect( - basic_stream& stream, + basic_stream& stream, EndpointSequence const& endpoints, ConnectCondition connect_condition, RangeConnectHandler&& handler); @@ -1296,13 +1331,13 @@ async_connect( to using `net::post`. */ template< - class Protocol, class Executor, + class Protocol, class Executor, class RatePolicy, class Iterator, class IteratorConnectHandler> BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, void (error_code, Iterator)) async_connect( - basic_stream& stream, + basic_stream& stream, Iterator begin, Iterator end, IteratorConnectHandler&& handler); @@ -1355,14 +1390,14 @@ async_connect( to using `net::post`. */ template< - class Protocol, class Executor, + class Protocol, class Executor, class RatePolicy, class Iterator, class ConnectCondition, class IteratorConnectHandler> BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, void (error_code, Iterator)) async_connect( - basic_stream& stream, + basic_stream& stream, Iterator begin, Iterator end, ConnectCondition connect_condition, IteratorConnectHandler&& handler); diff --git a/include/boost/beast/core/detail/stream_base.hpp b/include/boost/beast/core/detail/stream_base.hpp index 1a04e2a4..7bd3b1ef 100644 --- a/include/boost/beast/core/detail/stream_base.hpp +++ b/include/boost/beast/core/detail/stream_base.hpp @@ -21,7 +21,7 @@ namespace boost { namespace beast { namespace detail { -template +template class basic_stream_connect_op; struct any_endpoint diff --git a/include/boost/beast/core/impl/basic_stream.hpp b/include/boost/beast/core/impl/basic_stream.hpp index 680234b4..9befc076 100644 --- a/include/boost/beast/core/impl/basic_stream.hpp +++ b/include/boost/beast/core/impl/basic_stream.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -28,21 +29,82 @@ namespace beast { //------------------------------------------------------------------------------ -template +template template -basic_stream:: +basic_stream:: impl_type:: impl_type(Args&&... args) : socket(std::forward(args)...) , read(ex()) , write(ex()) + , timer(ex()) { reset(); } -template +template +template void -basic_stream:: +basic_stream:: +impl_type:: +on_timer(Executor2 const& ex2) +{ + BOOST_ASSERT(waiting > 0); + + // the last waiter starts the new slice + if(--waiting > 0) + return; + + // update the expiration time + BOOST_VERIFY(timer.expires_after( + std::chrono::seconds(1)) == 0); + + rate_policy_access::on_timer(policy()); + + struct handler : boost::empty_value + { + boost::weak_ptr wp; + + using executor_type = Executor2; + + executor_type + get_executor() const noexcept + { + return this->get(); + } + + handler( + Executor2 const& ex2, + boost::shared_ptr const& sp) + : boost::empty_value( + boost::empty_init_t{}, ex2) + , wp(sp) + { + } + + void + operator()(error_code ec) + { + auto sp = wp.lock(); + if(! sp) + return; + if(ec == net::error::operation_aborted) + return; + BOOST_ASSERT(! ec); + if(ec) + return; + sp->on_timer(this->get()); + } + }; + + // wait on the timer again + ++waiting; + timer.async_wait(handler(ex2, this->shared_from_this())); +} + +template +void +basic_stream:: impl_type:: reset() { @@ -62,13 +124,14 @@ reset() write.timer.expires_at(never()) == 0); } -template +template void -basic_stream:: +basic_stream:: impl_type:: close() { socket.close(); + timer.cancel(); // have to let the read/write ops cancel the timer, // otherwise we will get error::timeout on close when @@ -80,9 +143,9 @@ close() //------------------------------------------------------------------------------ -template -struct basic_stream< - Protocol, Executor>::timeout_handler +template +struct basic_stream:: + timeout_handler { op_state& state; boost::weak_ptr wp; @@ -124,9 +187,9 @@ struct basic_stream< also provides this. */ -template +template template -class basic_stream::async_op +class basic_stream::async_op : public async_op_base , public boost::asio::coroutine { @@ -134,6 +197,8 @@ class basic_stream::async_op pending_guard pg_; Buffers b_; + using is_read = std::integral_constant; + op_state& state(std::true_type) { @@ -153,18 +218,62 @@ class basic_stream::async_op std::integral_constant{}); } - void - async_perform(std::true_type) + std::size_t + available_bytes(std::true_type) { - impl_->socket.async_read_some( - b_, std::move(*this)); + return rate_policy_access:: + available_read_bytes(impl_->policy()); + } + + std::size_t + available_bytes(std::false_type) + { + return rate_policy_access:: + available_write_bytes(impl_->policy()); + } + + std::size_t + available_bytes() + { + return available_bytes(is_read{}); } void - async_perform(std::false_type) + transfer_bytes(std::size_t n, std::true_type) + { + rate_policy_access:: + transfer_read_bytes(impl_->policy(), n); + } + + void + transfer_bytes(std::size_t n, std::false_type) + { + rate_policy_access:: + transfer_write_bytes(impl_->policy(), n); + } + + void + transfer_bytes(std::size_t n) + { + transfer_bytes(n, is_read{}); + } + + void + async_perform( + std::size_t amount, std::true_type) + { + impl_->socket.async_read_some( + beast::buffers_prefix(amount, b_), + std::move(*this)); + } + + void + async_perform( + std::size_t amount, std::false_type) { impl_->socket.async_write_some( - b_, std::move(*this)); + beast::buffers_prefix(amount, b_), + std::move(*this)); } public: @@ -189,16 +298,23 @@ public: { BOOST_ASIO_CORO_REENTER(*this) { + // handle empty buffers if(detail::buffers_empty(b_)) { + // make sure we perform the no-op BOOST_ASIO_CORO_YIELD - async_perform( - std::integral_constant{}); + async_perform(0, is_read{}); + // apply the timeout manually, otherwise + // behavior varies across platforms. if(state().timer.expiry() <= clock_type::now()) + { + impl_->close(); ec = beast::error::timeout; + } goto upcall; } + // if a timeout is active, wait on the timer if(state().timer.expiry() != never()) state().timer.async_wait( net::bind_executor( @@ -209,9 +325,38 @@ public: state().tick })); + // check rate limit, maybe wait + std::size_t amount; + amount = available_bytes(); + if(amount == 0) + { + ++impl_->waiting; + BOOST_ASIO_CORO_YIELD + impl_->timer.async_wait(std::move(*this)); + if(ec) + { + // socket was closed, or a timeout + BOOST_ASSERT(ec == + net::error::operation_aborted); + // timeout handler invoked? + if(state().timeout) + { + // yes, socket already closed + ec = beast::error::timeout; + state().timeout = false; + } + goto upcall; + } + impl_->on_timer(this->get_executor()); + + // Allow at least one byte, otherwise + // bytes_transferred could be 0. + amount = std::max( + available_bytes(), 1); + } + BOOST_ASIO_CORO_YIELD - async_perform( - std::integral_constant{}); + async_perform(amount, is_read{}); if(state().timer.expiry() != never()) { @@ -239,6 +384,7 @@ public: upcall: pg_.reset(); + transfer_bytes(bytes_transferred); this->invoke_now(ec, bytes_transferred); } } @@ -249,12 +395,13 @@ public: namespace detail { template< - class Protocol, class Executor, class Handler> + class Protocol, class Executor, class RatePolicy, + class Handler> class basic_stream_connect_op : public async_op_base { - using stream_type = - beast::basic_stream; + using stream_type = beast::basic_stream< + Protocol, Executor, RatePolicy>; using timeout_handler = typename stream_type::timeout_handler; @@ -390,8 +537,8 @@ public: //------------------------------------------------------------------------------ -template -basic_stream:: +template +basic_stream:: ~basic_stream() { // the shared object can outlive *this, @@ -400,17 +547,17 @@ basic_stream:: impl_->close(); } -template +template template -basic_stream:: +basic_stream:: basic_stream(Args&&... args) : impl_(boost::make_shared( std::forward(args)...)) { } -template -basic_stream:: +template +basic_stream:: basic_stream(basic_stream&& other) : impl_(boost::make_shared( std::move(*other.impl_))) @@ -420,9 +567,9 @@ basic_stream(basic_stream&& other) //------------------------------------------------------------------------------ -template +template auto -basic_stream:: +basic_stream:: release_socket() -> socket_type { @@ -430,9 +577,9 @@ release_socket() -> return std::move(impl_->socket); } -template +template void -basic_stream:: +basic_stream:: expires_after(std::chrono::nanoseconds expiry_time) { // If assert goes off, it means that there are @@ -455,9 +602,9 @@ expires_after(std::chrono::nanoseconds expiry_time) expiry_time) == 0); } -template +template void -basic_stream:: +basic_stream:: expires_at( net::steady_timer::time_point expiry_time) { @@ -481,36 +628,37 @@ expires_at( expiry_time) == 0); } -template +template void -basic_stream:: +basic_stream:: expires_never() { impl_->reset(); } -template +template void -basic_stream:: +basic_stream:: cancel() { error_code ec; impl_->socket.cancel(ec); + impl_->timer.cancel(); } -template +template void -basic_stream:: +basic_stream:: close() { impl_->close(); } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE(ConnectHandler, void(error_code)) -basic_stream:: +basic_stream:: async_connect( endpoint_type const& ep, ConnectHandler&& handler) @@ -518,17 +666,18 @@ async_connect( BOOST_BEAST_HANDLER_INIT( ConnectHandler, void(error_code)); detail::basic_stream_connect_op< - Protocol, Executor, BOOST_ASIO_HANDLER_TYPE( + Protocol, Executor, RatePolicy, + BOOST_ASIO_HANDLER_TYPE( ConnectHandler, void(error_code))>(*this, ep, std::forward(handler)); return init.result.get(); } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void(error_code, std::size_t)) -basic_stream:: +basic_stream:: async_read_some( MutableBufferSequence const& buffers, ReadHandler&& handler) @@ -544,11 +693,11 @@ async_read_some( return init.result.get(); } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(error_code, std::size_t)) -basic_stream:: +basic_stream:: async_write_some( ConstBufferSequence const& buffers, WriteHandler&& handler) @@ -567,20 +716,21 @@ async_write_some( //------------------------------------------------------------------------------ template< - class Protocol, class Executor, + class Protocol, class Executor, class RatePolicy, class EndpointSequence, class RangeConnectHandler, class> BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, void(error_code, typename Protocol::endpoint)) async_connect( - basic_stream& stream, + basic_stream& stream, EndpointSequence const& endpoints, RangeConnectHandler&& handler) { BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, void(error_code, typename Protocol::endpoint)); - detail::basic_stream_connect_op( stream, endpoints, detail::any_endpoint{}, @@ -589,7 +739,7 @@ async_connect( } template< - class Protocol, class Executor, + class Protocol, class Executor, class RatePolicy, class EndpointSequence, class ConnectCondition, class RangeConnectHandler, @@ -597,14 +747,15 @@ template< BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, void (error_code, typename Protocol::endpoint)) async_connect( - basic_stream& stream, + basic_stream& stream, EndpointSequence const& endpoints, ConnectCondition connect_condition, RangeConnectHandler&& handler) { BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, void(error_code, typename Protocol::endpoint)); - detail::basic_stream_connect_op( stream, endpoints, connect_condition, @@ -613,19 +764,20 @@ async_connect( } template< - class Protocol, class Executor, + class Protocol, class Executor, class RatePolicy, class Iterator, class IteratorConnectHandler> BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, void (error_code, Iterator)) async_connect( - basic_stream& stream, + basic_stream& stream, Iterator begin, Iterator end, IteratorConnectHandler&& handler) { BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, void(error_code, Iterator)); - detail::basic_stream_connect_op( stream, begin, end, detail::any_endpoint{}, @@ -634,21 +786,22 @@ async_connect( } template< - class Protocol, class Executor, + class Protocol, class Executor, class RatePolicy, class Iterator, class ConnectCondition, class IteratorConnectHandler> BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, void (error_code, Iterator)) async_connect( - basic_stream& stream, + basic_stream& stream, Iterator begin, Iterator end, ConnectCondition connect_condition, IteratorConnectHandler&& handler) { BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, void(error_code, Iterator)); - detail::basic_stream_connect_op( stream, begin, end, connect_condition, @@ -663,20 +816,22 @@ async_connect( #if ! BOOST_BEAST_DOXYGEN -template +template< + class Protocol, class Executor, class RatePolicy> void beast_close_socket( - basic_stream& stream) + basic_stream& stream) { error_code ec; stream.socket().close(ec); } -template +template< + class Protocol, class Executor, class RatePolicy> void teardown( websocket::role_type role, - basic_stream& stream, + basic_stream& stream, error_code& ec) { using beast::websocket::teardown; @@ -684,12 +839,12 @@ teardown( } template< - class Protocol, class Executor, + class Protocol, class Executor, class RatePolicy, class TeardownHandler> void async_teardown( websocket::role_type role, - basic_stream& stream, + basic_stream& stream, TeardownHandler&& handler) { using beast::websocket::async_teardown; diff --git a/include/boost/beast/core/rate_policy.hpp b/include/boost/beast/core/rate_policy.hpp new file mode 100644 index 00000000..49876318 --- /dev/null +++ b/include/boost/beast/core/rate_policy.hpp @@ -0,0 +1,222 @@ +// +// 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_RATE_POLICY_HPP +#define BOOST_BEAST_CORE_RATE_POLICY_HPP + +#include +#include +#include + +namespace boost { +namespace beast { + +/** Helper class to assist implementing a RatePolicy. + + This class is used by the implementation to gain access to the + private members of a user-defined object meeting the requirements + of RatePolicy. To use it, simply declare it as a friend + in your class: + + @par Example + @code + class custom_rate_policy + { + friend class beast::rate_policy_access; + ... + @endcode + + @par Concepts + + @li RatePolicy + + @see @ref beast::basic_stream +*/ +class rate_policy_access +{ +private: + template + friend class basic_stream; + + template + static + std::size_t + available_read_bytes(Policy& policy) + { + return policy.available_read_bytes(); + } + + template + static + std::size_t + available_write_bytes(Policy& policy) + { + return policy.available_write_bytes(); + } + + template + static + void + transfer_read_bytes( + Policy& policy, std::size_t n) + { + return policy.transfer_read_bytes(n); + } + + template + static + void + transfer_write_bytes( + Policy& policy, std::size_t n) + { + return policy.transfer_write_bytes(n); + } + + template + static + void + on_timer(Policy& policy) + { + return policy.on_timer(); + } +}; + +//------------------------------------------------------------------------------ + +/** A rate policy with unlimited throughput. + + This rate policy places no restrictions on read and write + bandwidth utilization. + + @par Concepts + + @li RatePolicy + + @see @ref beast::basic_stream +*/ +class unlimited_rate_policy +{ + static std::size_t constexpr all = + (std::numeric_limits::max)(); + +private: + friend class rate_policy_access; + + std::size_t + available_read_bytes() + { + return all; + } + + std::size_t + available_write_bytes() + { + return all; + } + + void + transfer_read_bytes(std::size_t) + { + } + + void + transfer_write_bytes(std::size_t) + { + } + + void + on_timer() + { + } +}; + +//------------------------------------------------------------------------------ + +/** A rate policy with simple, configurable limits on read and write throughput. + + This rate policy allows for simple individual limits on the amount + of bytes per second allowed for reads and writes. + + @par Concepts + + @li RatePolicy + + @see @ref beast::basic_stream +*/ +class simple_rate_policy +{ + friend class rate_policy_access; + + static std::size_t constexpr all = + std::numeric_limits::max(); + + std::size_t rd_remain_ = all; + std::size_t wr_remain_ = all; + std::size_t rd_limit_ = all; + std::size_t wr_limit_ = all; + + std::size_t + available_read_bytes() + { + return rd_remain_; + } + + std::size_t + available_write_bytes() + { + return wr_remain_; + } + + void + transfer_read_bytes(std::size_t n) + { + if( rd_remain_ != all) + rd_remain_ = + (n < rd_remain_) ? rd_remain_ - n : 0; + } + + void + transfer_write_bytes(std::size_t n) + { + if( wr_remain_ != all) + wr_remain_ = + (n < wr_remain_) ? wr_remain_ - n : 0; + } + + void + on_timer() + { + rd_remain_ = rd_limit_; + wr_remain_ = wr_limit_; + } + +public: + /// Set the limit of bytes per second to read + void + read_limit(std::size_t bytes_per_second) + { + rd_limit_ = bytes_per_second; + if( rd_remain_ > bytes_per_second) + rd_remain_ = bytes_per_second; + } + + /// Set the limit of bytes per second to write + void + write_limit(std::size_t bytes_per_second) + { + wr_limit_ = bytes_per_second; + if( wr_remain_ > bytes_per_second) + wr_remain_ = bytes_per_second; + } +}; + +} // beast +} // boost + +#endif diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt index 0be21dd7..6fe50495 100644 --- a/test/beast/core/CMakeLists.txt +++ b/test/beast/core/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable (tests-beast-core make_strand.cpp multi_buffer.cpp ostream.cpp + rate_policy.cpp read_size.cpp saved_handler.cpp span.cpp diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile index 21a5e6f4..3bab6352 100644 --- a/test/beast/core/Jamfile +++ b/test/beast/core/Jamfile @@ -47,6 +47,7 @@ local SOURCES = make_strand.cpp multi_buffer.cpp ostream.cpp + rate_policy.cpp read_size.cpp saved_handler.cpp span.cpp diff --git a/test/beast/core/basic_stream.cpp b/test/beast/core/basic_stream.cpp index 88b18bfa..a3534b06 100644 --- a/test/beast/core/basic_stream.cpp +++ b/test/beast/core/basic_stream.cpp @@ -31,6 +31,18 @@ namespace boost { namespace beast { +#if 0 +template class basic_stream< + net::ip::tcp, + net::executor, + unlimited_rate_policy>; + +template class basic_stream< + net::ip::tcp, + net::executor, + simple_rate_policy>; +#endif + namespace { template diff --git a/test/beast/core/rate_policy.cpp b/test/beast/core/rate_policy.cpp new file mode 100644 index 00000000..c2099124 --- /dev/null +++ b/test/beast/core/rate_policy.cpp @@ -0,0 +1,34 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include + +namespace boost { +namespace beast { + +class rate_policy_test : public unit_test::suite +{ +public: + void + run() override + { + unlimited_rate_policy{}; + simple_rate_policy{}; + + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,core,rate_policy); + +} // beast +} // boost