Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bdlcc_objectpool
[Package bdlcc]

Provide a thread-safe object pool. More...

Namespaces

namespace  bdlcc

Detailed Description

Outline
Purpose:
Provide a thread-safe object pool.
Classes:
bdlcc::ObjectPool thread-safe container of managed objects
bdlcc::ObjectPoolFunctors namespace for resetter/creator implementations
See also:
Component bdlcc_sharedobjectpool
Description:
This component provides a generic thread-safe pool of objects, bdlcc::ObjectPool, using the acquire-release idiom and a struct with useful functors for a pool of objects, bdlcc::ObjectPoolFunctors. An object pool provides two main methods: getObject, which returns an object from the pool, and releaseObject, which returns an object to the pool for further reuse (thus avoiding the overhead of object construction and destruction). A major requirement of using the object pool is that any call to getObject can be satisfied by any object in the pool.
Thread Safety:
The bdlcc::ObjectPool class template is fully thread-safe (see bsldoc_glossary|Fully Thread-Safe), assuming that the allocator is fully thread-safe. Each method is executed by the calling thread.
Object Construction and Destruction:
The object pool owns the memory required to store the pooled objects, and manages the construction, resetting, and destruction of objects. The user may supply functors to create objects and to reset them to a valid state for their return to the pool. Alternatively, this component supplies reasonable defaults. Upon destruction, the object pool deallocates all memory associated with the objects in the pool.
The object pool also implements the bdlma::Factory protocol for TYPE. Its createObject and deleteObject methods are provided only for this purpose and should not be invoked directly (they are just synonyms for getObject and releaseObject, respectively). The pool can thus be used anywhere a bdlma::Factory (or, therefore, a bdlma::Deleter) is expected.
Integrating with bslma::ManagedPtr and bsl::shared_ptr:
A bdlcc::ObjectPool is designed to work with both managed and shared pointer types. Note however, that bdlcc_sharedobjectpool is an object-pool specifically designed for use with shared pointers.
Because bdlcc::ObjectPool provides a deleteObject method, it can serve as a factory of both bslma::ManagedPtr and bsl::shared_ptr objects. For example, to create a managed pointer from an object pool of bsl::string objects: To create a shared pointer (using the same object pool):
  bslma::Allocator *allocator = bslma::Default::allocator();
  bsl::shared_ptr<bsl::string> sharedPtr(pool.getObject(), &pool, allocator);
Note that an allocator is a required argument to the bsl::shared_ptr constructor used here, and the provided allocator is used to supply memory for the internal representation of the pointer, and not to allocate memory for the object itself.
Creator and Resetter Template Contract:
bdlcc::ObjectPool is templated on two types CREATOR and RESETTER in addition to the underlying object TYPE. Objects of these types may be provided at construction. The namespace bdlcc::ObjectPoolFunctors provides several commonly used implementations. The creator will be invoked as: void(*)(void*, bslma::Allocator*). The resetter will be invoked as: void(*)(TYPE*). The creator functor is called to construct a new object of the parameterized TYPE when the pool must be expanded (and thus it typically invokes placement new and passes its allocator argument to the constructor of TYPE). The resetter functor is called before each object is returned to the pool, and is required to put the object into a state such that it is ready to be reused. The defaults for these types are as follows: bdlcc::ObjectPoolFunctors::Nil is a no-op; it is only suitable if the objects stored in the pool are always in a valid state to be reused. Otherwise another kind of RESETTER should be provided. In bdlcc::ObjectPoolFunctors, the classes Clear, RemoveAll, and Reset are all acceptable types for RESETTER. Since these functor types are fully inlined, it is generally most efficient to define reset (or clear or removeAll) in the underlying TYPE and allow the functor to call that method. The CREATOR functor defaults to an object that invokes the default constructor with placement new, passing the allocator argument if the type traits of the object indicate it uses an allocator (see bslalg_typetraits). If a custom creator functor or a custom CREATOR type is specified, it is the user's responsibility to ensure that it correctly passes its allocator argument to the constructor of TYPE if TYPE takes an allocator.
Exception safety:
There are two potential sources of exceptions in this component: memory allocation and object construction. The object pool is exception-neutral with full guarantee of rollback for the following methods: if an exception is thrown in getObject, reserveCapacity, or increaseCapacity, then the pool is in a valid unmodified state (i.e., identical to its state prior to the call to getObject). No other method of bdlcc::ObjectPool can throw.
Pool replenishment policy:
The growBy parameter can be specified in the pool's constructor to instruct the pool how to increase its capacity each time the pool is depleted. If growBy is positive, the pool always replenishes itself with enough objects to satisfy at least growBy object requests before the next replenishment. If growBy is negative, the pool will increase its capacity geometrically until it exceeds the internal maximum (which is implementation-defined), and after that it will be replenished with constant number of objects. If growBy is not specified, it defaults to -1 (i.e., geometric increase beginning at 1).
Usage:
This section illustrates intended use of this component.
Example 1: Handling Database Queries:
In this example, we simulate a database server accepting queries from clients and executing each query in a separate thread. Client requests are simulated by function getClientQuery which returns a query to be executed. The class Query encapsulates a database query and queryFactory is an object of a query factory class QueryFactory.
  enum {
      k_CONNECTION_OPEN_TIME  = 100,  // (simulated) time to open a
                                      // connection (in microseconds)

      k_CONNECTION_CLOSE_TIME = 8,    // (simulated) time to close a
                                      // connection (in microseconds)

      k_QUERY_EXECUTION_TIME  = 4     // (simulated) time to execute a query
                                      // (in microseconds)
  };

  class my_DatabaseConnection
      // This class simulates a database connection.
  {
    public:
      my_DatabaseConnection()
      {
          bslmt::ThreadUtil::microSleep(k_CONNECTION_OPEN_TIME);
      }

      ~my_DatabaseConnection()
      {
          bslmt::ThreadUtil::microSleep(k_CONNECTION_CLOSE_TIME);
      }

      void executeQuery(Query *query)
      {
          bslmt::ThreadUtil::microSleep(k_QUERY_EXECUTION_TIME);
          (void)query;
      }
  };
