// bdlcc_objectcatalog.h -*-C++-*- // ---------------------------------------------------------------------------- // NOTICE // // This component is not up to date with current BDE coding standards, and // should not be used as an example for new development. // ---------------------------------------------------------------------------- #ifndef INCLUDED_BDLCC_OBJECTCATALOG #define INCLUDED_BDLCC_OBJECTCATALOG #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@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. #include <bdlscm_version.h> #include <bslmt_rwmutex.h> #include <bslmt_readlockguard.h> #include <bslmt_writelockguard.h> #include <bdlma_pool.h> #include <bslalg_scalarprimitives.h> #include <bslma_allocator.h> #include <bslma_default.h> #include <bslma_usesbslmaallocator.h> #include <bslmf_assert.h> #include <bslmf_movableref.h> #include <bslmf_nestedtraitdeclaration.h> #include <bsls_alignmentutil.h> #include <bsls_assert.h> #include <bsls_atomic.h> #include <bsls_keyword.h> #include <bsls_libraryfeatures.h> #include <bsls_objectbuffer.h> #include <bsls_platform.h> #include <bsls_review.h> #include <bsl_utility.h> #include <bsl_vector.h> #ifndef BDE_DONT_ALLOW_TRANSITIVE_INCLUDES #include <bslalg_typetraits.h> #endif // BDE_DONT_ALLOW_TRANSITIVE_INCLUDES #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR # include <memory_resource> #endif #include <vector> namespace BloombergLP { namespace bdlcc { template <class TYPE> class ObjectCatalog_AutoCleanup; template <class TYPE> class ObjectCatalogIter; template <class TYPE> class ObjectCatalog; // ===================================== // local class ObjectCatalog_AutoCleanup // ===================================== template <class TYPE> class ObjectCatalog_AutoCleanup { // This class provides a specialized proctor object that, upon destruction // and unless the 'release' method is called (1) removes a managed node // from the 'ObjectCatalog', and (2) deallocates all associated memory as // necessary. ObjectCatalog<TYPE> *d_catalog_p; // temporarily managed catalog typename ObjectCatalog<TYPE>::Node *d_node_p; // temporarily managed node bool d_deallocateFlag; // how to return the managed node private: // NOT IMPLEMENTED ObjectCatalog_AutoCleanup(const ObjectCatalog_AutoCleanup&) BSLS_KEYWORD_DELETED; ObjectCatalog_AutoCleanup& operator=(const ObjectCatalog_AutoCleanup&) BSLS_KEYWORD_DELETED; public: // CREATORS explicit ObjectCatalog_AutoCleanup(ObjectCatalog<TYPE> *catalog); // Create a proctor to manage the specified 'catalog'. ~ObjectCatalog_AutoCleanup(); // Remove a managed node from the 'ObjectCatalog' (by returning it to // the catalog's free list or node pool, as specified in 'manageNode'), // deallocate all associated memory, and destroy this object. // MANIPULATORS void manageNode(typename ObjectCatalog<TYPE>::Node *node, bool deallocateFlag); // Release from management the catalog node, if any, currently managed // by this object and begin managing the specified catalog 'node'. The // specified 'deallocateFlag' tells the destructor how to dispose of // 'node' if 'node' is managed during the destruction of this object. void releaseNode(); // Release from management the catalog node, if any, currently managed // by this object, if any. void release(); // Release from management all resources currently managed by this // object, if any. }; // =================== // class ObjectCatalog // =================== template <class TYPE> class ObjectCatalog { // This class defines an efficient indexed object catalog of 'TYPE' // objects. This container is *exception* *neutral* with no guarantee of // rollback: if an exception is thrown during the invocation of a method on // a pre-existing instance, the object is left in a valid but undefined // state. In no event is memory leaked or a mutex left in a locked state. // PRIVATE TYPES typedef ObjectCatalogIter<TYPE> Iter; enum { // Masks used for breaking up a handle. Note: a handle (of type int) // is always 4 bytes, even on 64 bit modes. k_INDEX_MASK = 0x007fffff, k_BUSY_INDICATOR = 0x00800000, k_GENERATION_INC = 0x01000000, k_GENERATION_MASK = 0xff000000 }; struct Node { // PUBLIC DATA typedef union { // PUBLIC DATA bsls::ObjectBuffer<TYPE> d_value; Node *d_next_p; // when free, pointer // to next free node } Payload; Payload d_payload; int d_handle; }; // DATA bsl::vector<Node *> d_nodes; bdlma::Pool d_nodePool; Node *d_nextFreeNode_p; bsls::AtomicInt d_length; mutable bslmt::RWMutex d_lock; private: // NOT IMPLEMENTED ObjectCatalog(const ObjectCatalog&) BSLS_KEYWORD_DELETED; ObjectCatalog& operator=(const ObjectCatalog&) BSLS_KEYWORD_DELETED; // FRIENDS friend class ObjectCatalog_AutoCleanup<TYPE>; friend class ObjectCatalogIter<TYPE>; private: // PRIVATE CLASS METHODS static TYPE *getNodeValue(Node *node); // Return a pointer to the 'd_value' field of the specified 'node'. // The behavior is undefined unless '0 != node' and // 'node->d_payload.d_value' is initialized to a 'TYPE' object. // PRIVATE MANIPULATORS void freeNode(Node *node); // Add the specified 'node' to the free node list. Destruction of the // object held in the node must be handled by the 'remove' function // directly. (This is because 'freeNode' is also used in the // 'ObjectCatalog_AutoCleanup' guard, but there it should not invoke // the object's destructor.) template <class VECTOR> void removeAllImp(VECTOR *buffer); // Remove all objects that are currently held in this catalog and // optionally load into the optionally specified 'buffer' the removed // objects. // PRIVATE ACCESSORS Node *findNode(int handle) const; // Return a pointer to the node with the specified 'handle', or 0 if // not found. public: // TRAITS BSLMF_NESTED_TRAIT_DECLARATION(ObjectCatalog, bslma::UsesBslmaAllocator); // CREATORS explicit ObjectCatalog(bslma::Allocator *allocator = 0); // Create an empty object catalog, using the optionally specified // 'allocator' to supply any memory. ~ObjectCatalog(); // Destroy this object catalog. // MANIPULATORS int add(TYPE const& object); // Add the value of the specified 'object' to this catalog and return a // non-zero integer handle that may be used to refer to the object in // future calls to this catalog. The behavior is undefined if the // catalog was full. int add(bslmf::MovableRef<TYPE> object); // Add the value of the specified 'object' to this catalog and return a // non-zero integer handle that may be used to refer to the object in // future calls to this catalog, leaving 'object' in an unspecified but // valid state. The behavior is undefined if the catalog was full. int remove(int handle, TYPE *valueBuffer = 0); // Optionally load into the optionally specified 'valueBuffer' the // value of the object having the specified 'handle' and remove it from // this catalog. Return zero on success, and a non-zero value if the // 'handle' is not contained in this catalog. Note that 'valueBuffer' // is assigned into, and thus must point to a valid 'TYPE' instance. void removeAll(); void removeAll(bsl::vector<TYPE> *buffer); void removeAll(std::vector<TYPE> *buffer); #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR void removeAll(std::pmr::vector<TYPE> *buffer); #endif // Remove all objects that are currently held in this catalog and // optionally load into the optionally specified 'buffer' the removed // objects. int replace(int handle, const TYPE& newObject); // Replace the object having the specified 'handle' with the specified // 'newObject'. Return 0 on success, and a non-zero value if the // handle is not contained in this catalog. int replace(int handle, bslmf::MovableRef<TYPE> newObject); // Replace the object having the specified 'handle' with the specified // 'newObject', leaving 'newObject' in an unspecified but valid state. // Return 0 on success, and a non-zero value if the handle is not // contained in this catalog. // ACCESSORS bslma::Allocator *allocator() const; // Return the allocator used by this object. int find(int handle) const; int find(int handle, TYPE *valueBuffer) const; // Locate the object having the specified 'handle' and optionally load // its value into the optionally specified 'valueBuffer'. Return zero // on success, and a non-zero value if the 'handle' is not contained in // this catalog. Note that 'valueBuffer' is assigned into, and thus // must point to a valid 'TYPE' instance. Note that the overload with // 'valueBuffer' passed is not supported unless 'TYPE' has a copy // constructor. bool isMember(const TYPE& object) const; // Return 'true' if the catalog contains an item that compares equal to // the specified 'object' and 'false' otherwise. int length() const; // Return a "snapshot" of the number of items currently contained in // this catalog. const TYPE& value(int handle) const; // Return a 'const' reference to the object having the specified // 'handle'. The behavior is undefined unless 'handle' is contained in // this catalog. // FOR TESTING PURPOSES ONLY void verifyState() const; // Verify that this catalog is in a consistent state. This function is // introduced for testing purposes only. }; // ======================= // class ObjectCatalogIter // ======================= template <class TYPE> class ObjectCatalogIter { // Provide thread safe iteration through all the objects of an object // catalog of parameterized 'TYPE'. The order of the iteration is // implementation defined. An iterator is *valid* if it is associated with // an object in the catalog, otherwise it is *invalid*. 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 (nevertheless, multiple threads can // concurrently read the object catalog). const ObjectCatalog<TYPE> *d_catalog_p; int d_index; private: // NOT IMPLEMENTED ObjectCatalogIter(const ObjectCatalogIter&) BSLS_KEYWORD_DELETED; ObjectCatalogIter& operator=(const ObjectCatalogIter&) BSLS_KEYWORD_DELETED; bool operator==(const ObjectCatalogIter&) const BSLS_KEYWORD_DELETED; bool operator!=(const ObjectCatalogIter&) const BSLS_KEYWORD_DELETED; template <class BDE_OTHER_TYPE> bool operator==( const ObjectCatalogIter<BDE_OTHER_TYPE>&) const BSLS_KEYWORD_DELETED; template <class BDE_OTHER_TYPE> bool operator!=( const ObjectCatalogIter<BDE_OTHER_TYPE>&) const BSLS_KEYWORD_DELETED; public: // CREATORS explicit ObjectCatalogIter(const ObjectCatalog<TYPE>& catalog); // Create an iterator for the specified 'catalog' and associate it with // the first member of the 'catalog'. If the 'catalog' is empty then // the iterator is initialized to be invalid. The 'catalog' is locked // for read for the duration of iterator's life. ~ObjectCatalogIter(); // Destroy this iterator and unlock the catalog associated with it. // MANIPULATORS void operator++(); // Advance this iterator to refer to the next object of the associated // catalog; if there is no next object in the associated catalog, then // this iterator becomes *invalid*. The behavior is undefined unless // this iterator is valid. Note that the order of the iteration is not // specified. // ACCESSORS operator const void *() const; // Return non-zero if the iterator is *valid*, and 0 otherwise. bsl::pair<int, TYPE> operator()() const; // Return a pair containing the handle (as the first element of the // pair) and the object (as the second element of the pair) associated // with this iterator. The behavior is undefined unless the iterator // is *valid*. int handle() const; // Return the handle referred to by the iterator. The behavior is // undefined unless the iterator is *valid*. const TYPE& value() const; // Return a 'const' reference to the value referred to by the iterator. // The behavior is undefined unless the iterator is *valid*. }; // ---------------------------------------------------------------------------- // INLINE DEFINITIONS // ---------------------------------------------------------------------------- // ------------------------------------- // local class ObjectCatalog_AutoCleanup // ------------------------------------- // CREATORS template <class TYPE> ObjectCatalog_AutoCleanup<TYPE>::ObjectCatalog_AutoCleanup( ObjectCatalog<TYPE> *catalog) : d_catalog_p(catalog) , d_node_p(0) , d_deallocateFlag(false) { } template <class TYPE> ObjectCatalog_AutoCleanup<TYPE>::~ObjectCatalog_AutoCleanup() { if (d_catalog_p && d_node_p) { if (d_deallocateFlag) { // Return node to the pool. d_catalog_p->d_nodePool.deallocate(d_node_p); } else { // Return node to the catalog's free list. d_catalog_p->freeNode(d_node_p); } } } // MANIPULATORS template <class TYPE> void ObjectCatalog_AutoCleanup<TYPE>::manageNode( typename ObjectCatalog<TYPE>::Node *node, bool deallocateFlag) { d_node_p = node; d_deallocateFlag = deallocateFlag; } template <class TYPE> void ObjectCatalog_AutoCleanup<TYPE>::releaseNode() { d_node_p = 0; } template <class TYPE> void ObjectCatalog_AutoCleanup<TYPE>::release() { d_catalog_p = 0; d_node_p = 0; } // ------------------- // class ObjectCatalog // ------------------- // PRIVATE CLASS METHODS template <class TYPE> inline TYPE *ObjectCatalog<TYPE>::getNodeValue( typename ObjectCatalog<TYPE>::Node *node) { BSLS_ASSERT(node); return node->d_payload.d_value.address(); } // PRIVATE MANIPULATORS template <class TYPE> inline void ObjectCatalog<TYPE>::freeNode(typename ObjectCatalog<TYPE>::Node *node) { BSLS_ASSERT(node->d_handle & k_BUSY_INDICATOR); node->d_handle += k_GENERATION_INC; node->d_handle &= ~k_BUSY_INDICATOR; node->d_payload.d_next_p = d_nextFreeNode_p; d_nextFreeNode_p = node; } template <class TYPE> template <class VECTOR> void ObjectCatalog<TYPE>::removeAllImp(VECTOR *buffer) { static const bool isVector = bsl::is_same<bsl::vector<TYPE>, VECTOR>::value #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR || bsl::is_same<std::pmr::vector<TYPE>, VECTOR>::value #endif || bsl::is_same<std::vector<TYPE>, VECTOR>::value; BSLMF_ASSERT(isVector); typedef typename bsl::vector<Node *>::iterator VIt; bslmt::WriteLockGuard<bslmt::RWMutex> guard(&d_lock); for (VIt it = d_nodes.begin(); it != d_nodes.end(); ++it) { if ((*it)->d_handle & k_BUSY_INDICATOR) { TYPE *value = getNodeValue(*it); if (buffer) { buffer->push_back(bslmf::MovableRefUtil::move(*value)); } value->~TYPE(); } } // Even though we get rid of the container of 'Node *' without returning // the nodes to the pool prior, the release of the pool immediately after // will properly (and efficiently) dispose of those nodes without leaking // memory. d_nodes.clear(); d_nodePool.release(); d_nextFreeNode_p = 0; d_length = 0; } // PRIVATE ACCESSORS template <class TYPE> inline typename ObjectCatalog<TYPE>::Node * ObjectCatalog<TYPE>::findNode(int handle) const { int index = handle & k_INDEX_MASK; // if (d_nodes.size() < index || !(handle & k_BUSY_INDICATOR)) return 0; if (0 > index || index >= (int)d_nodes.size() || !(handle & k_BUSY_INDICATOR)) { return 0; // RETURN } Node *node = d_nodes[index]; return node->d_handle == handle ? node : 0; } // CREATORS template <class TYPE> inline ObjectCatalog<TYPE>::ObjectCatalog(bslma::Allocator *allocator) : d_nodes(allocator) , d_nodePool(sizeof(Node), allocator) , d_nextFreeNode_p(0) , d_length(0) { } template <class TYPE> inline ObjectCatalog<TYPE>::~ObjectCatalog() { removeAll(); } // MANIPULATORS template <class TYPE> int ObjectCatalog<TYPE>::add(const TYPE& object) { int handle; bslmt::WriteLockGuard<bslmt::RWMutex> guard(&d_lock); ObjectCatalog_AutoCleanup<TYPE> proctor(this); Node *node; if (d_nextFreeNode_p) { node = d_nextFreeNode_p; d_nextFreeNode_p = node->d_payload.d_next_p; proctor.manageNode(node, false); // Destruction of this proctor will put node back onto the free list. } else { // If 'd_nodes' grows as big as the flags used to indicate BUSY and // generations, then the handle will be all mixed up! BSLS_REVIEW_OPT(d_nodes.size() < k_BUSY_INDICATOR); node = static_cast<Node *>(d_nodePool.allocate()); proctor.manageNode(node, true); // Destruction of this proctor will deallocate node. d_nodes.push_back(node); node->d_handle = static_cast<int>(d_nodes.size()) - 1; proctor.manageNode(node, false); // Destruction of this proctor will put node back onto the free list, // which is now OK since the 'push_back' succeeded without throwing. } node->d_handle |= k_BUSY_INDICATOR; handle = node->d_handle; // We need to use the copyConstruct logic to pass the allocator through. bslalg::ScalarPrimitives::copyConstruct( getNodeValue(node), object, d_nodes.get_allocator().mechanism()); // If the copy constructor throws, the proctor will properly put the node // back onto the free list. Otherwise, the proctor should do nothing. proctor.release(); ++d_length; return handle; } template <class TYPE> int ObjectCatalog<TYPE>::add(bslmf::MovableRef<TYPE> object) { TYPE& local = object; int handle; bslmt::WriteLockGuard<bslmt::RWMutex> guard(&d_lock); ObjectCatalog_AutoCleanup<TYPE> proctor(this); Node *node; if (d_nextFreeNode_p) { node = d_nextFreeNode_p; d_nextFreeNode_p = node->d_payload.d_next_p; proctor.manageNode(node, false); // Destruction of this proctor will put node back onto the free list. } else { // If 'd_nodes' grows as big as the flags used to indicate BUSY and // generations, then the handle will be all mixed up! BSLS_REVIEW_OPT(d_nodes.size() < k_BUSY_INDICATOR); node = static_cast<Node *>(d_nodePool.allocate()); proctor.manageNode(node, true); // Destruction of this proctor will deallocate node. d_nodes.push_back(node); node->d_handle = static_cast<int>(d_nodes.size()) - 1; proctor.manageNode(node, false); // Destruction of this proctor will put node back onto the free list, // which is now OK since the 'push_back' succeeded without throwing. } node->d_handle |= k_BUSY_INDICATOR; handle = node->d_handle; // We need to use the moveConstruct logic to pass the allocator through. bslalg::ScalarPrimitives::moveConstruct( getNodeValue(node), local, d_nodes.get_allocator().mechanism()); // If the copy constructor throws, the proctor will properly put the node // back onto the free list. Otherwise, the proctor should do nothing. proctor.release(); ++d_length; return handle; } template <class TYPE> inline int ObjectCatalog<TYPE>::remove(int handle, TYPE *valueBuffer) { bslmt::WriteLockGuard<bslmt::RWMutex> guard(&d_lock); Node *node = findNode(handle); if (!node) { return -1; // RETURN } TYPE *value = getNodeValue(node); if (valueBuffer) { *valueBuffer = bslmf::MovableRefUtil::move(*value); } value->~TYPE(); freeNode(node); --d_length; return 0; } template <class TYPE> inline void ObjectCatalog<TYPE>::removeAll() { removeAllImp(static_cast<bsl::vector<TYPE> *>(0)); } template <class TYPE> inline void ObjectCatalog<TYPE>::removeAll(bsl::vector<TYPE> *buffer) { removeAllImp(buffer); } template <class TYPE> inline void ObjectCatalog<TYPE>::removeAll(std::vector<TYPE> *buffer) { removeAllImp(buffer); } #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR template <class TYPE> inline void ObjectCatalog<TYPE>::removeAll(std::pmr::vector<TYPE> *buffer) { removeAllImp(buffer); } #endif template <class TYPE> int ObjectCatalog<TYPE>::replace(int handle, const TYPE& newObject) { bslmt::WriteLockGuard<bslmt::RWMutex> guard(&d_lock); Node *node = findNode(handle); if (!node) { return -1; // RETURN } TYPE *value = getNodeValue(node); value->~TYPE(); // We need to use the copyConstruct logic to pass the allocator through. bslalg::ScalarPrimitives::copyConstruct( value, newObject, d_nodes.get_allocator().mechanism()); return 0; } template <class TYPE> int ObjectCatalog<TYPE>::replace(int handle, bslmf::MovableRef<TYPE> newObject) { TYPE& local = newObject; bslmt::WriteLockGuard<bslmt::RWMutex> guard(&d_lock); Node *node = findNode(handle); if (!node) { return -1; // RETURN } TYPE *value = getNodeValue(node); value->~TYPE(); // We need to use the moveConstruct logic to pass the allocator through. bslalg::ScalarPrimitives::moveConstruct( value, local, d_nodes.get_allocator().mechanism()); return 0; } // ACCESSORS template <class TYPE> inline bslma::Allocator *ObjectCatalog<TYPE>::allocator() const { return d_nodePool.allocator(); } template <class TYPE> inline int ObjectCatalog<TYPE>::find(int handle) const { bslmt::ReadLockGuard<bslmt::RWMutex> guard(&d_lock); return 0 == findNode(handle) ? -1 : 0; } template <class TYPE> inline int ObjectCatalog<TYPE>::find(int handle, TYPE *valueBuffer) const { bslmt::ReadLockGuard<bslmt::RWMutex> guard(&d_lock); Node *node = findNode(handle); if (!node) { return -1; // RETURN } *valueBuffer = *getNodeValue(node); return 0; } template <class TYPE> bool ObjectCatalog<TYPE>::isMember(const TYPE& object) const { for (Iter it(*this); it; ++it) { if (it.value() == object) { return true; // RETURN } } return false; } template <class TYPE> inline int ObjectCatalog<TYPE>::length() const { return d_length; } template <class TYPE> inline const TYPE& ObjectCatalog<TYPE>::value(int handle) const { Node *node = findNode(handle); BSLS_ASSERT(node); return *getNodeValue(node); } template <class TYPE> void ObjectCatalog<TYPE>::verifyState() const { bslmt::ReadLockGuard<bslmt::RWMutex> guard(&d_lock); BSLS_ASSERT( 0 <= d_length); BSLS_ASSERT(d_nodes.size() >= static_cast<unsigned>(d_length)); unsigned numBusy = 0, numFree = 0; for (unsigned ii = 0; ii < d_nodes.size(); ++ii) { const int handle = d_nodes[ii]->d_handle; BSLS_ASSERT((handle & k_INDEX_MASK) == ii); handle & k_BUSY_INDICATOR ? ++numBusy : ++numFree; } BSLS_ASSERT( numBusy == static_cast<unsigned>(d_length)); BSLS_ASSERT(numFree + numBusy == d_nodes.size()); for (const Node *p = d_nextFreeNode_p; p; p = p->d_payload.d_next_p) { BSLS_ASSERT(!(p->d_handle & k_BUSY_INDICATOR)); --numFree; } BSLS_ASSERT(0 == numFree); } // ----------------- // ObjectCatalogIter // ----------------- // CREATORS template <class TYPE> inline ObjectCatalogIter<TYPE>::ObjectCatalogIter(const ObjectCatalog<TYPE>& catalog) : d_catalog_p(&catalog) , d_index(-1) { d_catalog_p->d_lock.lockRead(); operator++(); } template <class TYPE> inline ObjectCatalogIter<TYPE>::~ObjectCatalogIter() { d_catalog_p->d_lock.unlock(); } // MANIPULATORS template <class TYPE> void ObjectCatalogIter<TYPE>::operator++() { ++d_index; while ((unsigned)d_index < d_catalog_p->d_nodes.size() && !(d_catalog_p->d_nodes[d_index]->d_handle & ObjectCatalog<TYPE>::k_BUSY_INDICATOR)) { ++d_index; } } template <class TYPE> inline int ObjectCatalogIter<TYPE>::handle() const { BSLS_ASSERT(static_cast<unsigned>(d_index) < d_catalog_p->d_nodes.size()); return d_catalog_p->d_nodes[d_index]->d_handle; } template <class TYPE> inline const TYPE& ObjectCatalogIter<TYPE>::value() const { BSLS_ASSERT(static_cast<unsigned>(d_index) < d_catalog_p->d_nodes.size()); return *ObjectCatalog<TYPE>::getNodeValue(d_catalog_p->d_nodes[d_index]); } } // close package namespace // ACCESSORS template <class TYPE> inline bdlcc::ObjectCatalogIter<TYPE>::operator const void *() const { return static_cast<unsigned>(d_index) < d_catalog_p->d_nodes.size() ? this : 0; } namespace bdlcc { template <class TYPE> inline bsl::pair<int, TYPE> ObjectCatalogIter<TYPE>::operator()() const { typedef ObjectCatalog<TYPE> Catalog; typename Catalog::Node *node = d_catalog_p->d_nodes[d_index]; return bsl::pair<int, TYPE>(node->d_handle, *Catalog::getNodeValue(node)); } } // 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 ----------------------------------