// ball_fixedsizerecordbuffer.h                                       -*-C++-*-
#ifndef INCLUDED_BALL_FIXEDSIZERECORDBUFFER
#define INCLUDED_BALL_FIXEDSIZERECORDBUFFER

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

//@PURPOSE: Provide a thread-safe fixed-size buffer of record handles.
//
//@CLASSES:
//  ball::FixedSizeRecordBuffer: thread-safe fixed-size buffer of records
//
//@SEE_ALSO: ball_recordbuffer
//
//@DESCRIPTION: This component provides a concrete thread-safe implementation
// of the `ball::RecordBuffer` protocol, `ball::FixedSizeRecordBuffer`:
// ```
//             ( ball::FixedSizeRecordBuffer )
//                           |              ctor
//                           V
//                 ( ball::RecordBuffer )
//                                          dtor
//                                          beginSequence
//                                          endSequence
//                                          popBack
//                                          popFront
//                                          pushBack
//                                          pushFront
//                                          removeAll
//                                          length
//                                          back
//                                          front
// ```
// The thread-safe class `ball::FixedSizeRecordBuffer` manages record handles
// (specifically, the instances of `bsl::shared_ptr<ball::Record>`) in a
// double-ended buffer.  At any time, the sum of sizes of all records contained
// in a `ball::FixedSizeRecordBuffer` object *plus* the amount of memory
// allocated by the `ball::FixedSizeRecordBuffer` object itself is guaranteed
// to be less than or equal to an upper bound specified at creation.  In order
// to accommodate a record, existing records may be removed from the buffer
// (see below).  The `ball::FixedSizeRecordBuffer` class provides methods to
// push a record handle into either end (back or front) of the buffer
// (`pushBack` and `pushFront`), to obtain read-only access to the log record
// positioned at either end (`back` and `front`) and to remove the record
// positioned at either end (`popBack` and `popFront`).  In order to
// accommodate a `pushBack` request, the records from the front end of the
// buffer may be removed.  Similarly, in order to accommodate a `pushFront`
// request, the records from the back end of the buffer may be removed.  If a
// record can not be accommodated in the buffer, it is silently (but otherwise
// safely) discarded.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Basic Usage
/// - - - - - - - - - - -
// In the following example we demonstrate creation of a limited record buffer
// followed by concurrent access to it by multiple threads.
// ```
// enum {
//     KILO_BYTE      = 1024,   // one kilo is (2^10) bytes
//     MAX_TOTAL_SIZE = 32 * K, // 'maxTotalSize' parameter
//     NUM_ITERATIONS = 1000,   // number of iterations
//     NUM_THREADS    = 4       // number of threads
// };
// bslma::Allocator *basicAllocator = bslma::Default::defaultAllocator();
// ```
// First we create a record buffer.
// ```
// bdlma::DefaultDeleter<ball::Record> recordDeleter(basicAllocator);
// ball::FixedSizeRecordBuffer recordBuffer(MAX_TOTAL_SIZE, basicAllocator);
// ```
// Note that since the record buffer will contain shared pointers to the
// records, `recordDeleter` must be created before `recordBuffer` to ensure
// that the former has the longer lifetime.
//
// Now we create several threads each of which repeatedly performs the
// following operations in a tight loop;
//  (1) create a record;
//  (2) build a message and store it into the record;
//  (3) create a record handle;
//  (4) push this record handle at the back end of the record buffer
//
// ```
// void *workerThread(void *arg)
// {
//     int id = (int)arg; // thread id
//     for (int i = 0; i < NUM_ITERATIONS; ++i) {
//         ball::Record *record =
//                       new (*basicAllocator) ball::Record(basicAllocator);
//
//         // build a message
//         enum { MAX_SIZE = 100 };
//         char msg[MAX_SIZE];
//         sprintf(msg, "message no. %d from thread no. %d", i, id);
//
//         record->getFixedFields().setMessage(msg);
//
//         bsl::shared_ptr<ball::Record>
//           handle(record, &recordDeleter, basicAllocator);
//
//         recordBuffer.pushBack(handle);
//     }
// ```
// After completing the loop each thread iterates, in LIFO order, over all of
// the records contained in record buffer.
// ```
//     // print messages in LIFO order
//     recordBuffer.beginSequence();
//     while (recordBuffer.length()) {
//         const ball::Record &rec = recordBuffer.back();
//         bsl::cout << rec.getFixedFields().message() << bsl::endl;
//         recordBuffer.popBack();
//     }
//     recordBuffer.endSequence();
//
//     return NULL;
// }
// ```

