BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bdlmt_throttle

Detailed Description

Outline

Purpose

Provide mechanism for limiting the rate at which actions may occur.

Classes

Macros

See also
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.
Initialize this 'Throttle' to allow all actions.
Initialize this 'Throttle' to allow no actions.
#define BDLMT_THROTTLE_INIT_REALTIME(maxSimultaneousActions, nanosecondsPerAction)
Definition bdlmt_throttle.h:634
#define BDLMT_THROTTLE_INIT_ALLOW_ALL
Definition bdlmt_throttle.h:648
#define BDLMT_THROTTLE_INIT(maxSimultaneousActions, nanosecondsPerAction)
Definition bdlmt_throttle.h:620
#define BDLMT_THROTTLE_INIT_ALLOW_NONE
Definition bdlmt_throttle.h:649

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.
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.
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.
#define BDLMT_THROTTLE_IF_REALTIME(maxSimultaneousActions, nanosecondsPerAction)
Definition bdlmt_throttle.h:666
#define BDLMT_THROTTLE_IF_ALLOW_NONE
Definition bdlmt_throttle.h:678
#define BDLMT_THROTTLE_IF_ALLOW_ALL
Definition bdlmt_throttle.h:677
#define BDLMT_THROTTLE_IF(maxSimultaneousActions, nanosecondsPerAction)
Definition bdlmt_throttle.h:655

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:

/// Report an error to the specified `stream`.
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:

static const bsls::Types::Int64 nanosecondsPerSustainedTrace =
static const bsls::Types::Int64 k_NANOSECONDS_PER_SECOND
Definition bdlt_timeunitratio.h:217
long long Int64
Definition bsls_types.h:132

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:

maxSimultaneousTraces, nanosecondsPerSustainedTrace);
Definition bdlmt_throttle.h:306

Now, we call requestPermission at run-time to determine whether to report the next error to the log:

if (throttle.requestPermission()) {
bool requestPermission()
Definition bdlmt_throttle.h:571

Finally, we write the message to the log:

stream << "Help! I'm being held prisoner in a microprocessor!\n";
}
}