Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bdlmt_timereventscheduler
[Package bdlmt]

Provide a thread-safe recurring and non-recurring event scheduler. More...

Namespaces

namespace  bdlmt

Detailed Description

Outline
Purpose:
Provide a thread-safe recurring and non-recurring event scheduler.
Classes:
bdlmt::TimerEventScheduler thread-safe event scheduler
See also:
Component bdlmt_eventscheduler, Component bdlcc_timequeue
Description:
This component provides a thread-safe event scheduler, bdlmt::TimerEventScheduler. It provides methods to schedule and cancel recurring and non-recurring events. (A recurring event is also referred to as a clock). The callbacks are processed by a separate thread (called the dispatcher thread). By default the callbacks are executed in the dispatcher thread, but this behavior can be altered by providing a dispatcher functor at the creation time (see the section "The dispatcher thread and the dispatcher functor"). Use this component for implementing timeouts, deferred executions, calendars and reminders, and recurring tasks, among other time-bound behaviors.
The number of active events permitted by the timer-event scheduler defaults to an implementation defined constant, and in any case no more than 2**24 - 1. Note that if the scheduled event goes into infinite loop, and the default displatcher is used, the event scheduler may get into live lock.
Comparison to bdlmt::EventScheduler:
This class has been made mostly obsolete by the newer bdlmt_eventscheduler, which addresses two main disadvantages of this component: 1) bdlmt_timereventscheduler can only manage a finite number of events -- this limit is in the millions, but bdlmt_eventscheduler has no such limit; and 2) accessing the queue of a bdlmt::TimerEventScheduler is inefficient when there is a large number of managed events (since adding or removing an event involves a linear search); bdlmt_eventscheduler has a more sophisticated queue that can be accessed in constant or worst-case log(n) time. The advantage this component provides over bdlmt_eventscheduler is that it provides light-weight handles to events in the queue, while bdlmt_eventscheduler provides more heavy-weight reference-counted handles that must be released.
Order of Execution of Events:
It is intended that recurring and non-recurring events are processed as close as possible to their respective time values. Delays and unfairness in thread contention can sometimes delay execution, but this component guarantees that (1) events are processed in increasing time order, and (2) are never processed sooner than their corresponding time (but could be processed arbitrarily long afterward, if the dispatcher thread has not been able to gain control in the meantime, due to thread contention or to a long event).
Note that it is possible to schedule events in a scheduler that has not been started yet. When starting a scheduler, scheduled events whose times have already passed will be dispatched as soon as possible after the start time, still in order of their corresponding time.
The only exception to those guarantees are when an event e1 at time T say, is already pending while another event e2 is scheduled at a time <= T. Then the dispatcher will complete the execution of e1 before dispatching e2.
The Dispatcher Thread and the Dispatcher Functor:
Between calls to start and stop, the scheduler creates a separate thread (called the dispatcher thread) to process all the callbacks. The dispatcher thread executes the callbacks by passing them to the dispatcher functor (optionally specified at creation time). The default dispatcher functor simply invokes the passed callback, effectively executing it in the dispatcher thread. Users can alter this behavior by defining their own dispatcher functor (for example in order to use a thread pool or a separate thread to run the callbacks). In that case, the user-supplied functor will still be run in the dispatcher thread, different from the scheduler thread.
Thread Safety:
The bdlmt::TimerEventScheduler class is both fully thread-safe (i.e., all non-creator methods can correctly execute concurrently), and is thread-enabled (i.e., the classes does not function correctly in a non-multi-threading environment). See bsldoc_glossary for complete definitions of fully thread-safe and thread-enabled.
Supported Clock-Types:
The component bsls::SystemClockType supplies the enumeration indicating the system clock on which times supplied to other methods should be based. If the clock type indicated at construction is bsls::SystemClockType::e_REALTIME, time should be expressed as an absolute offset since 00:00:00 UTC, January 1, 1970 (which matches the epoch used in bdlt::SystemTime::now(bsls::SystemClockType::e_REALTIME). If the clock type indicated at construction is bsls::SystemClockType::e_MONOTONIC, time should be expressed as an absolute offset since the epoch of this clock (which matches the epoch used in bdlt::SystemTime::now(bsls::SystemClockType::e_MONOTONIC).
The current epoch time for a particular bdlmt::TimerEventScheduler instance according to the correct clock is available via the bdlmt::TimerEventScheduler::now accessor.
Event Clock Substitution:
For testing purposes, a class bdlmt::TimerEventSchedulerTestTimeSource is provided to allow manual manipulation of the system-time observed by a bdlmt::TimerEventScheduler. A test driver that interacts with a bdlmt::TimerEventScheduler can use a bdlmt::TimerEventSchedulerTestTimeSource object to control when scheduled events are triggered, allowing more reliable tests.
A bdlmt::TimerEventSchedulerTestTimeSource can be constructed for any existing bdlmt::TimerEventScheduler object that has not been started and has not had any events scheduled. When the bdlmt::TimerEventSchedulerTestTimeSource is constructed, it will replace the clock of the bdlmt::TimerEventScheduler to which it is attached. The internal clock of the bdlmt::TimerEventSchedulerTestTimeSource will be initialized with an arbitrary value on construction, and will advance only when explicitly instructed to do so by a call to bdlt::TimerEventSchedulerTestTimeSource::advanceTime. The current value of the internal clock can be accessed by calling bdlt::TimerEventSchedulerTestTimeSource::now, or bdlmt::TimerEventScheduler::now on the instance supplied to the bdlmt::TimerEventSchedulerTestTimeSource.
Note that the initial value of bdlt::TimerEventSchedulerTestTimeSource::now is intentionally not synchronized with bdlt::SystemTime::now. All test events scheduled for a bdlmt::TimerEventScheduler that is instrumented with a bdlt::TimerEventSchedulerTestTimeSource should be scheduled in terms of an offset from whatever arbitrary time is reported by bdlt::TimerEventSchedulerTestTimeSource. See Example 3 below for an illustration of how this is done.
Thread Name for Dispatcher Thread:
To facilitate debugging, users can provide a thread name as the threadName attribute of the bslmt::ThreadAttributes argument passed to the start method, that will be used for the dispatcher thread. The thread name should not be used programmatically, but will appear in debugging tools on platforms that support naming threads to help users identify the source and purpose of a thread. If no ThreadAttributes object is passed, or if the threadName attribute is not set, the default value "bdl.TimerEvent" will be used.
Usage:
The following example shows how to use a bdlmt::TimerEventScheduler to implement a timeout mechanism in a server. my_Session maintains several connections. It closes a connection if the data for it does not arrive before a timeout (specified at the server creation time).
    class my_Session{
        // This class encapsulates the data and state associated with a
        // connection and provides a method 'processData' to process the
        // incoming data for the connection.
      public:
        int processData(void *data, int length);
            // Process the specified 'data' of the specified 'length'.
    };

    class my_Server {
     // This class implements a server maintaining several connections.
     // A connection is closed if the data for it does not arrive
     // before a timeout (specified at the server creation time).

     struct Connection {
         bdlmt::TimerEventScheduler::Handle d_timerId; // handle for timeout
                                                     // event

         my_Session *d_session_p;                    // session for this
                                                     // connection
     };

     bsl::vector<Connection*>     d_connections; // maintained connections
     bdlmt::TimerEventScheduler     d_scheduler;   // timeout event scheduler
     bsls::TimeInterval            d_ioTimeout;   // time out

     void newConnection(Connection *connection);
         // Add the specified 'connection' to this server and schedule
         // the timeout event that closes this connection if the data
         // for this connection does not arrive before the timeout.

     void closeConnection(Connection *connection);
         // Close the specified 'connection' and remove it from this server.

     void dataAvailable(Connection *connection, void *data, int length);
         // Return if the specified 'connection' has already timed-out.
         // If not, cancel the existing timeout event for the 'connection',
         // process the specified 'data' of the specified 'length' and
         // schedule a new timeout event that closes the 'connection' if
         // the data does not arrive before the timeout.

   public:
     my_Server(const bsls::TimeInterval&  ioTimeout,
               bslma::Allocator          *allocator = 0);
         // Construct a 'my_Server' object with a timeout value of
         // 'ioTimeout' seconds.  Optionally specify a 'allocator' used to
         // supply memory.  If 'allocator' is 0, the currently installed
         // default allocator is used.

     ~my_Server();
         // Perform the required clean-up and destroy this object.
 };

 my_Server::my_Server(const bsls::TimeInterval&  ioTimeout,
                      bslma::Allocator          *allocator)
 : d_connections(allocator)
 , d_scheduler(allocator)
 , d_ioTimeout(ioTimeout)
 {
      // logic to start monitoring the arriving connections or data

      d_scheduler.start();
 }

 my_Server::~my_Server()
 {
     // logic to clean up

     d_scheduler.stop();
 }

 void my_Server::newConnection(my_Server::Connection *connection)
 {
     // logic to add 'connection' to the 'd_connections'

     // setup the timeout for data arrival
     connection->d_timerId = d_scheduler.scheduleEvent(
        d_scheduler.now() + d_ioTimeout,
        bdlf::BindUtil::bind(&my_Server::closeConnection, this, connection));
 }

 void my_Server::closeConnection(my_Server::Connection *connection)
 {
     // logic to close the 'connection' and remove it from 'd_ioTimeout'
 }

 void my_Server::dataAvailable(my_Server::Connection *connection,
                               void                  *data,
                               int                   length)
 {
     // If connection has already timed out and closed, simply return.
     if (d_scheduler.cancelEvent(connection->d_timerId)) {
         return;                                                    // RETURN
     }

     // process the data
     connection->d_session_p->processData(data, length);

     // setup the timeout for data arrival
     connection->d_timerId = d_scheduler.scheduleEvent(
        d_scheduler.now() + d_ioTimeout,
        bdlf::BindUtil::bind(&my_Server::closeConnection, this, connection));
 }
Example 3: Using the Test Time Source:
For testing purposes, the class bdlmt::TimerEventSchedulerTestTimeSource is provided to allow a test to manipulate the system-time observed by a bdlmt::TimeEventScheduler in order to control when events are triggered. After a scheduler is constructed, a bdlmt::TimerEventSchedulerTestTimeSource object can be created atop the scheduler. A test can then use the test time-source to advance the scheduler's observed system-time in order to dispatch events in a manner coordinated by the test. Note that a bdlmt::TimerEventSchedulerTestTimeSource must be created on an event-scheduler before any events are scheduled, or the event-scheduler is started.
This example shows how the clock may be altered:
 void myCallbackFunction() {
     puts("Event triggered!");
 }

 void testCase() {
     // Construct the scheduler
     bdlmt::TimerEventScheduler scheduler;

     // Construct the time-source.
     // Install the time-source in the scheduler.
     bdlmt::TimerEventSchedulerTestTimeSource timeSource(&scheduler);

     // Retrieve the initial time held in the time-source.
     bsls::TimeInterval initialAbsoluteTime = timeSource.now();

     // Schedule a single-run event at a 35s offset.
     scheduler.scheduleEvent(initialAbsoluteTime + 35,
                             bsl::function<void()()>(&myCallbackFunction));

     // Schedule a 30s recurring event.
     scheduler.startClock(bsls::TimeInterval(30),
                          bsl::function<void()()>(&myCallbackFunction));

     // Start the dispatcher thread.
     scheduler.start();

     // Advance the time by 40 seconds so that each
     // event will run once.
     timeSource.advanceTime(bsls::TimeInterval(40));

     // The line "Event triggered!" should now have
     // been printed to the console twice.

     scheduler.stop();
 }
Note that this feature should be used only for testing purposes, never in production code.