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