// bdlcc_timequeue.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_TIMEQUEUE #define INCLUDED_BDLCC_TIMEQUEUE #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide an efficient queue for time events. // //@CLASSES: // bdlcc::TimeQueue: Templatized time event queue // bdlcc::TimeQueueItem: ('struct') Templatized item in the time event queue // //@SEE_ALSO: // //@DESCRIPTION: This component provides a thread-safe and efficient templatized // time queue. The queue stores an ordered list of time values and associated // 'DATA'. Each item added to the queue is assigned a unique identifier that // can be used to efficiently remove the item making this queue suitable for // conditions where time items are added and removed very frequently. // // Class 'bdlcc::TimeQueue<DATA>' provides a public interface which is similar // in structure and intent to 'bdlcc::Queue<DATA>', with the exception that // each item stored in the 'bdlcc::TimeQueue' is of type // 'bdlcc::TimeQueueItem<DATA>'. This structure contains a single // 'bsls::TimeInterval' value along with the 'DATA' value. // // Idiomatic usage of 'bdlcc::TimeQueue' includes the member function 'popLE', // which finds all items on the queue whose 'bsls::TimeInterval' are less than // a specified value, and transfers those items to a provided vector of items. // Through the use of this member function, clients can retrieve and process // multiple elements that have expired, that is, whose 'bsls::TimeInterval' // values are in the past. // // 'bdlcc::TimeQueue' also makes use of an opaque data type // 'bdlcc::TimeQueue::Handle' which serves to identify an individual element on // the Time Queue. A value of type 'Handle' is returned from the 'add' member // function, and can then be used to remove or modify the corresponding element // on the queue. In this way, the 'update' member function can update the time // value for a specific 'bdlcc::TimeQueueItem' without removing it from the // queue. // ///'bdlcc::TimeQueue::Handle' Uniqueness, Reuse and 'numIndexBits' ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // 'bdlcc::TimeQueue::Handle' is an alias for a 32-bit 'int' type. A handle // consists of two parts, the "index section" and the "iteration section". The // index section, which is the low-order 'numIndexBits' (which defaults to // 'numIndexBits == 17'), uniquely identifies the node. Once a node is added, // it never ceases to exist - it may be freed, but it will be kept on a free // list to be eventually recycled, and the same index section will always // identify that node. The iteration section, the high-order // '32 - numIndexBits', is changed every time a node is freed, so that an // out-of-date handle can be identified as out-of-date. But since the // iteration section has only a finite number of bits, if a node is freed and // re-added enough times, old handle values will eventually be reused. // // Up to '2 ** numIndexBits - 1' nodes can exist in a given time queue. A // given handle won't be reused for a node until that node has been freed and // reused '2 ** (32 - numIndexBits) - 1' times. // // 'numIndexBits' is an optional parameter to the time queue constructors. If // unspecified, it has a value of 17. The behavior is undefined unless the // specified 'numIndexBits' is in the range '8 <= numIndexBits <= 24'. // ///Thread Safety ///- - - - - - - // It is safe to access or modify two distinct 'bdlcc::TimeQueue' objects // simultaneously, each from a separate thread. It is safe to access or modify // a single 'bdlcc::TimeQueue' object simultaneously from two or more separate // threads. // // It is safe to enqueue objects in a 'bdlcc::TimeQueue' object whose // destructor may access or even modify the same 'bdlcc::TimeQueue' object. // However, there is no guarantee regarding the safety of enqueuing objects // whose copy constructors or assignment operators may modify or even merely // access the same 'bdlcc::TimeQueue' object (except 'length'). Such attempts // generally lead to a deadlock. // ///Ordering /// - - - - // For a given 'bsls::TimeInterval' value, the order of item removal (via // 'popFront', 'popLE', 'removeAll', etc.) is guaranteed to match the order of // item insertion (via 'add') for a particular insertion thread or group of // externally synchronized insertion threads. // ///Usage ///----- // The following shows a typical usage of the 'bdlcc::TimeQueue' class, // implementing a simple threaded server 'my_Server' that manages individual // Connections ('my_Connection') on behalf of multiple Sessions ('my_Session'). // Each Connection is timed, such that input requests on that Connection will // "time out" after a user-specified time interval. When a specific Connection // times out, that Connection is removed from the 'bdlcc::TimeQueue' and the // corresponding 'my_Session' is informed. // // In this simplified example, class 'my_Session' will terminate when its // Connection times out. A more sophisticated implementation of 'my_Session' // would attempt recovery, perhaps by closing and reopening the physical // Connection. // ///Forward Declarations /// - - - - - - - - - - // Class 'my_Server' will spawn two service threads to monitor connections for // available data and to manage time-outs, respectively. Two forward-declared // "C" functions are invoked as the threads are spawned. The signature of each // function follows the "C" standard "'void *'" interface for spawning threads. // Each function will be called on a new thread when the 'start' method is // invoked for a given 'my_Server' object. Each function then delegates // processing for the thread back to the 'my_Server' object that spawned it. //.. // extern "C" { // // void *my_connectionMonitorThreadEntry(void *server); // // void *my_timerMonitorThreadEntry(void *server); // // } //.. // ///'struct my_Connection' /// - - - - - - - - - - - // The 'my_Connection' structure is used by 'my_Server' to manage a single // physical connection on behalf of a 'my_Session'. //.. // class my_Session; // struct my_Connection { // int d_timerId; // my_Session *d_session_p; // }; //.. // ///Protocol Classes /// - - - - - - - - // Protocol class 'my_Session' provides a pure abstract protocol to manage a // single "session" to be associated with a specific connection on a server. //.. // class my_Session { // // Pure protocol class to process a data buffer of arbitrary size. // // Concrete implementations in the "real world" would typically manage // // an external connection like a socket. // // public: // my_Session(); // virtual int processData(void *data, int length) = 0; // virtual int handleTimeout(my_Connection *connection) = 0; // virtual ~my_Session(); // }; //.. // The constructor and destructor do nothing: //.. // my_Session::my_Session() // { // } // // my_Session::~my_Session() // { // } //.. // Protocol class 'my_Server' provides a partial implementation of a simple // server that supports and monitors an arbitrary number of connections and // handles incoming data for those connections. Clients must provide a // concrete implementation that binds connections to concrete 'my_Session' // objects and monitors all open connections for incoming requests. The // concrete implementation calls 'my_Server::newConnection()' when a new // connections is required, and implements the virtual function // 'monitorConnections' to monitor all open connections. //.. // class my_Server { // // Simple server supporting multiple Connections. // // bsl::vector<my_Connection*> d_connections; // bdlcc::TimeQueue<my_Connection*> d_timeQueue; // int d_ioTimeout; // bslmt::Mutex d_timerMonitorMutex; // bslmt::Condition d_timerChangedCond; // bslmt::ThreadUtil::Handle d_connectionThreadHandle; // bslmt::ThreadUtil::Handle d_timerThreadHandle; // volatile bool d_done; // // protected: // void newConnection(my_Connection *connection); // // Add the specified 'connection' to the current 'my_Server', // // setting the new timeout value to the current time plus the // // timeout value provided at construction of this 'my_Server' // // instance. If the added connection is the new "top" of the // // queue, signal that the minimum time on the queue has changed. // // Upon seeing this signal, the TimerMonitor thread will wake up // // and look for expired timers. // // // // Behavior is undefined if 'connection' has already been added to // // any 'my_Server' and has not been removed via member function // // 'closeConnection'. // // void removeConnection(my_Connection *connection); // // Remove the specified 'connection' from the current 'my_Server', // // so that it will no longer be monitored for available data. // // virtual void closeConnection(my_Connection *connection)=0; // // Provide a mechanism for a concrete implementation to close a // // specified 'connection'. // // void dataAvailable(my_Connection *connection, // void *buffer_p, // int length); // // Receive in the specified 'buffer_p' a pointer to a data buffer // // of the specified 'length' bytes, and pass this to the specified // // 'connection' to be processed. Behavior is undefined if // // 'connection' is not currently added to this 'my_Server' object, // // or if 'length' <= 0. // // protected: // virtual void monitorConnections()=0; // // Monitor all connections in the current 'my_Server'. When data // // becomes available for a given connection, pass the data to that // // connection for processing. // // void monitorTimers(); // // Monitor all timers in the current 'my_Server', and handle each // // timer as it expires. // // friend void *my_connectionMonitorThreadEntry(void *server); // friend void *my_timerMonitorThreadEntry(void *server); // // private: // // Not implemented: // my_Server(const my_Server&); // // public: // // CREATORS // explicit // my_Server(int ioTimeout, bslma::Allocator *basicAllocator = 0); // // Construct a 'my_Server' object with a timeout value of the // // specified 'ioTimeout' seconds. Use the optionally specified // // 'basicAllocator' for all memory allocation for data members of // // 'my_Server'. // // virtual ~my_Server(); // // // MANIPULATORS // int start(); // // Begin monitoring timers and connections. // }; //.. // The constructor is simple: it initializes the internal 'bdlcc::TimeQueue' // and sets the I/O timeout value. The virtual destructor sets a shared // completion flag to indicate completion, wakes up all waiting threads, and // waits for them to join. //.. // my_Server::my_Server(int ioTimeout, bslma::Allocator *basicAllocator) // : d_timeQueue(basicAllocator) // , d_ioTimeout(ioTimeout) // , d_connectionThreadHandle(bslmt::ThreadUtil::invalidHandle()) // , d_timerThreadHandle(bslmt::ThreadUtil::invalidHandle()) // { // } // // my_Server::~my_Server() // { // d_done = true; // d_timerChangedCond.broadcast(); // if (bslmt::ThreadUtil::invalidHandle() != d_connectionThreadHandle) { // bslmt::ThreadUtil::join(d_connectionThreadHandle); // } // if (bslmt::ThreadUtil::invalidHandle()!= d_timerThreadHandle) { // bslmt::ThreadUtil::join(d_timerThreadHandle); // } // } //.. // Member function 'newConnection' adds the 'connection' to the current set of // connections to be monitored. This is done in two steps. First, the // 'connection' is added to the internal array, and then a timer is set for the // 'connection' by creating a corresponding entry in the internal // 'bdlcc::TimeQueue'. //.. // void my_Server::newConnection(my_Connection *connection) // { // d_connections.push_back(connection); // int isNewTop = 0; // connection->d_timerId = d_timeQueue.add(bdlt::CurrentTime::now() + // d_ioTimeout, // connection, // &isNewTop); // if (isNewTop) { // bslmt::LockGuard<bslmt::Mutex> lock(&d_timerMonitorMutex); // d_timerChangedCond.signal(); // } // } //.. // Member function 'monitorConnections', provided by the concrete // implementation class, can use the internal array to determine the set of // connections to be monitored. // // Member function 'removeConnection' removes the 'connection' from the current // set of connections to be monitored. This is done in two steps, in reversed // order from 'newConnection'. First, the 'connection' is removed from the // internal 'bdlcc::TimeQueue', and then the 'connection' is removed from the // internal array. // // The concrete implementation class must provide an implementation of virtual // function 'closeConnection'; this implementation must call 'removeConnection' // when the actual connection is to be removed from the 'my_Server' object. // // Function 'closeConnection' is in turn called by function 'monitorTimers', // which manages the overall timer monitor thread. Because 'monitorTimers' // takes responsibility for notifying other threads when the queue status // changes, function 'removeConnection' does not address these concerns. //.. // void my_Server::removeConnection(my_Connection *connection) // { // // Remove from d_timeQueue // d_timeQueue.remove(connection->d_timerId); // // Remove from d_connections // bsl::vector<my_Connection*>::iterator begin = d_connections.begin(), // end = d_connections.end(), // it = begin; // for (; it != end; ++it) { // if (connection == *it) { // d_connections.erase(it); // } // } // } //.. // The 'dataAvailable' function will be called when data becomes available for // a specific connection. It removes the connection from the timer queue while // the connection is busy, processes the available data, and returns the // connection to the queue with a new time value. //.. // void my_Server::dataAvailable(my_Connection *connection, // void *buffer_p, // int length) // { // if (connection->d_timerId) { // if (d_timeQueue.remove(connection->d_timerId)) return; // RETURN // connection->d_timerId = 0; // } // connection->d_session_p->processData(buffer_p, length); // // int isNewTop = 0; // // connection->d_timerId = d_timeQueue.add(bdlt::CurrentTime::now() + // d_ioTimeout, // connection, // &isNewTop); // if (isNewTop) { // bslmt::LockGuard<bslmt::Mutex> lock(&d_timerMonitorMutex); // d_timerChangedCond.signal(); // } // } //.. // Function 'monitorTimers' manages the timer monitor thread; it is called when // the thread is spawned, and checks repeatedly for expired timers; after each // check, it does a timed wait based upon the minimum time value seen in the // queue after all expired timers have been removed. //.. // void my_Server::monitorTimers() // { // while (!d_done) { // bsl::vector<bdlcc::TimeQueueItem<my_Connection*> > expiredTimers; // { // bslmt::LockGuard<bslmt::Mutex> lock(&d_timerMonitorMutex); // bsls::TimeInterval minTime; // int newLength; // // d_timeQueue.popLE(bdlt::CurrentTime::now(), // &expiredTimers, // &newLength, // &minTime ); // // if (!expiredTimers.size()) { // if (newLength) { // // no expired timers, but unexpired timers remain. // d_timerChangedCond.timedWait(&d_timerMonitorMutex, // minTime); // } // else { // // no expired timers, and timer queue is empty. // d_timerChangedCond.wait(&d_timerMonitorMutex); // } // continue; // } // } // // int length = static_cast<int>(expiredTimers.size()); // if (length) { // bdlcc::TimeQueueItem<my_Connection*> *data = // &expiredTimers.front(); // for (int i = 0; i < length; ++i) { // closeConnection(data[i].data()); // } // } // } // } //.. // Function 'start' spawns two separate threads. The first thread will monitor // connections and handle any data received on them. The second monitors the // internal timer queue and removes connections that have timed out. Function // 'start' calls 'bslmt::ThreadUtil::create', which expects a function pointer // to a function with the standard "C" callback signature // 'void *fn(void *data)'. This non-member function will call back into the // 'my_Server' object immediately. //.. // int my_Server::start() // { // bslmt::ThreadAttributes attr; // // if (bslmt::ThreadUtil::create(&d_connectionThreadHandle, attr, // &my_connectionMonitorThreadEntry, // this)) { // return -1; // RETURN // } // // if (bslmt::ThreadUtil::create(&d_timerThreadHandle, attr, // &my_timerMonitorThreadEntry, // this)) { // return -1; // RETURN // } // return 0; // } //.. // Finally, we are now in a position to implement the two thread dispatchers: //.. // extern "C" { // // void *my_connectionMonitorThreadEntry(void *server) // { // ((my_Server*)server)->monitorConnections(); // return server; // } // // void *my_timerMonitorThreadEntry(void *server) // { // ((my_Server*)server)->monitorTimers(); // return server; // } // // } //.. // In order to test our server, we provide two concrete implementations of a // test session and of a test server as follows. //.. // // myTestSession.h -*-C++-*- // // class my_TestSession : public my_Session { // // Concrete implementation of my_Session, providing simple test // // semantics In particular, implement the virtual function // // processData() to record all incoming data for the controlling // // connection, and virtual function handleTimeout() for handling // // timeouts. // // int d_verbose; // // public: // // CREATORS // explicit // my_TestSession(int verbose) : my_Session(), d_verbose(verbose) { } // // // MANIPULATORS // virtual int handleTimeout(my_Connection *connection) // { // // Do something to handle timeout. // if (d_verbose) { // bsl::cout << bdlt::CurrentTime::utc() << ": "; // bsl::cout << "Connection " << connection << "timed out.\n"; // } // return 0; // } // // virtual int processData(void *data, int length) // { // // Do something with the data... // if (d_verbose) { // bsl::cout << bdlt::CurrentTime::utc() << ": "; // bsl::cout << "Processing data at address " << data // << " and length " << length << ".\n"; // } // return 0; // } // }; // // // myTestSession.h -*-C++-*- // // class my_TestServer : public my_Server { // // Concrete implementation of my_Server, providing connection logic. // // int d_verbose; // // protected: // virtual void closeConnection(my_Connection *connection); // // Close the specified external 'connection' and call // // 'removeConnection' when done. // // virtual void monitorConnections(); // // Monitor all connections in the current 'my_Server'. When data // // becomes available for a given connection, pass the data to that // // connection for processing. // // private: // // Not implemented: // my_TestServer(const my_TestServer&); // // public: // // CREATORS // explicit // my_TestServer(int ioTimeout, // int verbose = 0, // bslma::Allocator *basicAllocator = 0) // : my_Server(ioTimeout, basicAllocator) // , d_verbose(verbose) // { // } // // virtual ~my_TestServer(); // }; // // // myTestSession.cpp -*-C++-*- // // my_TestServer::~my_TestServer() // { // } // // void my_TestServer::closeConnection(my_Connection *connection) // { // if (d_verbose) { // bsl::cout << bdlt::CurrentTime::utc() << ": "; // bsl::cout << "Closing connection " << connection << bsl::endl; // } // delete connection; // } // // void my_TestServer::monitorConnections() // { // my_Session *session = new my_TestSession(d_verbose); // // // Simulate connection monitor logic... // my_Connection *connection1 = new my_Connection; // connection1->d_session_p = session; // newConnection(connection1); // if (d_verbose) { // bsl::cout << bdlt::CurrentTime::utc() << ": "; // bsl::cout << "Opening connection " << connection1 << endl; // } // // my_Connection *connection2 = new my_Connection; // connection2->d_session_p = session; // newConnection(connection2); // if (d_verbose) { // bsl::cout << bdlt::CurrentTime::utc() << ": "; // bsl::cout << "Opening connection " << connection2 << endl; // } // // bslmt::ThreadUtil::sleep(bsls::TimeInterval(2)); // 2s // // // Simulate transmission... // const int length = 1024; // const char*buffer[length]; // if (d_verbose) { // bsl::cout << bdlt::CurrentTime::utc() << ": "; // bsl::cout << "Connection " << connection1 // << " receives " << length << " bytes " << endl; // } // dataAvailable(connection1, buffer, length); // // // Wait for timeout to occur, otherwise session gets destroyed from // // stack too early. // // bslmt::ThreadUtil::sleep(bsls::TimeInterval(8)); // 8s // } //.. // The program that would exercise this test server would simply consist of: //.. // int usageExample(int verbose) // { // my_TestServer mX(5, verbose); // timeout for connections: 5s // mX.start(); // // // Wait sufficiently long to observe all events. // bslmt::ThreadUtil::sleep(bsls::TimeInterval(10)); // 10s // // return 0; // } //.. // The output of this program would look something as follows: //.. // 17:10:35.000: Opening connection 0x00161880 // 17:10:35.000: Opening connection 0x001618b0 // 17:10:37.000: Connection 0x00161880 receives 1024 bytes // 17:10:37.000: Processing data at address 0xfeefaf04 and length 1024. // 17:10:40.000: Closing connection 0x001618b0 // 17:10:42.000: Closing connection 0x00161880 //.. #include <bdlscm_version.h> #include <bdlma_concurrentpoolallocator.h> #include <bdlma_pool.h> #include <bslmt_lockguard.h> #include <bslmt_mutex.h> #include <bslalg_scalarprimitives.h> #include <bslma_default.h> #include <bslma_destructionutil.h> #include <bslma_usesbslmaallocator.h> #include <bslmf_assert.h> #include <bslmf_nestedtraitdeclaration.h> #include <bsls_alignment.h> #include <bsls_assert.h> #include <bsls_atomic.h> #include <bsls_keyword.h> #include <bsls_libraryfeatures.h> #include <bsls_platform.h> #include <bsls_timeinterval.h> #include <bsl_climits.h> #include <bsl_cstdint.h> #include <bsl_map.h> #include <bsl_vector.h> #ifndef BDE_DONT_ALLOW_TRANSITIVE_INCLUDES #include <bslalg_scalardestructionprimitives.h> #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 DATA> class TimeQueueItem; // =============== // class TimeQueue // =============== template <class DATA> class TimeQueue { // This parameterized class provides a public interface which is similar in // structure and intent to 'Queue<DATA>', with the exception that each item // stored in the 'TimeQueue' has an associated time value. Items are // retrieved or exchanged by proxy of a 'TimeQueueItem<DATA>', and are // referred to by an opaque data type 'TimeQueue::Handle' which serves to // identify an individual element on the Time Queue. Idiomatic usage of // 'TimeQueue' includes the member function 'popLE', which finds all items // on the queue whose 'bsls::TimeInterval' are less than a specified value // and transfers those items to a provided vector of items, and the member // function 'update', which can update the time value for a specific // 'TimeQueueItem' without removing it from the queue. // TYPES enum { k_NUM_INDEX_BITS_MIN = 8, k_NUM_INDEX_BITS_MAX = 24, k_NUM_INDEX_BITS_DEFAULT = 17 }; public: // TYPES typedef int Handle; // 'Handle' defines an alias for uniquely identifying a valid node in // the time queue. Handles are returned when nodes are added to the // time queue, and must be supplied to the 'update' and 'remove' // methods to identify existing nodes. When a node is removed, the // handle value becomes invalid, though invalidated handle values are // eventually reused. See the component-level documentation for more // details. class Key { // This type is a wrapper around a void pointer that will be supplied // and used by clients to uniquely identify an item in the queue. // PRIVATE DATA MEMBERS const void *d_key; public: // CREATORS explicit Key(const void *key) : d_key(key) // Create a 'Key' object having the specified 'key' value. {} explicit Key(int key) : d_key(reinterpret_cast<const void*>(static_cast<bsl::intptr_t>(key))) // Create a 'Key' object having the specified 'key' value cast to a // 'void *'. {} ~Key() // Destroy this 'Key' object. {} // ACCESSORS bool operator==(const Key& rhs) const // Return 'true' if this object has the same value as the specified // 'rhs' object, and 'false' otherwise. { return d_key == rhs.d_key; } bool operator!=(const Key& rhs) const // Return 'true' if this object does not have the same value as the // specified 'rhs' object, and 'false' otherwise. { return d_key != rhs.d_key; } }; private: // PRIVATE TYPES template <class VECTOR> struct IsVector; struct Node { // This queue is implemented internally as a map of time values, each // entry in the map storing a doubly-linked circular list of items // having the same time value. This struct provides the node in the // list. // PUBLIC DATA MEMBERS int d_index; bsls::TimeInterval d_time; Key d_key; Node *d_prev_p; Node *d_next_p; bsls::ObjectBuffer<DATA> d_data; // CREATORS Node() : d_index(0) , d_key(0) , d_prev_p(0) , d_next_p(0) // Create a 'Node' having a time value of 0. { } explicit Node(const bsls::TimeInterval& time) : d_index(0) , d_time(time) , d_key(0) , d_prev_p(0) , d_next_p(0) // Create a 'Node' having the specified 'time' value. { } }; typedef bsl::map<bsls::TimeInterval, Node*> NodeMap; // Internal typedef for the time index map. typedef typename NodeMap::iterator MapIter; // Internal typedef for the iterator type used to navigate the time // index. typedef typename NodeMap::const_iterator MapCIter; // Internal typedef for the const-iterator type used to navigate the // time index. // PRIVATE DATA MEMBERS const int d_indexMask; const int d_indexIterationMask; const int d_indexIterationInc; mutable bslmt::Mutex d_mutex; // used for synchronizing // access to this queue bsl::vector<Node*> d_nodeArray; // array of nodes in this queue bsls::AtomicPointer<Node> d_nextFreeNode_p; // pointer to the next free // node in this queue (the free // list is singly linked only, // using d_next_p) NodeMap d_map; // list of time values in // increasing time order bsls::AtomicInt d_length; // number of items currently in // this queue (not necessarily // equal to d_map.size()) bslma::Allocator *d_allocator_p; // allocator (held, not owned) // PRIVATE MANIPULATORS void freeNode(Node *node); // Prepare the specified 'node' for being reused on the free list by // incrementing the iteration count. Set 'd_prev_p' field to 0. template <class VECTOR> void popLEImp(const bsls::TimeInterval& time, VECTOR *buffer, int *newLength = 0, bsls::TimeInterval *newMinTime = 0); // Remove from this queue all the items that have a time value less // than or equal to the specified 'time', and optionally append into // the optionally specified 'buffer' a list of the removed items, // ordered by their corresponding time values (top item first). // Optionally load into the optionally specified 'newLength' the number // of items remaining in this queue, and into the optionally specified // 'newMinTime' the lowest remaining time value in this queue. Note // that 'newMinTime' is only loaded if there are items remaining in the // time queue; therefore, 'newLength' should be specified and examined // to determine whether items remain, and 'newMinTime' used only when // 'newLength' > 0. Also note that if 'DATA' follows the 'bdema' // allocator model, the allocator of the 'buffer' vector is used to // supply memory for the items appended to the 'buffer'. template <class VECTOR> void popLEImp(const bsls::TimeInterval& time, int maxTimers, VECTOR *buffer, int *newLength = 0, bsls::TimeInterval *newMinTime = 0); // Remove from this queue up to the specified 'maxTimers' number of // items that have a time value less than or equal to the specified // 'time', and optionally append into the optionally specified 'buffer' // a list of the removed items, ordered by their corresponding time // values (top item first). Optionally load into the optionally // specified 'newLength' the number of items remaining in this queue, // and into the optionally specified 'newMinTime' the lowest remaining // time value in this queue. The behavior is undefined unless // 'maxTimers' >= 0. Note that 'newMinTime' is only loaded if there // are items remaining in the time queue; therefore, 'newLength' should // be specified and examined to determine whether items remain, and // 'newMinTime' used only when 'newLength' > 0. Also note that if // 'DATA' follows the 'bdema' allocator model, the allocator of the // 'buffer' vector is used to supply memory. Note finally that all the // items appended into 'buffer' have a time value less than or equal to // the elements remaining in this queue. void putFreeNode(Node *node); // Destroy the data located at the specified 'node' and reattach this // 'node' to the front of the free list starting at 'd_nextFreeNode_p', // making 'node' the new 'd_nextFreeNode_p'. Note that the caller must // not have acquired the lock to this queue. void putFreeNodeList(Node *begin); // Destroy the 'DATA' of every node in the singly-linked list starting // at the specified 'begin' node and ending with a null pointer, and // reattach these nodes to the front of the free list starting at // 'd_nextFreeNode_p', making 'begin' the new 'd_nextFreeNode_p' and // calling 'freeNode'. Note that the caller must not have acquired the // lock to this queue. private: // NOT IMPLEMENTED TimeQueue(const TimeQueue&) BSLS_KEYWORD_DELETED; TimeQueue& operator=(const TimeQueue&) BSLS_KEYWORD_DELETED; public: // CREATORS explicit TimeQueue(bslma::Allocator *basicAllocator = 0); explicit TimeQueue(int numIndexBits, bslma::Allocator *basicAllocator = 0); // Create an empty time queue. Optionally specify 'numIndexBits' to // configure the number of index bits used by this object. If // 'numIndexBits' is not specified a default value of 17 is used. // Optionally specify a 'basicAllocator' used to supply memory. If // 'basicAllocator' is 0, the currently installed default allocator is // used. The behavior is undefined unless '8 <= numIndexBits <= 24'. // See the component-level documentation for more information regarding // 'numIndexBits'. explicit TimeQueue(bool poolTimerMemory, bslma::Allocator *basicAllocator = 0); TimeQueue(int numIndexBits, bool poolTimerMemory, bslma::Allocator *basicAllocator = 0); // [!DEPRECATED!] Use the other constructor overloads instead. Note // that the specified 'poolTimerMemory' argument controlled whether // additional memory used by an internal 'bsl::map' was pooled. When // 'bsl::map' was modified to pool its own nodes, this option became // irrelevant and is now ignored. ~TimeQueue(); // Destroy this time queue. // MANIPULATORS Handle add(const bsls::TimeInterval& time, const DATA& data, int *isNewTop = 0, int *newLength = 0); Handle add(const bsls::TimeInterval& time, const DATA& data, const Key& key, int *isNewTop = 0, int *newLength = 0); // Add a new item to this queue having the specified 'time' value, and // associated 'data'. Optionally use the specified 'key' to uniquely // identify the item in subsequent calls to 'remove' and 'update'. // Optionally load into the optionally specified 'isNewTop' a non-zero // value if the item is now the lowest item in this queue, and a 0 // value otherwise. If specified, load into the optionally specified // 'newLength', the new number of items in this queue. Return a value // that may be used to identify the newly added item in future calls to // time queue on success, and -1 if the maximum queue length has been // reached. Handle add(const TimeQueueItem<DATA>& item, int *isNewTop = 0, int *newLength = 0); // Add the value of the specified 'item' to this queue. Optionally // load into the optionally specified 'isNewTop' a non-zero value if // the replaces is now the lowest element in this queue, and a 0 value // otherwise. If specified, load into the optionally specified // 'newLength', the new number of elements in this queue. Return a // value that may be used to identify the newly added item in future // calls to time queue on success, and -1 if the maximum queue length // has been reached. int popFront(TimeQueueItem<DATA> *buffer = 0, int *newLength = 0, bsls::TimeInterval *newMinTime = 0); // Atomically remove the top item from this queue, and optionally load // into the optionally specified 'buffer' the time and associated data // of the item removed. Optionally load into the optionally specified // 'newLength', the number of items remaining in the queue. Optionally // load into the optionally specified 'newMinTime' the new lowest time // in this queue. Return 0 on success, and a non-zero value if there // are no items in the queue. Note that if 'DATA' follows the 'bdema' // allocator model, the allocator of the 'buffer' is used to supply // memory. void popLE(const bsls::TimeInterval& time); void popLE(const bsls::TimeInterval& time, bsl::vector<TimeQueueItem<DATA> > *buffer, int *newLength = 0, bsls::TimeInterval *newMinTime = 0); void popLE(const bsls::TimeInterval& time, std::vector<TimeQueueItem<DATA> > *buffer, int *newLength = 0, bsls::TimeInterval *newMinTime = 0); #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR void popLE(const bsls::TimeInterval& time, std::pmr::vector<TimeQueueItem<DATA> > *buffer, int *newLength = 0, bsls::TimeInterval *newMinTime = 0); #endif // Remove from this queue all the items that have a time value less // than or equal to the specified 'time', and optionally append into // the optionally specified 'buffer' a list of the removed items, // ordered by their corresponding time values (top item first). // Optionally load into the optionally specified 'newLength' the number // of items remaining in this queue, and into the optionally specified // 'newMinTime' the lowest remaining time value in this queue. Note // that 'newMinTime' is only loaded if there are items remaining in the // time queue; therefore, 'newLength' should be specified and examined // to determine whether items remain, and 'newMinTime' used only when // 'newLength' > 0. Also note that if 'DATA' follows the 'bdema' // allocator model, the allocator of the 'buffer' vector is used to // supply memory for the items appended to the 'buffer'. void popLE(const bsls::TimeInterval& time, int maxTimers); void popLE(const bsls::TimeInterval& time, int maxTimers, bsl::vector<TimeQueueItem<DATA> > *buffer, int *newLength = 0, bsls::TimeInterval *newMinTime = 0); void popLE(const bsls::TimeInterval& time, int maxTimers, std::vector<TimeQueueItem<DATA> > *buffer, int *newLength = 0, bsls::TimeInterval *newMinTime = 0); #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR void popLE(const bsls::TimeInterval& time, int maxTimers, std::pmr::vector<TimeQueueItem<DATA> > *buffer, int *newLength = 0, bsls::TimeInterval *newMinTime = 0); #endif // Remove from this queue up to the specified 'maxTimers' number of // items that have a time value less than or equal to the specified // 'time', and optionally append into the optionally specified 'buffer' // a list of the removed items, ordered by their corresponding time // values (top item first). Optionally load into the optionally // specified 'newLength' the number of items remaining in this queue, // and into the optionally specified 'newMinTime' the lowest remaining // time value in this queue. The behavior is undefined unless // 'maxTimers' >= 0. Note that 'newMinTime' is only loaded if there // are items remaining in the time queue; therefore, 'newLength' should // be specified and examined to determine whether items remain, and // 'newMinTime' used only when 'newLength' > 0. Also note that if // 'DATA' follows the 'bdema' allocator model, the allocator of the // 'buffer' vector is used to supply memory. Note finally that all the // items appended into 'buffer' have a time value less than or equal to // the elements remaining in this queue. int remove(Handle handle, int *newLength = 0, bsls::TimeInterval *newMinTime = 0, TimeQueueItem<DATA> *item = 0); int remove(Handle handle, const Key& key, int *newLength = 0, bsls::TimeInterval *newMinTime = 0, TimeQueueItem<DATA> *item = 0); // Remove from this queue the item having the specified 'handle', and // optionally load into the optionally specified 'item' the time and // data values of the recently removed item. Optionally use the // specified 'key' to uniquely identify the item. If specified, load // into the optionally specified 'newMinTime', the resulting lowest // time value remaining in the queue. Return 0 on success, and a // non-zero value if no item with the 'handle' exists in the queue. // Note that if 'DATA' follows the 'bdema' allocator model, the // allocator of the 'item' instance is used to supply memory. void removeAll(bsl::vector<TimeQueueItem<DATA> > *buffer = 0); // Remove all the items from this queue. Optionally specify a 'buffer' // in which to load the removed items. The resultant items in the // 'buffer' are ordered by increasing time interval; items of // equivalent time interval have arbitrary ordering. Note that the // allocator of the 'buffer' vector is used to supply memory. int update(Handle handle, const bsls::TimeInterval& newTime, int *isNewTop = 0); int update(Handle handle, const Key& key, const bsls::TimeInterval& newTime, int *isNewTop = 0); // Update the time value of the item having the specified 'handle' to // the specified 'newTime' and optionally load into the optionally // specified 'isNewTop' a non-zero value if the modified item is now // the lowest time value in the time queue or zero otherwise. Return 0 // on success, and a non-zero value if there is currently no item // having the 'handle' registered with this time queue. // ACCESSORS int length() const; // Return number of items in this queue. Note that the value returned // may be obsolete by the time it is received. bool isRegisteredHandle(Handle handle) const; bool isRegisteredHandle(Handle handle, const Key& key) const; // Return 'true' if an item having specified 'handle' is currently // registered with this time queue and false otherwise. int minTime(bsls::TimeInterval *buffer) const; // Load into the specified 'buffer', the time value of the lowest time // in this queue. Return 0 on success, and a non-zero value if this // queue is empty. int countLE(const bsls::TimeInterval& time) const; // Return the number of items in this queue that have a time value less // than or equal to the specified 'time'. Note that the value returned // may be obsolete by the time it is received. }; // ==================== // struct TimeQueueItem // ==================== template <class DATA> class TimeQueueItem { // This parameterized structure holds a time, data and associated handle. // This structure is used in the interface of 'TimeQueue<DATA>' to provide // thread-safe access to individual elements on the queue. Note that // 'DATA' must be default-constructible. public: // TRAITS BSLMF_NESTED_TRAIT_DECLARATION(TimeQueueItem, bslma::UsesBslmaAllocator); // PUBLIC TYPES typedef typename TimeQueue<DATA>::Handle Handle; typedef typename TimeQueue<DATA>::Key Key; private: bsls::TimeInterval d_time; // Time value DATA d_data; // Associated data value Handle d_handle; // Associated handle Key d_key; // Associated key public: // CREATORS explicit TimeQueueItem(bslma::Allocator *basicAllocator = 0); // Create an empty time queue item. Optionally specify a // 'basicAllocator' used to supply memory. If 'basicAllocator' is // zero, then use the currently installed default allocator. TimeQueueItem(const bsls::TimeInterval& time, const DATA& data, Handle handle, bslma::Allocator *basicAllocator = 0); // Create time queue item holding a copy of the specified 'data', with // the specified associated 'time' and 'handle' information. // Optionally specify a 'basicAllocator' used to supply memory. If // 'basicAllocator' is zero, then use the currently installed default // allocator. TimeQueueItem(const bsls::TimeInterval& time, const DATA& data, Handle handle, const Key& key, bslma::Allocator *basicAllocator = 0); // Create time queue item holding a copy of the specified 'data', with // the specified associated 'time', 'handle', and 'key' information. // Optionally specify a 'basicAllocator' used to supply memory. If // 'basicAllocator' is zero, then use the currently installed default // allocator. TimeQueueItem(const TimeQueueItem<DATA>& original, bslma::Allocator *basicAllocator = 0); // Create a copy of the specified 'original' time queue item. // Optionally specify a 'basicAllocator' used to supply memory. If // 'basicAllocator' is zero, then use the currently installed default // allocator. // MANIPULATORS TimeQueueItem& operator=(const TimeQueueItem<DATA>& rhs); // Set the value of this 'TimeQueueItem' to that of 'rhs'. bsls::TimeInterval& time(); // Return the modifiable time value associated with this item. DATA& data(); // Return the modifiable data instance associated with this item. Handle& handle() // Return the modifiable handle value associated with this item. { // this definition was moved into the class declaration to work around // a Visual Studio .NET 2003 bug. return d_handle; } Key& key(); // Return the modifiable key value associated with this item. // ACCESSORS const bsls::TimeInterval& time() const; // Return the non-modifiable time value associated with this item. const DATA& data() const; // Return the non-modifiable data associated with this item. Handle handle() const // Return the non-modifiable handle value associated with this item. { // this definition was moved into the class declaration to work around // a Visual Studio .NET 2003 bug. return d_handle; } const Key& key() const; // Return the non-modifiable key value associated with this item. }; // ========================= // TimeQueue<DATA>::IsVector // ========================= template <class DATA> template <class VECTOR> struct TimeQueue<DATA>::IsVector { // This 'struct' has a 'value' that evaluates to 'true' if the specified // 'VECTOR' is a 'bsl', 'std', or 'std::pmr' 'vector<VALUE>'. // TYPE typedef TimeQueueItem<DATA> Item; // CLASS DATA static const bool value = bsl::is_same<bsl::vector<Item>, VECTOR>::value #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR || bsl::is_same<std::pmr::vector<Item>, VECTOR>::value #endif || bsl::is_same<std::vector<Item>, VECTOR>::value; }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // --------- // TimeQueue // --------- // PRIVATE MANIPULATORS template <class DATA> inline void TimeQueue<DATA>::freeNode(Node *node) { node->d_index = ((node->d_index + d_indexIterationInc) & d_indexIterationMask) | (node->d_index & d_indexMask); if (!(node->d_index & d_indexIterationMask)) { node->d_index += d_indexIterationInc; } node->d_prev_p = 0; } template <class DATA> template <class VECTOR> void TimeQueue<DATA>::popLEImp(const bsls::TimeInterval& time, VECTOR *buffer, int *newLength, bsls::TimeInterval *newMinTime) { BSLMF_ASSERT(IsVector<VECTOR>::value); bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); MapIter it = d_map.begin(); Node *begin = 0; while (d_map.end() != it && it->first <= time) { Node *const first = it->second; Node *const last = first->d_prev_p; Node *node = first; do { if (buffer) { buffer->push_back(TimeQueueItem<DATA>(it->first, node->d_data.object(), node->d_index, node->d_key, d_allocator_p)); } freeNode(node); node = node->d_next_p; --d_length; } while (node != first); last->d_next_p = begin; begin = first; MapIter condemned = it; ++it; d_map.erase(condemned); } if (newLength) { *newLength = d_length; } if (d_map.end() != it && newMinTime) { *newMinTime = it->first; } lock.release()->unlock(); putFreeNodeList(begin); } template <class DATA> template <class VECTOR> void TimeQueue<DATA>::popLEImp(const bsls::TimeInterval& time, int maxTimers, VECTOR *buffer, int *newLength, bsls::TimeInterval *newMinTime) { BSLS_ASSERT(0 <= maxTimers); BSLMF_ASSERT(IsVector<VECTOR>::value); bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); MapIter it = d_map.begin(); Node *begin = 0; while (d_map.end() != it && it->first <= time && 0 < maxTimers) { Node *const first = it->second; Node *const last = first->d_prev_p; Node *node = first; Node *prevNode = first->d_prev_p; do { if (buffer) { buffer->push_back(TimeQueueItem<DATA>( it->first, node->d_data.object(), node->d_index, node->d_key, d_allocator_p)); } freeNode(node); prevNode = node; node = node->d_next_p; --d_length; --maxTimers; } while (0 < maxTimers && node != first); prevNode->d_next_p = begin; begin = first; if (node == first) { MapIter condemned = it; ++it; d_map.erase(condemned); } else { node->d_prev_p = last; last->d_next_p = node; it->second = node; break; } } if (newLength) { *newLength = d_length; } if (d_map.end() != it && newMinTime) { *newMinTime = it->first; } lock.release()->unlock(); putFreeNodeList(begin); } template <class DATA> void TimeQueue<DATA>::putFreeNode(Node *node) { node->d_data.object().~DATA(); Node *nextFreeNode = d_nextFreeNode_p; node->d_next_p = nextFreeNode; while (nextFreeNode != d_nextFreeNode_p.testAndSwap(nextFreeNode, node)) { nextFreeNode = d_nextFreeNode_p; node->d_next_p = nextFreeNode; } } template <class DATA> void TimeQueue<DATA>::putFreeNodeList(Node *begin) { if (begin) { begin->d_data.object().~DATA(); Node *end = begin; while (end->d_next_p) { end = end->d_next_p; end->d_data.object().~DATA(); } Node *nextFreeNode = d_nextFreeNode_p; end->d_next_p = nextFreeNode; while (nextFreeNode != d_nextFreeNode_p.testAndSwap(nextFreeNode, begin)) { nextFreeNode = d_nextFreeNode_p; end->d_next_p = nextFreeNode; } } return; } // CREATORS template <class DATA> TimeQueue<DATA>::TimeQueue(bslma::Allocator *basicAllocator) : d_indexMask((1 << k_NUM_INDEX_BITS_DEFAULT) - 1) , d_indexIterationMask(~(int)d_indexMask) , d_indexIterationInc(d_indexMask + 1) , d_nodeArray(basicAllocator) , d_nextFreeNode_p(0) , d_map(basicAllocator) , d_length(0) , d_allocator_p(bslma::Default::allocator(basicAllocator)) { } template <class DATA> TimeQueue<DATA>::TimeQueue(bool poolTimerMemory, bslma::Allocator *basicAllocator) : d_indexMask((1 << k_NUM_INDEX_BITS_DEFAULT) - 1) , d_indexIterationMask(~(int)d_indexMask) , d_indexIterationInc(d_indexMask + 1) , d_nodeArray(basicAllocator) , d_nextFreeNode_p(0) , d_map(basicAllocator) , d_length(0) , d_allocator_p(bslma::Default::allocator(basicAllocator)) { // The 'poolTimerMemory' option has been deprecated (see method // documentation). (void)poolTimerMemory; } template <class DATA> TimeQueue<DATA>::TimeQueue(int numIndexBits, bslma::Allocator *basicAllocator) : d_indexMask((1 << numIndexBits) - 1) , d_indexIterationMask(~d_indexMask) , d_indexIterationInc(d_indexMask + 1) , d_nodeArray(basicAllocator) , d_nextFreeNode_p(0) , d_map(basicAllocator) , d_length(0) , d_allocator_p(bslma::Default::allocator(basicAllocator)) { BSLS_ASSERT(k_NUM_INDEX_BITS_MIN <= numIndexBits && k_NUM_INDEX_BITS_MAX >= numIndexBits); } template <class DATA> TimeQueue<DATA>::TimeQueue(int numIndexBits, bool poolTimerMemory, bslma::Allocator *basicAllocator) : d_indexMask((1 << numIndexBits) - 1) , d_indexIterationMask(~d_indexMask) , d_indexIterationInc(d_indexMask + 1) , d_nodeArray(basicAllocator) , d_nextFreeNode_p(0) , d_map(basicAllocator) , d_length(0) , d_allocator_p(bslma::Default::allocator(basicAllocator)) { BSLS_ASSERT(k_NUM_INDEX_BITS_MIN <= numIndexBits && k_NUM_INDEX_BITS_MAX >= numIndexBits); // The 'poolTimerMemory' option has been deprecated (see method // documentation). (void)poolTimerMemory; } template <class DATA> TimeQueue<DATA>::~TimeQueue() { removeAll(); if (!d_nodeArray.empty()) { Node **data = &d_nodeArray.front(); const int numNodes = static_cast<int>(d_nodeArray.size()); for (int i = 0; i < numNodes; ++i) { d_allocator_p->deleteObjectRaw(data[i]); } } } // MANIPULATORS template <class DATA> inline typename TimeQueue<DATA>:: Handle TimeQueue<DATA>::add( const bsls::TimeInterval& time, const DATA& data, int *isNewTop, int *newLength) { return add(time, data, Key(0), isNewTop, newLength); } template <class DATA> typename TimeQueue<DATA>:: Handle TimeQueue<DATA>::add( const bsls::TimeInterval& time, const DATA& data, const Key& key, int *isNewTop, int *newLength) { bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); Node *node; if (d_nextFreeNode_p) { // All allocation of nodes goes through this routine, which is guarded // by the mutex. So no other thread will remove anything from the free // list while this code is executing. However, other threads may add // to the free list. node = d_nextFreeNode_p; Node *next = node->d_next_p; while (node != d_nextFreeNode_p.testAndSwap(node, next)) { node = d_nextFreeNode_p; next = node->d_next_p; } } else { // The number of nodes cannot grow to a size larger than the range of // available indices. if ((int)d_nodeArray.size() >= d_indexMask - 1) { return -1; // RETURN } node = new (*d_allocator_p) Node; d_nodeArray.push_back(node); node->d_index = static_cast<int>(d_nodeArray.size()) | d_indexIterationInc; } node->d_time = time; node->d_key = key; bslalg::ScalarPrimitives::copyConstruct(&node->d_data.object(), data, d_allocator_p); { MapIter it = d_map.find(time); if (d_map.end() == it) { node->d_prev_p = node; node->d_next_p = node; d_map[time] = node; } else { node->d_prev_p = it->second->d_prev_p; it->second->d_prev_p->d_next_p = node; node->d_next_p = it->second; it->second->d_prev_p = node; } } ++d_length; if (isNewTop) { *isNewTop = d_map.begin()->second == node && node->d_prev_p == node; } if (newLength) { *newLength = d_length; } BSLS_ASSERT(-1 != node->d_index); return node->d_index; } template <class DATA> inline typename TimeQueue<DATA>::Handle TimeQueue<DATA>::add( const TimeQueueItem<DATA>& item, int *isNewTop, int *newLength) { return add(item.time(), item.data(), item.key(), isNewTop, newLength); } template <class DATA> int TimeQueue<DATA>::popFront(TimeQueueItem<DATA> *buffer, int *newLength, bsls::TimeInterval *newMinTime) { bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); MapIter it = d_map.begin(); if (d_map.end() == it) { return 1; // RETURN } Node *node = it->second; if (buffer) { buffer->time() = node->d_time; buffer->data() = node->d_data.object(); buffer->handle() = node->d_index; buffer->key() = node->d_key; } if (node->d_next_p != node) { node->d_prev_p->d_next_p = node->d_next_p; node->d_next_p->d_prev_p = node->d_prev_p; if (it->second == node) { it->second = node->d_next_p; } } else { d_map.erase(it); } freeNode(node); --d_length; if (d_length && newMinTime && !d_map.empty()) { *newMinTime = d_map.begin()->first; } if (newLength) { *newLength = d_length; } lock.release()->unlock(); putFreeNode(node); return 0; } template <class DATA> inline void TimeQueue<DATA>::popLE(const bsls::TimeInterval& time) { popLEImp(time, static_cast<bsl::vector<TimeQueueItem<DATA> > *>(0)); } template <class DATA> inline void TimeQueue<DATA>::popLE(const bsls::TimeInterval& time, bsl::vector<TimeQueueItem<DATA> > *buffer, int *newLength, bsls::TimeInterval *newMinTime) { popLEImp(time, buffer, newLength, newMinTime); } template <class DATA> inline void TimeQueue<DATA>::popLE(const bsls::TimeInterval& time, std::vector<TimeQueueItem<DATA> > *buffer, int *newLength, bsls::TimeInterval *newMinTime) { popLEImp(time, buffer, newLength, newMinTime); } #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR template <class DATA> inline void TimeQueue<DATA>::popLE(const bsls::TimeInterval& time, std::pmr::vector<TimeQueueItem<DATA> > *buffer, int *newLength, bsls::TimeInterval *newMinTime) { popLEImp(time, buffer, newLength, newMinTime); } #endif template <class DATA> inline void TimeQueue<DATA>::popLE(const bsls::TimeInterval& time, int maxTimers) { popLEImp(time, maxTimers, static_cast<bsl::vector<TimeQueueItem<DATA> > *>(0)); } template <class DATA> inline void TimeQueue<DATA>::popLE(const bsls::TimeInterval& time, int maxTimers, bsl::vector<TimeQueueItem<DATA> > *buffer, int *newLength, bsls::TimeInterval *newMinTime) { popLEImp(time, maxTimers, buffer, newLength, newMinTime); } template <class DATA> inline void TimeQueue<DATA>::popLE(const bsls::TimeInterval& time, int maxTimers, std::vector<TimeQueueItem<DATA> > *buffer, int *newLength, bsls::TimeInterval *newMinTime) { popLEImp(time, maxTimers, buffer, newLength, newMinTime); } #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR template <class DATA> inline void TimeQueue<DATA>::popLE(const bsls::TimeInterval& time, int maxTimers, std::pmr::vector<TimeQueueItem<DATA> > *buffer, int *newLength, bsls::TimeInterval *newMinTime) { popLEImp(time, maxTimers, buffer, newLength, newMinTime); } #endif template <class DATA> inline int TimeQueue<DATA>::remove(typename TimeQueue<DATA>::Handle handle, int *newLength, bsls::TimeInterval *newMinTime, TimeQueueItem<DATA> *item) { return remove(handle, Key(0), newLength, newMinTime, item); } template <class DATA> int TimeQueue<DATA>::remove(typename TimeQueue<DATA>::Handle handle, const Key& key, int *newLength, bsls::TimeInterval *newMinTime, TimeQueueItem<DATA> *item) { bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); int index = ((int)handle & d_indexMask) - 1; if (index < 0 || index >= (int)d_nodeArray.size()) { return 1; // RETURN } Node *node = d_nodeArray[index]; if (node->d_index != (int)handle || node->d_key != key || 0 == node->d_prev_p) { return 1; // RETURN } if (item) { item->time() = node->d_time; item->data() = node->d_data.object(); item->handle() = node->d_index; item->key() = node->d_key; } if (node->d_next_p != node) { node->d_prev_p->d_next_p = node->d_next_p; node->d_next_p->d_prev_p = node->d_prev_p; MapIter it = d_map.find(node->d_time); if (it->second == node) { it->second = node->d_next_p; } } else { d_map.erase(node->d_time); } freeNode(node); --d_length; if (newLength) { *newLength = d_length; } if (d_length && newMinTime) { BSLS_ASSERT(! d_map.empty()); *newMinTime = d_map.begin()->first; } lock.release()->unlock(); putFreeNode(node); return 0; } template <class DATA> void TimeQueue<DATA>::removeAll(bsl::vector<TimeQueueItem<DATA> > *buffer) { bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); MapIter it = d_map.begin(); Node *begin = 0; while (d_map.end() != it) { Node *const first = it->second; Node *const last = first->d_prev_p; Node *node = first; do { if (buffer) { buffer->push_back(TimeQueueItem<DATA>( it->first, node->d_data.object(), node->d_index, node->d_key, d_allocator_p)); } freeNode(node); node = node->d_next_p; --d_length; } while (node != first); last->d_next_p = begin; begin = first; MapIter condemned = it; ++it; d_map.erase(condemned); } lock.release()->unlock(); putFreeNodeList(begin); } template <class DATA> inline int TimeQueue<DATA>::update(typename TimeQueue<DATA>::Handle handle, const bsls::TimeInterval& newTime, int *isNewTop) { return update(handle, Key(0), newTime, isNewTop); } template <class DATA> int TimeQueue<DATA>::update(typename TimeQueue<DATA>::Handle handle, const Key& key, const bsls::TimeInterval& newTime, int *isNewTop) { bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); int index = ((int)handle & d_indexMask) - 1; if (index < 0 || (unsigned) index >= d_nodeArray.size()) { return 1; // RETURN } Node *node = d_nodeArray[index]; if (node->d_index != (int)handle || node->d_key != key) { return 1; // RETURN } if (node->d_prev_p != node) { node->d_prev_p->d_next_p = node->d_next_p; node->d_next_p->d_prev_p = node->d_prev_p; MapIter it = d_map.find(node->d_time); if (it->second == node) { it->second = node->d_next_p; } } else { d_map.erase(node->d_time); } node->d_time = newTime; MapIter it = d_map.find(newTime); if (d_map.end() == it) { node->d_prev_p = node; node->d_next_p = node; d_map[newTime] = node; } else { node->d_prev_p = it->second->d_prev_p; it->second->d_prev_p->d_next_p = node; node->d_next_p = it->second; it->second->d_prev_p = node; } if (isNewTop) { *isNewTop = d_map.begin()->second == node && node->d_prev_p == node; } return 0; } // ACCESSORS template <class DATA> inline int TimeQueue<DATA>::length() const { return d_length; } template <class DATA> inline bool TimeQueue<DATA>::isRegisteredHandle( typename TimeQueue<DATA>::Handle handle) const { return isRegisteredHandle(handle, Key(0)); } template <class DATA> inline bool TimeQueue<DATA>::isRegisteredHandle( typename TimeQueue<DATA>::Handle handle, const Key& key) const { bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); int index = (handle & d_indexMask) - 1; if ( 0 > index || index >= (int)d_nodeArray.size()) { return false; // RETURN } Node *node = d_nodeArray[index]; if (node->d_index != (int)handle || node->d_key != key) { return false; // RETURN } return true; } template <class DATA> inline int TimeQueue<DATA>::minTime(bsls::TimeInterval *buffer) const { bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); if (d_map.empty()) { return 1; // RETURN } *buffer = d_map.begin()->first; return 0; } template <class DATA> inline int TimeQueue<DATA>::countLE(const bsls::TimeInterval& time) const { int count = 0; bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex); for (MapCIter it = d_map.cbegin(); it != d_map.cend() && it->first <= time; ++it) { Node *first = it->second; Node *node = first; do { BSLS_ASSERT(count < (1 << k_NUM_INDEX_BITS_MAX) - 1); // container size is bounded to 2^24-1 BSLMF_ASSERT((1 << k_NUM_INDEX_BITS_MAX) - 1 < INT_MAX); // container size bound prevents overflow of 'count' ++count; node = node->d_next_p; } while (node != first); } return count; } // -------------------- // struct TimeQueueItem // -------------------- // CREATORS template <class DATA> TimeQueueItem<DATA>::TimeQueueItem(bslma::Allocator *basicAllocator) : d_key(0) { bslma::DestructionUtil::destroy(&d_data); bslalg::ScalarPrimitives::defaultConstruct(&d_data, basicAllocator); } template <class DATA> TimeQueueItem<DATA>:: TimeQueueItem(TimeQueueItem<DATA> const& original, bslma::Allocator *basicAllocator) : d_time(original.d_time) // require that 'd_data' be default-constructible, hopefully at no cost , d_handle(original.d_handle) , d_key(original.d_key) { bslma::DestructionUtil::destroy(&d_data); bslalg::ScalarPrimitives::copyConstruct(&d_data, original.d_data, basicAllocator); } template <class DATA> TimeQueueItem<DATA>:: TimeQueueItem(const bsls::TimeInterval& time, const DATA& data, Handle handle, bslma::Allocator *basicAllocator) : d_time(time) // require that 'd_data' be default-constructible, hopefully at no cost , d_handle(handle) , d_key(0) { bslma::DestructionUtil::destroy(&d_data); bslalg::ScalarPrimitives::copyConstruct(&d_data, data, basicAllocator); } template <class DATA> TimeQueueItem<DATA>:: TimeQueueItem(const bsls::TimeInterval& time, const DATA& data, Handle handle, const Key& key, bslma::Allocator *basicAllocator) : d_time(time) // require that 'd_data' be default-constructible, hopefully at no cost , d_handle(handle) , d_key(key) { bslma::DestructionUtil::destroy(&d_data); bslalg::ScalarPrimitives::copyConstruct(&d_data, data, basicAllocator); } // MANIPULATORS template <class DATA> inline TimeQueueItem<DATA>& TimeQueueItem<DATA>::operator=( const TimeQueueItem<DATA>& rhs) { d_time = rhs.d_time; d_data = rhs.d_data; d_handle = rhs.d_handle; d_key = rhs.d_key; return *this; } template <class DATA> inline bsls::TimeInterval& TimeQueueItem<DATA>::time() { return d_time; } template <class DATA> inline DATA& TimeQueueItem<DATA>::data() { return d_data; } } // close package namespace #if 0 namespace bdlcc { // this definition was moved into the class declaration // to work around a Visual Studio .NET 2003 bug. template <typename DATA> inline typename TimeQueueItem<DATA>::Handle& TimeQueueItem<DATA>::handle() { return d_handle; } } // close package namespace #endif namespace bdlcc { template <class DATA> inline typename TimeQueueItem<DATA>::Key& TimeQueueItem<DATA>::key() { return d_key; } // ACCESSORS template <class DATA> inline const bsls::TimeInterval& TimeQueueItem<DATA>::time() const { return d_time; } template <class DATA> inline const DATA& TimeQueueItem<DATA>::data() const { return d_data; } } // close package namespace #if 0 namespace bdlcc { // this definition was moved into the class declaration // to work around a Visual Studio .NET 2003 bug. template <typename DATA> inline typename TimeQueueItem<DATA>::Handle TimeQueueItem<DATA>::handle() const { return d_handle; } } // close package namespace #endif namespace bdlcc { template <class DATA> inline const typename TimeQueueItem<DATA>::Key& TimeQueueItem<DATA>::key() const { return d_key; } } // 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 ----------------------------------