BDE 4.14.0 Production release
|
Provide mechanism for limiting the rate at which actions may occur.
This component provides a mechanism, bdlmt::Throttle
, that can be used by clients to regulate the frequency at which actions can be taken. Clients initialize a Throttle
with configuration values for nanosecondsPerAction
, maxSimultaneousActions
, and clock type. Then clients request permission from this component to execute actions. The component keeps track of the number of actions requested, and over time throttles the average number of actions permitted to a rate of 1 / nanosecondsPerAction
(actions-per-nanosecond). So, for example, to limit the average rate of actions permitted to 10 actions per second (10 actions / one billion nanoseconds), the value for nanosecondsPerAction
would be 100000000 (which is one billion / 10).
As clients request permission to perform actions the component accumulates a time debt for those actions that dissipates over time. The maximum value for this time debt is given by maxSimultaneousActions * nanosecondsPerAction
. The maxSimultaneousActions
configuration parameter thereby limits the maximum number of actions that can be simultaneously permitted.
This behavior is known as a "leaky-bucket" algorithm: actions permitted place water in the bucket, the passage of time drains water from the bucket, and the bucket has a maximum capacity. Actions are permitted when there is enough empty room in the bucket that the water placed won't overflow it. A leaky bucket is an efficiently implementable approximation for allowing a certain number of actions over a window of time.
The component bsls::SystemClockType
supplies the enumeration indicating the system clock by which this component measures time. By default, this component uses bsls::SystemClock::e_MONOTONIC
. If the clock type indicated at initialization is bsls::SystemClockType::e_MONOTONIC
, the timeout should be expressed as an absolute offset since the epoch of this clock (which matches the epoch used in bsls::SystemTime::now(bsls::SystemClockType::e_MONOTONIC)
. If the clock type indicated at initialization is bsls::SystemClockType::e_REALTIME
, the time should be expressed as an absolute offset since 00:00:00 UTC, January 1, 1970 (which matches the epoch used in bsls::SystemTime::now(bsls::SystemClockType::e_REALTIME)
.
bdlmt::Throttle
is fully thread-safe, meaning that all non-initialization operations on a given instance instance can be safely invoked simultaneously from multiple threads.
Throttle
objects declared with static storage duration must be initialized using one of the BDLMT_THROTTLE_INIT*
macros. In order to provide thread safety on C++03 compilers (which do not have constexpr
), these macros perform aggregate initialization that can be evaluated at compile time.
One of these macros must be used to aggregate initialize bdlmt::Throttle
objects that have static storage duration – the values are guaranteed to be evaluated at compile-time, avoiding race conditions.
bdlmt::Throttle
does not provide overloads for requestPermission
and requestPermissionIfValid
that take a bsl::chrono::time_point
as a representation for now
. There are three reasons for this. First, converting between different clocks is expensive, involving at least two calls to now
(one for the clock defined in the time point, and one for the clock used by the throttle). This is supposed to be a performant component, allowing the caller to avoid the call to bsls::SystemTime::now
by passing in their own value of now
. Second, it is inherently imprecise; conversions with the same input can return slightly different results, depending on the scheduling of the calls to now
. Third, we have no way to support clocks that run at different rates.
In this section we show intended usage of this component.
Suppose we have an error reporting function reportError
, that prints an error message to a log stream. There is a possibility that reportError
will be called very frequently, and that reports of this error will overwhelm the other contents of the log, so we want to throttle the number of times this error will be reported. For our application we decide that we want to see at most 10 reports of the error at any given time, and that if the error is occurring continuously, that we want a maximum sustained rate of one error report every five seconds.
First, we declare the signature of our reportError
function:
Then, we define the maximum number of traces that can happen at a time to be 10:
Next, we define the minimum interval between subsequent reported errors, if errors are being continuously reported to be one report every 5 seconds. Note that the units are nanoseconds, which must be represented using a 64 bit integral value:
Then, we declare our throttle
object and use the BDLMT_THROTTLE_INIT
macro to initialize it, using the two above constants. Note that the two above constants MUST be calculated at compile-time, which means, among other things, that they can't contain any floating point sub-expressions:
Now, we call requestPermission
at run-time to determine whether to report the next error to the log:
Finally, we write the message to the log: