// balb_reservationguard.h                                            -*-C++-*-
#ifndef INCLUDED_BALB_RESERVATIONGUARD
#define INCLUDED_BALB_RESERVATIONGUARD

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a generic proctor for rate controlling objects.
//
//@CLASSES:
//  balb::ReservationGuard: a guard for reserving resources from rate limiters.
//
//@SEE_ALSO: balb_leakybucket, balb_ratelimiter
//
//@DESCRIPTION: This component provides generic proctor to automatically
// reserve and release units from a rate controlling object.  The rate
// controlling object can be of any type (typically either a
// 'balb::RateLimiter' or 'balb::LeakyBucket') that provides the following
// methods:
//..
//  void reserve(bsls::Types::Uint64 numOfUnits);
//  void submitReserved(bsls::Types::Uint64 numOfUnits);
//  void cancelReserved(bsls::Types::Uint64 numOfUnits);
//..
// Use 'balb::ReservationGuard' to ensure that reserved units will be correctly
// returned to a rate controlling object in a programming scope.  Note that
// 'balb::ReservationGuard' does not assume ownership of the rate controlling
// object.
//
///Usage
///-----
// This section illustrates the intended use of this component.
//
///Example 1: Guarding units reservation in operations with balb::LeakyBucket
///--------------------------------------------------------------------------
// Suppose that we are limiting the rate of network traffic generation using a
// 'balb::LeakyBucket' object.  We send data buffer over a network interface
// using the 'mySendData' function:
//..
//  bsls::Types::Uint64 mySendData(size_t dataSize);
//      // Send a specified 'dataSize' amount of data over the network.  Return
//      // the amount of data actually sent.  Throw an exception if a network
//      // failure is detected.
//..
// Notice that the 'mySendData' function may throw an exception; therefore, we
// should wait until 'mySendData' returns before indicating the amount of data
// sent to the leaky bucket.
//
// Further suppose that multiple threads are sending network data and sharing
// the same leaky bucket.  If every thread simply checks for overflowing of the
// leaky bucket, send data, and then submit to the leaky bucket, then the rate
// of data usage may exceed the limits imposed by the leaky bucket due to race
// conditions.  We can avoid the this issue by reserving the amount of data
// immediately after checking whether the leaky bucket has overflown and submit
// the reserved amount after the data has been sent.  However, this process
// could lead to the loss of the reserved units (effectively decreasing the
// leaky bucket's capacity) if 'mySendData' throws an exception.
// 'balb::ReservationGuard' is designed to resolve this issue.
//
// First, we define the size of each data chunk and the total size of the data
// to send:
//..
//  const bsls::Types::Uint64 CHUNK_SIZE = 256;
//  bsls::Types::Uint64       bytesSent  = 0;
//  bsls::Types::Uint64       totalSize  = 10 * 1024; // in bytes
//..
// Then, we create a 'balb::LeakyBucket' object to limit the rate of data
// transmission:
//..
//  bsls::Types::Uint64 rate     = 512;
//  bsls::Types::Uint64 capacity = 1536;
//  bsls::TimeInterval  now      = bdlt::CurrentTime::now();
//  balb::LeakyBucket   bucket(rate, capacity, now);
//..
// Next, we send the chunks of data using a loop.  For each iteration, we check
// whether submitting another byte would cause the leaky bucket to overflow:
//..
//  while (bytesSent < totalSize) {
//
//      now = bdlt::CurrentTime::now();
//      if (!bucket.wouldOverflow(now)) {
//..
// Now, if the leaky bucket would not overflow, we create a
// 'balb::ReservationGuard' object to reserve the amount of data to be sent:
//..
//          balb::ReservationGuard<balb::LeakyBucket> guard(&bucket,
//                                                          CHUNK_SIZE);
//..
// Then, we use the 'mySendData' function to send the data chunk over the
// network.  After the data had been sent, we submit the amount of reserved
// data that was actually sent:
//..
//          bsls::Types::Uint64 result;
//          result = mySendData(CHUNK_SIZE);
//          bytesSent += result;
//          guard.submitReserved(result);
//..
// Note that we do not have manually cancel any remaining units reserved by the
// 'balb::ReservationGuard' object either because 'mySendData' threw an
// exception, or the data was only partially sent, because when the guard
// object goes out of scope, all remaining reserved units will be automatically
// cancelled.
//..
//      }
//..
// Finally, if submitting another byte will cause the leaky bucket to overflow,
// then we wait until the submission will be allowed by waiting for an amount
// time returned by the 'calculateTimeToSubmit' method:
//..
//      else {
//          bsls::TimeInterval  timeToSubmit = bucket.calculateTimeToSubmit(
//                                                                        now);
//          bsls::Types::Uint64 uS = timeToSubmit.totalMicroseconds() +
//                                 (timeToSubmit.nanoseconds() % 1000) ? 1 : 0;
//          bslmt::ThreadUtil::microSleep(static_cast<int>(uS));
//      }
//  }
//..

