Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bdlcc_objectcatalog
[Package bdlcc]

Provide an efficient indexed, thread-safe object container. More...

Namespaces

namespace  bdlcc

Detailed Description

Outline
Purpose:
Provide an efficient indexed, thread-safe object container.
Classes:
bdlcc::ObjectCatalog templatized, thread-safe, indexed object container
bdlcc::ObjectCatalogIter thread-safe iterator for bdlcc::ObjectCatalog
See also:
Description:
This component provides a thread-safe and efficient templatized catalog of objects. A bdlcc::ObjectCatalog supports efficient insertion of objects through the add method, which returns a handle that can be used for further reference to the newly added element. An element can be accessed by providing its handle to the find function. Thread-safe design implies that the element is returned by value into an object buffer rather than by reference (see this package documentation for a discussion of thread-safe container design). Likewise, an element can be modified by providing its handle and a new value to the replace method. Finally, an element can be removed by passing its handle to the remove method; the handle is then no longer valid and subsequent calls to find or remove with this handle will return 0.
bdlcc::ObjectCatalogIter provides thread safe iteration through all the objects of an object catalog of parameterized TYPE. The order of the iteration is implementation defined. Thread safe iteration is provided by (read)locking the object catalog during the iterator's construction and unlocking it at the iterator's destruction. This guarantees that during the life time of an iterator, the object catalog can't be modified (however multiple threads can still concurrently read the object catalog).
Note that an object catalog has a maximum capacity of 2^23 items.
Usage:
This section illustrates intended use of this component.
Example 1: Catalog Usage:
Consider a client sending queries to a server asynchronously. When the response to a query arrives, the client needs to invoke the callback associated with that query. For good performance, the callback should be invoked as quickly as possible. One way to achieve this is as follows. The client creates a catalog for the functors associated with queries. It sends to the server the handle (obtained by passing the callback functor associated with the query to the add method of catalog), along with the query. The server does not interpret this handle in any way and sends it back to the client along with the computed query result. The client, upon receiving the response, gets the functor (associated with the query) back by passing the handle (contained in the response message) to the find method of catalog.
Assume the following declarations (we leave the implementations as undefined, as the definitions are largely irrelevant to this example):
  struct Query {
      // Class simulating the query.
  };

  class QueryResult {
      // Class simulating the result of a query.
  };

  class RequestMsg
      // Class encapsulating the request message.  It encapsulates the
      // actual query and the handle associated with the callback for the
      // query.
  {
      Query d_query;
      int   d_handle;

    public:
      RequestMsg(Query query, int handle)
          // Create a request message with the specified 'query' and
          // 'handle'.
      : d_query(query)
      , d_handle(handle)
      {
      }

      int handle() const
          // Return the handle contained in this response message.
      {
          return d_handle;
      }
  };

  class ResponseMsg
      // Class encapsulating the response message.  It encapsulates the query
      // result and the handle associated with the callback for the query.
  {
      int d_handle;

    public:
      void setHandle(int handle)
          // Set the "handle" contained in this response message to the
          // specified 'handle'.
      {
          d_handle = handle;
      }

      QueryResult queryResult() const
          // Return the query result contained in this response message.
      {
          return QueryResult();
      }

      int handle() const
          // Return the handle contained in this response message.
      {
          return d_handle;
      }
  };

  void sendMessage(RequestMsg msg, RemoteAddress peer)
      // Send the specified 'msg' to the specified 'peer'.
  {
      serverMutex.lock();
      peer->push(msg.handle());
      serverNotEmptyCondition.signal();
      serverMutex.unlock();
  }

  void recvMessage(ResponseMsg *msg, RemoteAddress peer)
      // Get the response from the specified 'peer' into the specified 'msg'.
  {
      serverMutex.lock();
      while (peer->empty()) {
          serverNotEmptyCondition.wait(&serverMutex);
      }
      msg->setHandle(peer->front());
      peer->pop();
      serverMutex.unlock();
  }

  void getQueryAndCallback(Query                            *query,
                           bsl::function<void(QueryResult)> *callBack)
      // Set the specified 'query' and 'callBack' to the next 'Query' and its
      // associated functor (the functor to be called when the response to
      // this 'Query' comes in).
  {
      (void)query;
      *callBack = &queryCallBack;
  }
Furthermore, let also the following variables be declared:
  RemoteAddress serverAddress;  // address of remote server

  bdlcc::ObjectCatalog<bsl::function<void(QueryResult)> > catalog;
      // Catalog of query callbacks, used by the client internally to keep
      // track of callback functions across multiple queries.  The invariant
      // is that each element corresponds to a pending query (i.e., the
      // callback function has not yet been or is in the process of being
      // invoked).
Now we define functions that will be used in the thread entry functions:
  void testClientProcessQueryCpp()
  {
      int queriesToBeProcessed = NUM_QUERIES_TO_PROCESS;
      while (queriesToBeProcessed--) {
          Query query;
          bsl::function<void(QueryResult)> callBack;

          // The following call blocks until a query becomes available.
          getQueryAndCallback(&query, &callBack);

          // Register 'callBack' in the object catalog.
          int handle = catalog.add(callBack);
          assert(handle);

          // Send query to server in the form of a 'RequestMsg'.
          RequestMsg msg(query, handle);
          sendMessage(msg, serverAddress);
      }
  }

  void testClientProcessResponseCpp()
  {
      int queriesToBeProcessed = NUM_QUERIES_TO_PROCESS;
      while (queriesToBeProcessed--) {
          // The following call blocks until some response is available in
          // the form of a 'ResponseMsg'.

          ResponseMsg msg;
          recvMessage(&msg, serverAddress);
          int handle = msg.handle();
          QueryResult result = msg.queryResult();

          // Process query 'result' by applying registered 'callBack' to it.
          // The 'callBack' function is retrieved from the 'catalog' using
          // the given 'handle'.

          bsl::function<void(QueryResult)> callBack;
          assert(0 == catalog.find(handle, &callBack));
          callBack(result);

          // Finally, remove the no-longer-needed 'callBack' from the
          // 'catalog'.  Assert so that 'catalog' may not grow unbounded if
          // remove fails.

          assert(0 == catalog.remove(handle));
      }
  }
In some thread, the client executes the following code.
  extern "C" void *testClientProcessQuery(void *)
  {
      testClientProcessQueryCpp();
      return 0;
  }
In some other thread, the client executes the following code.
  extern "C" void *testClientProcessResponse(void *)
  {
      testClientProcessResponseCpp();
      return 0;
  }
Example 2: Iterator Usage:
The following code fragment shows how to use bdlcc::ObjectCatalogIter to iterate through all the objects of catalog (a catalog of objects of type MyType).
  void use(bsl::function<void(QueryResult)> object)
  {
      (void)object;
  }
Now iterate through the catalog:
  for (bdlcc::ObjectCatalogIter<MyType> it(catalog); it; ++it) {
      bsl::pair<int, MyType> p = it(); // p.first contains the handle and
                                       // p.second contains the object
      use(p.second);                   // the function 'use' uses the
                                       // object in some way
  }
  // 'it' is now destroyed out of the scope, releasing the lock.
Note that the associated catalog is (read)locked when the iterator is constructed and is unlocked only when the iterator is destroyed. This means that until the iterator is destroyed, all the threads trying to modify the catalog will remain blocked (even though multiple threads can concurrently read the object catalog). So clients must make sure to destroy their iterators after they are done using them. One easy way is to use the for (bdlcc::ObjectCatalogIter<MyType> it(catalog); ... as above.