Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bdlmt_throttle
[Package bdlmt]

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:
bdlmt::Throttle a mechanism for limiting the rate at which actions occur
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)
      // Report an error to the specified '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 =
                          5 * bdlt::TimeUnitRatio::k_NANOSECONDS_PER_SECOND;
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:
      if (throttle.requestPermission()) {
Finally, we write the message to the log:
          stream << "Help!  I'm being held prisoner in a microprocessor!\n";
      }
  }