#include <balscm_version.h>

#include <ball_countingallocator.h>
#include <ball_record.h>
#include <ball_recordbuffer.h>

#include <bslmt_lockguard.h>
#include <bslmt_recursivemutex.h>

#include <bslma_allocator.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsls_keyword.h>

#include <bsl_deque.h>
#include <bsl_memory.h>

namespace BloombergLP {
namespace ball {

                          // ===========================
                          // class FixedSizeRecordBuffer
                          // ===========================

/// This class provides a concrete, thread-safe implementation of the
/// `RecordBuffer` protocol.  This class is a mechanism.  At any time, the
/// sum of sizes of all records contained in a `FixedSizeRecordBuffer`
/// object *plus* the amount of memory allocated by the
/// `FixedSizeRecordBuffer` object itself is guaranteed to be less than or
/// equal to an upper bound specified at creation.  The class is
/// thread-safe, except that the methods `front` and `back` must be called
/// after locking the buffer by invoking `beginSequence`.  In order to
/// accommodate a `pushBack` request, the records from the front end of the
/// buffer may be removed.  Similarly, in order to accommodate a `pushFront`
/// request, the records from the back end of the buffer may be removed.  If
/// a record can not accommodate in the buffer, it is silently (but
/// otherwise safely) discarded.
class FixedSizeRecordBuffer: public RecordBuffer {

    // DATA
    mutable bslmt::RecursiveMutex d_mutex;       // synchronizes access to the
                                                 // buffer

    int                           d_maxTotalSize;
                                                 // maximum possible sum of
                                                 // sizes of contained records

    int                           d_currentTotalSize;
                                                 // current sum of sizes of
                                                 // contained records

    // Implementation note: The order of the following data members is
    // required to ensure proper initialization order.

    CountingAllocator             d_allocator;   // allocator for `d_deque`

    bsl::deque<bsl::shared_ptr<Record> >
                                  d_deque;       // deque of record handles

    // NOT IMPLEMENTED
    FixedSizeRecordBuffer(const FixedSizeRecordBuffer&);
    FixedSizeRecordBuffer& operator=(const FixedSizeRecordBuffer&);

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(FixedSizeRecordBuffer,
                                   bslma::UsesBslmaAllocator);

    // CREATORS

    /// Create a limited record buffer such that at any time the sum of
    /// sizes of all records contained *plus* the amount of memory allocated
    /// by this object is guaranteed to be less than or equal to the
    /// specified `maxTotalSize`.  Optionally specify a `basicAllocator`
    /// used to supply memory.  If `basicAllocator` is 0, the currently
    /// installed default allocator is used.  The behavior is undefined
    /// unless `maxTotalSize > 0`.
    FixedSizeRecordBuffer(int               maxTotalSize,
                          bslma::Allocator *basicAllocator = 0);

    /// Remove all record handles from this record buffer and destroy this
    /// record buffer.
    ~FixedSizeRecordBuffer() BSLS_KEYWORD_OVERRIDE;

    // MANIPULATORS

    /// *Lock* this record buffer so that a sequence of method invocations
    /// on this record buffer can occur uninterrupted by other threads.  The
    /// buffer will remain *locked* until `endSequence` is called.  It is
    /// valid to invoke other methods on this record buffer between the
    /// calls to `beginSequence` and `endSequence` (the implementation
    /// guarantees this by employing a recursive mutex).
    void beginSequence() BSLS_KEYWORD_OVERRIDE;