#include <balscm_version.h>

#include <bsls_assert.h>
#include <bsls_asserttest.h>
#include <bsls_types.h>

namespace BloombergLP {
namespace balb {

                           //=======================
                           // class ReservationGuard
                           //=======================

template<class TYPE>
class ReservationGuard {
    // This class template implements a proctor for reserving and cancelling
    // units in a rate controlling object.
    //
    // This class:
    //: o is *exception* *neutral* (agnostic)
    //: o is *const* *thread-safe*
    // For terminology see 'bsldoc_glossary'.

    // DATA
    TYPE                  *d_rateController_p;  // Pointer to the rate
                                                // controlling object in which
                                                // the units are reserved.

    bsls::Types::Uint64     d_unitsReserved;    // Number of units reserved by
                                                // this object.

  private:
    // NOT IMPLEMENTED
    ReservationGuard();
    ReservationGuard& operator =(const ReservationGuard<TYPE>&);
    ReservationGuard(const ReservationGuard<TYPE>&);

  public:
    // CREATORS
    ReservationGuard(TYPE* rateController, bsls::Types::Uint64 numUnits);
        // Create a 'ReservationGuard' object guarding the specified
        // 'rateController' and reserving the specified 'numUnits'.

    ~ReservationGuard();
        // Destroy this object.  Invoke the 'cancelReserved' method for the
        // remaining remaining units reserved by this proctor.

    // MANIPULATORS
    void cancelReserved(bsls::Types::Uint64 numUnits);
        // Cancel the specified 'numUnits' from the reserve units guarded by
        // this object.  Subtract the 'numUnits' from 'unitsReserved' and
        // invoke the 'cancelReserved' method on the guarded object for
        // 'numUnits'.  After this operation, the number of reserved units
        // guarded by this object will be reduced by 'numUnits'.  The behavior
        // is undefined unless 'numUnits <= unitsReserved()'.

    void submitReserved(bsls::Types::Uint64 numUnits);
        // Submit the specified 'numUnits' from the reserve units guarded by
        // this object.  After this operation, the number of reserved units
        // guarded by this object will be reduced by 'numUnits'.  The behavior
        // is undefined unless 'numUnits <= unitsReserved()'.

    // ACCESSORS
    TYPE *ptr() const;
        // Return a pointer to the rate controlling object used by this object.

    bsls::Types::Uint64 unitsReserved() const;
        // Return the number of units reserved by this object.
};

// ============================================================================
//                      INLINE FUNCTION DEFINITIONS
// ============================================================================

                           //-----------------------
                           // class ReservationGuard
                           //-----------------------

// CREATORS
template <class TYPE>
inline
ReservationGuard<TYPE>::ReservationGuard(TYPE*               rateController,
                                         bsls::Types::Uint64 numUnits)
{
    BSLS_ASSERT_SAFE(0 != rateController);

    d_rateController_p = rateController;
    d_unitsReserved    = numUnits;

    d_rateController_p->reserve(numUnits);
}

template <class TYPE>
inline
ReservationGuard<TYPE>::~ReservationGuard()
{
    d_rateController_p->cancelReserved(d_unitsReserved);
}

// ACCESSORS
template <class TYPE>
inline
TYPE *ReservationGuard<TYPE>::ptr() const
{
    return d_rateController_p;
}

template <class TYPE>
inline
bsls::Types::Uint64 ReservationGuard<TYPE>::unitsReserved() const
{
    return d_unitsReserved;
}

// MANIPULATORS
template <class TYPE>
inline
void ReservationGuard<TYPE>::cancelReserved(bsls::Types::Uint64 numUnits)
{
    BSLS_ASSERT_SAFE(numUnits <= d_unitsReserved);

    d_rateController_p->cancelReserved(numUnits);
    d_unitsReserved -= numUnits;
}

template <class TYPE>
inline
void ReservationGuard<TYPE>::submitReserved(bsls::Types::Uint64 numUnits)
{
    BSLS_ASSERT_SAFE(numUnits <= d_unitsReserved);

    d_rateController_p->submitReserved(numUnits);
    d_unitsReserved -= numUnits;
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2021 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------