The server runs several threads which, on each iteration, obtain a new client request from the query factory, and process it, until the desired total number of requests is achieved.
  extern "C" void serverThread(bsls::AtomicInt *queries,
                               int              max,
                               void             (*queryHandler)(Query*))
  {
      while (++(*queries) <= max) {
          Query *query = queryFactory->createQuery();
          queryHandler(query);
      }
  }
We first give an implementation that does not uses the object pool. Later we will give an implementation using an object pool to manage the database connections. We also keep track of total response time for each case. When object pool is not used, each thread, in order to execute a query, creates a new database connection, calls its executeQuery method to execute the query and finally closes the connection.
  extern "C" void queryHandler1(Query *query)
      // Handle the specified 'query' without using an objectpool.
  {
      bsls::Types::Int64 t1 = bsls::TimeUtil::getTimer();
      my_DatabaseConnection connection;
      connection.executeQuery(query);
      bsls::Types::Int64 t2 = bsls::TimeUtil::getTimer();

      totalResponseTime1 += t2 - t1;

      queryFactory->destroyQuery(query);

      // 'connection' is implicitly destroyed on function return.
  }
The main thread starts and joins these threads:
  enum {
      k_NUM_THREADS = 8,
      k_NUM_QUERIES = 10000
  };

  bsls::AtomicInt numQueries(0);
  bslmt::ThreadGroup tg;

  tg.addThreads(bdlf::BindUtil::bind(&serverThread,
                                     &numQueries,
                                     static_cast<int>(k_NUM_QUERIES),
                                     &queryHandler1),
                k_NUM_THREADS);
  tg.joinAll();
In above strategy, clients always incur the delay associated with opening and closing a database connection. Now we show an implementation that will use object pool to pool the database connections.
Object Pool Creation and Functor Argument:
In order to create an object pool, we may specify, at construction time, a functor encapsulating object creation. The pool invokes this functor to create an object in a memory location supplied by the allocator specified at construction and owned by the pool. By default, the creator invokes the default constructor of the underlying type, passing the pool's allocator if the type uses the bslma::Allocator protocol to supply memory (as specified by the "Uses Bslma Allocator" trait, see bslalg_typetraits). If this behavior is not sufficient, we can supply our own functor for type creation.
Creating an Object Pool that Constructs Default Objects:
When the default constructor of our type is sufficient, whether or not that type uses bslma::Allocator, we can simply use the default behavior of bdlcc::ObjectPool:
Creating an Object Pool that Constructs Non-Default Objects:
In this example, if we decide that connection IDs must be supplied to objects allocated from the pool, we must define a function which invokes placement new appropriately. When using a custom creator functor, it is the responsibility of client code to pass the pool's allocator (supplied as the second argument to the functor) to the new object if it uses bslma::Allocator.
    void createConnection(void *arena, bslma::Allocator *alloc, int id)
    {
       new (arena) my_DatabaseConnection(id, alloc);
    }
then... Whichever creator we choose, the modified server looks like
  connectionPool = &pool;

  for (int i = 0; i < k_NUM_QUERIES; ++i) {
      my_Query *query = getClientQuery();
      bslmt::ThreadUtil::create(&threads[i], queryHandler2, (void *)query);
  }
  for (int i = 0; i < k_NUM_QUERIES; ++i) {
      bslmt::ThreadUtil::join(threads[i]);
  }
Modified queryHandler:
Now each thread, instead of creating a new connection, gets a connection from the object pool. After using the connection, the client returns it back to the pool for further reuse. The modified queryHandler is following.
    bdlcc::ObjectPool<my_DatabaseConnection> *connectionPool;

    void queryHandler2(Query *query)
        // Process the specified 'query'.
    {
        bsls::Types::Int64 t1 = bsls::TimeUtil::getTimer();
        my_DatabaseConnection *connection = connectionPool->getObject();
        connection->executeQuery(query);
        bsls::Types::Int64 t2 = bsls::TimeUtil::getTimer();

        totalResponseTime2 += t2 - t1;

        connectionPool->releaseObject(connection);
        queryFactory->destroyQuery(query);
    }
The total response time for each strategy is:
 totalResponseTime1 = 199970775520
 totalResponseTime2 = 100354490480