    /// *Unlock* this record buffer, thus allowing other threads to access
    /// it.  The behavior is undefined unless the buffer is already *locked*
    /// by `beginSequence`.
    void endSequence() BSLS_KEYWORD_OVERRIDE;

    /// Remove from this record buffer the record handle positioned at the
    /// back end of the buffer.  The behavior is undefined unless
    /// `0 < length()`.
    void popBack() BSLS_KEYWORD_OVERRIDE;

    /// Remove from this record buffer the record handle positioned at the
    /// front end of the buffer.  The behavior is undefined unless
    /// `0 < length()`.
    void popFront() BSLS_KEYWORD_OVERRIDE;

    /// Push the specified `handle` at the back end of this record buffer.
    /// Return 0 on success, and a non-zero value otherwise.  In order to
    /// accommodate a record, the records from the front end of the buffer
    /// may be removed.  If a record can not be accommodated in the buffer,
    /// it is silently discarded.
    int pushBack(const bsl::shared_ptr<Record>& handle) BSLS_KEYWORD_OVERRIDE;

    /// Push the specified `handle` at the front end of this record buffer.
    /// Return 0 on success, and a non-zero value otherwise.  In order to
    /// accommodate a record, the records from the end end of the buffer may
    /// be removed.  If a record can not be accommodated in the buffer, it
    /// is silently discarded.
    int pushFront(const bsl::shared_ptr<Record>& handle) BSLS_KEYWORD_OVERRIDE;

    /// Remove all record handles stored in this record buffer.  Note that
    /// `length()` is now 0.
    void removeAll() BSLS_KEYWORD_OVERRIDE;

    // ACCESSORS

    /// Return a reference of the shared pointer referring to the record
    /// positioned at the back end of this record buffer.  The behavior is
    /// undefined unless this record buffer has been locked by the
    /// `beginSequence` method and unless `0 < length()`.
    const bsl::shared_ptr<Record>& back() const BSLS_KEYWORD_OVERRIDE;

    /// Return a reference of the shared pointer referring to the record
    /// positioned at the front end of this record buffer.  The behavior is
    /// undefined unless this record buffer has been locked by the
    /// `beginSequence` method and unless `0 < length()`.
    const bsl::shared_ptr<Record>& front() const BSLS_KEYWORD_OVERRIDE;

    /// Return the number of record handles in this record buffer.
    int length() const BSLS_KEYWORD_OVERRIDE;
};

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

                          // ---------------------------
                          // class FixedSizeRecordBuffer
                          // ---------------------------

// CREATORS
inline
FixedSizeRecordBuffer::FixedSizeRecordBuffer(int               maxTotalSize,
                                             bslma::Allocator *basicAllocator)
: d_maxTotalSize(maxTotalSize)
, d_currentTotalSize(0)
, d_allocator(basicAllocator)
, d_deque(&d_allocator)
{
}

// MANIPULATORS
inline
void FixedSizeRecordBuffer::beginSequence()
{
    d_mutex.lock();
}

inline
void FixedSizeRecordBuffer::endSequence()
{
    d_mutex.unlock();
}

inline
void FixedSizeRecordBuffer::removeAll()
{
    bslmt::LockGuard<bslmt::RecursiveMutex> guard(&d_mutex);
    d_deque.clear();
    d_currentTotalSize = 0;
}

// ACCESSORS
inline
const bsl::shared_ptr<Record>& FixedSizeRecordBuffer::back() const
{
    bslmt::LockGuard<bslmt::RecursiveMutex> guard(&d_mutex);
    return d_deque.back();
}

inline
const bsl::shared_ptr<Record>& FixedSizeRecordBuffer::front() const
{
    bslmt::LockGuard<bslmt::RecursiveMutex> guard(&d_mutex);
    return d_deque.front();
}

inline
int FixedSizeRecordBuffer::length() const
{
    bslmt::LockGuard<bslmt::RecursiveMutex> guard(&d_mutex);
    return static_cast<int>(d_deque.size());
}

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

#endif

// ----------------------------------------------------------------------------
// Copyright 2015 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 ----------------------------------
