|
BDE 4.14.0 Production release
|
Provide an efficient queue for time events.
struct) Templatized item in the time event queueThis 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 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.
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.
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.
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.
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.
The my_Connection structure is used by my_Server to manage a single physical connection on behalf of a my_Session.
Protocol class my_Session provides a pure abstract protocol to manage a single "session" to be associated with a specific connection on a server.
The constructor and destructor do nothing:
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.
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.
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.
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.
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.
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.
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.
Finally, we are now in a position to implement the two thread dispatchers:
In order to test our server, we provide two concrete implementations of a test session and of a test server as follows.
The program that would exercise this test server would simply consist of:
The output of this program would look something as follows: