Provide mechanism for limiting the rate at which actions may occur.
More...
Namespaces |
namespace | bdlmt |
Detailed Description
- Outline
-
-
- Purpose:
- Provide mechanism for limiting the rate at which actions may occur.
-
- Classes:
-
-
- Macros:
- BDLMT_THROTTLE_INIT, BDLMT_THROTTLE_INIT_REALTIME, BDLMT_THROTTLE_INIT_ALLOW_ALL, BDLMT_THROTTLE_INIT_ALLOW_NONE, BDLMT_THROTTLE_IF, BDLMT_THROTTLE_IF_REALTIME, BDLMT_THROTTLE_IF_ALLOW_ALL, BDLMT_THROTTLE_IF_ALLOW_NONE
- See also:
- Component bslmt_turnstile, btls_leakybucket
-
- Description:
- 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.
-
- Supported Clock-Types:
- 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)
.
-
- Thread Safety:
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.
-
- Static Throttle Objects:
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.
-
- Macro Reference:
-
- BLDMT_THROTTLE_INIT* macros:
- 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_INIT(maxSimultaneousActions,
nanosecondsPerAction)
BDLMT_THROTTLE_INIT_REALTIME(maxSimultaneousActions,
nanosecondsPerAction)
Initialize this 'Throttle' to limit the average period of actions
permitted to the specified 'nanosecondsPerAction', and the maximum
number of actions allowed at one time to the specified
'maxSimultaneousActions', where time is measured according to the
monotonic system clock. These macros must be used for 'Throttle'
objects having static storage duration. If 'maxSimultaneousActions'
is 0, the throttle will be configured to permit no actions. If
'nanosecondsPerAction' is 0, the throttle will be configured to permit
all actions. Use the '_REALTIME' variant of this macro to use the
real-time system clock to measure time, otherwise (by default) the
monotonic clock is used. The behavior is undefined unless
'0 <= maxSimultaneousActions', '0 <= nanosecondsPerAction',
'0 < maxSimultaneousActions || 0 < nanosecondsPerAction', and
'maxSimultaneousActions * nanosecondsPerAction <= LLONG_MAX'. Note
that floating-point expressions are not allowed in any of the
arguments, as they cannot be evaluated at compile-time on some
platforms.
BDLMT_THROTTLE_INIT_ALLOW_ALL
Initialize this 'Throttle' to allow all actions.
BDLMT_THROTTLE_INIT_ALLOW_NONE
Initialize this 'Throttle' to allow no actions.
-
- BDLMT_THROTTLE_IF* macros:
BDLMT_THROTTLE_IF(maxSimultaneousActions,
nanosecondsPerAction)
BDLMT_THROTTLE_IF_REALTIME(maxSimultaneousActions,
nanosecondsPerAction)
This macro behaves like an 'if' clause, executing the subsequent
statement or block if the time debt incurred by taking a single action
would *not* exceed the maximum allowed time debt indicated by the
specified 'nanosecondsPerAction' and 'maxSimultaneousActions'. If
this 'if' clause is 'true' (and the subsequent statement or block is
executed), then 'nanosecondsPerAction' is added to the time debt
accumulated by this macro instantiation. 'nanosecondsPerAction' is
the minimum average period between actions permitted by this macro
instantiation, and 'nanosecondsPerAction' is the maximum number of
simultaneous actions permitted by this macro instantiation. If
'maxSimultaneousActions' is 0, the 'if' clause will evaluate to
'false'. If 'nanosecondsPerAction' is 0, the 'if' clause will
evaluate to 'true'. Use the '_REALTIME' variant of this macro to use
the real-time system clock to measure time, otherwise (by default) the
monotonic clock is used. The behavior is undefined unless
'0 <= maxSimultaneousActions', '0 <= nanosecondsPerAction', and
'0 < maxSimultaneousActions || 0 < nanosecondsPerAction'. Note that
floating-point expressions are not allowed in any of the arguments, as
they cannot be evaluated at compile-time on some platforms.
BDLMT_THROTTLE_IF_ALLOW_ALL
Create an 'if' statement whose condition is always 'true', always
allowing execution of the statement controlled by it and never
allowing execution of any 'else' clause present.
BDLMT_THROTTLE_IF_ALLOW_NONE
Create an 'if' statement whose condition is always 'false', never
allowing execution of the statement controlled by it and always
allowing execution of any 'else' clause present.
-
- Lack of bsl::chrono-Based Overloads for requestPermission*:
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.
-
- Usage:
- In this section we show intended usage of this component.
-
- Example 1: Error Reporting:
- 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: void reportError(bsl::ostream& stream)
{
Then, we define the maximum number of traces that can happen at a time to be 10: static const int maxSimultaneousTraces = 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: static bdlmt::Throttle throttle = BDLMT_THROTTLE_INIT(
maxSimultaneousTraces, nanosecondsPerSustainedTrace);
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: stream << "Help! I'm being held prisoner in a microprocessor!\n";
}
}