// bdlmt_eventscheduler.h                                             -*-C++-*-
#ifndef INCLUDED_BDLMT_EVENTSCHEDULER
#define INCLUDED_BDLMT_EVENTSCHEDULER

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a thread-safe recurring and one-time event scheduler.
//
//@CLASSES:
//  bdlmt::EventScheduler: a thread-safe event scheduler
//  bdlmt::EventSchedulerEventHandle: handle to a single scheduled event
//  bdlmt::EventSchedulerRecurringEventHandle: handle to a recurring event
//  bdlmt::EventSchedulerTestTimeSource: class for testing time changes
//
//@METRICS:
//
// * `bde.startlag`
//   > seconds of delay in starting the next event (may be 0.0)
//
// Associated Metric Attributes:
//  * object type name: "bdlmt.eventscheduler"
//  * object type abbreviation: "es"
//
//@SEE_ALSO: bdlmt_timereventscheduler
//
//@DESCRIPTION: This component provides a thread-safe event scheduler.
// `bdlmt::EventScheduler`, that implements methods to schedule and cancel
// recurring and one-time events.  All of the callbacks for these events are
// processed by a separate thread (called the dispatcher thread).  By default
// the callbacks are also executed in the dispatcher thread, but that behavior
// can be altered by providing a dispatcher functor at creation time (see the
// section {The Dispatcher Thread and the Dispatcher Functor}).
//
// Events may be referred to by `bdlmt::EventSchedulerEventHandle` and
// `bdlmt::EventSchedulerRecurringEventHandle` objects, which clean up after
// themselves when they go out of scope, or by `Event` and `RecurringEvent`
// pointers, which must be released using `releaseEventRaw`.  Such pointers are
// used in the "Raw" API of this class and must be used carefully.  Note that
// the Handle objects have an implicit conversion to the corresponding `Event`
// or `RecurringEvent` pointer types, effectively providing extra overloads for
// methods that take a `const Event*` to also take a `const EventHandle&`.
//
///Comparison to `bdlmt::TimerEventScheduler`
///------------------------------------------
// This component was written after `bdlmt_timereventscheduler`, which suffered
// from a couple of short-comings:
// 1. there was a maximum number of events it could manage, and
// 2. it was inefficient at dealing with large numbers of events
//
// This component addresses both those problems -- there is no limit on the
// number of events it can manage, and it is more efficient at dealing with
// large numbers of events.  The disadvantage of this component relative to
// `bdlmt_timereventscheduler` is that handles referring to managed events in a
// `bdlmt::EventScheduler` are reference-counted and need to be released, while
// handles of events in a `bdlmt::TimerEventScheduler` are integral types that
// do not need to be released.
//
///Thread Safety and "Raw" Event Pointers
///--------------------------------------
// `bdlmt::EventScheduler` is thread-safe and thread-enabled, meaning that
// multiple threads may use their own instances of the class or use a shared
// instance without further synchronization.  The thread safety and correct
// behavior of the component depend on the correct usage of `Event` pointers,
// which refer to scheduled events in the "Raw" API of this class.  In
// particular:
// * Every `Event*`  and `RecurringEvent*` populated by `scheduleEventRaw`
//   and `scheduleRecurringEventRaw` must be released using `releaseEventRaw.`
//     - Pointers are not released automatically when events are completed.
//     - Pointers are not released automatically when events are canceled.
//     - Events are not canceled when pointers to them are released.
// * Pointers must not be used after being released.
// * Pointers must never be shared or duplicated without using
//   `addEventRefRaw` and `addRecurringEventRefRaw` to get additional
//   references; *each* such added reference must be released separately.
//
// `bdlmt::EventSchedulerEventHandle` and
// `bdlmt::EventSchedulerRecurringEventHandle` are **const thread-safe**.  It
// is not safe for multiple threads to invoke non-`const` methods on the same
// `EventHandle` or `RecurringEventHandle` object concurrently.
//
///The Dispatcher Thread and the Dispatcher Functor
///------------------------------------------------
// The scheduler creates a single 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).  Note
// that the user-supplied functor will still be run in the dispatcher thread.
//
// CAVEAT: Using a dispatcher functor such as the example above (to execute the
// callback in a separate thread) violates the guarantees of
// ``cancelEventAndWait()``.  Users who specify a dispatcher functor that
// transfers the event to another thread for execution should not use
// ``cancelEventAndWait()``, and should instead ensure that the lifetime of any
// object bound to an event exceeds the lifetime of the mechanism used by the
// customized dispatcher functor.
//
///Timer Resolution and Order of Execution
///---------------------------------------
// It is intended that recurring and one-time events are processed as closely
// as possible to their respective time values, and that they are processed in
// the order scheduled.  However, this component **guarantees** only that
// events will not be executed before their scheduled time.  Generally, events
// that are scheduled more than 1 microsecond apart will be executed in the
// order scheduled; but different behavior may be observed when events are
// submitted after (or shortly before) their scheduled time.
//
// When events are executed in the dispatcher thread and take longer to
// complete than the time between events, the dispatcher can fall behind.  In
// this case, events will be executed in the correct order as soon as the
// dispatcher thread becomes available; once the backlog is worked off, events
// will be executed at or near their scheduled times.
//
///Supported Clock Types
///---------------------
// An `EventScheduler` optionally accepts a clock type at construction
// indicating the clock by which it will internally schedule events.  The clock
// type may be indicated by either a `bsls::SystemClockType::Enum` value, a
// `bsl::chrono::system_clock` object (which is equivalent to specifying
// `e_REALTIME`), or a `bsl::chrono::steady_clock` object (equivalent to
// specifying `e_MONOTONIC`).  If a clock type is not specified, `e_REALTIME`
// is used.
//
///Scheduling Using a `bsl::chrono::time_point`
/// - - - - - - - - - - - - - - - - - - - - - -
// When creating either a one-time or recurring event, clients may pass a
// `bsl::chrono::time_point` indicating the time the event should occur.  This
// `time_point` object can be associated with an arbitrary clock.  If the
// `time_point` is associated with a different clock than was indicated at
// construction of the event scheduler, those time points are converted to be
// relative to the event scheduler's clock for processing.  A possible
// implementation of such a conversion would be:
// ```
// bsls::TimeInterval(time - t_CLOCK::now()) + eventScheduler.now()
// ```
// where `time` is a `time_point`, `t_CLOCK` is the clock associated with
// `time`, and `eventScheduler` is the `EventScheduler` on which the event is
// being scheduled.  Notice that the conversion adds some imprecision and
// overhead to evaluation of the event.  An event scheduler guarantees an event
// will occur at or after the supplied `time_point`, even if that `time_point`
// is defined in terms of a `t_CLOCK` different from the one used by the event
// scheduler.  If there is a discontinuity between the clock for a `time_point`
// and the event scheduler's clock, additional processing overhead may result
// (because the event may need to be rescheduled), and the event may also occur
// later than what one might otherwise expect.
//
///Scheduling Using a `bsls::TimeInterval`
///- - - - - - - - - - - - - - - - - - - -
// When creating either a one-time or recurring event, clients may pass a
// `bsls::TimeInterval` indicating the time the event should occur as an offset
// from an epoch.  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::CurrentTime::now(bsls::SystemClockType::e_REALTIME)`, and
// `bsl::chrono::system_clock::now()`).  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::CurrentTime::now(bsls::SystemClockType::e_MONOTONIC)` and
// `bsl::chrono::steady_clock`).
//
///Event Clock Substitution
///------------------------
// For testing purposes, a class `bdlmt::EventSchedulerTestTimeSource` is
// provided to allow manual manipulation of the system-time observed by a
// `bdlmt::EventScheduler`.  A test driver that interacts with a
// `bdlmt::EventScheduler` can use a `bdlmt::EventSchedulerTestTimeSource`
// object to control when scheduled events are triggered, allowing more
// reliable tests.
//
// A `bdlmt::EventSchedulerTestTimeSource` can be constructed for any existing
// `bdlmt::EventScheduler` object that has not been started and has not had any
// events scheduled.  When the `bdlmt::EventSchedulerTestTimeSource` is
// constructed, it will replace the clock of the `bdlmt::EventScheduler` to
// which it is attached.  The internal clock of the
// `bdlmt::EventSchedulerTestTimeSource` will be initialized with an arbitrary
// value on construction, and will advance only when explicitly instructed to
// do so by a call to `bdlt::EventSchedulerTestTimeSource::advanceTime`.  The
// current value of the internal clock can be accessed by calling
// `bdlt::EventSchedulerTestTimeSource::now`, or `bdlmt::EventScheduler::now`
// on the instance supplied to the `bdlmt::EventSchedulerTestTimeSource`.
//
// Note that the initial value of `bdlt::EventSchedulerTestTimeSource::now` is
// intentionally not synchronized with `bsls::SystemTime::nowRealtimeClock`.
// All test events scheduled for a `bdlmt::EventScheduler` that is instrumented
// with a `bdlt::EventSchedulerTestTimeSource` should be scheduled in terms of
// an offset from whatever arbitrary time is reported by
// `bdlt::EventSchedulerTestTimeSource`.  See
// [](#Example 3: Using the Test Time Source) below for an illustration of how
// this is done.
//
// Also note that `bdlt::EventSchedulerTestTimeSource::advanceTime`
// synchronizes with the dispatching thread.  Specifically, `advanceTime` does
// not return until the dispatcher processes all events triggered by the change
// in current time.
//
///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.EventSched" will
// be used.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Simple Clock
///- - - - - - - - - - - -
// In this example we wish to log some statistics periodically.  We define a
// method to store the value of a variable into an array, and set up a
// scheduler to call that as a recurring event.
// ```
//  bsls::AtomicInt  g_data;  // Some global data we want to track
//  typedef pair<bsls::TimeInterval, int> Value;
//
//  void saveData(vector<Value> *array)
//  {
//     array->push_back(Value(bsls::SystemTime::nowRealtimeClock(), g_data));
//  }
// ```
// We allow the scheduler to run for a short time while changing this value and
// observe that the callback is executed:
// ```
//  bdlmt::EventScheduler scheduler;
//  vector<Value> values;
//
//  scheduler.scheduleRecurringEvent(bsls::TimeInterval(1.5),
//                                 bdlf::BindUtil::bind(&saveData, &values)));
//  scheduler.start();
//  bsls::TimeInterval start = bsls::SystemTime::nowRealtimeClock();
//  while ((bsls::SystemTime::nowRealtimeClock() -
//                                        start).totalSecondsAsDouble() < 7) {
//    ++g_data;
//  }
//  scheduler.stop();
//  assert(values.size() >= 4);
//  for (int i = 0; i < (int) values.size(); ++i) {
//      cout << "At " << bdlt::EpochUtil::convertFromTimeInterval(
//                                                         values[i].first) <<
//              " g_data was " << values[i].second << endl;
//  }
// ```
// This will display, e.g.:
// ```
// At 26OCT2020_23:51:51.097283 g_data was 8008406
// At 26OCT2020_23:51:52.597287 g_data was 16723918
// At 26OCT2020_23:51:54.097269 g_data was 24563722
// At 26OCT2020_23:51:55.597262 g_data was 30291748
// ```
//
///Example 2: Server Timeouts
/// - - - - - - - - - - - - -
// The following example shows how to use a `bdlmt::EventScheduler` 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).
//
// Because the timeout is relative to the arrival of data, it is best to use a
// "monotonic" clock that advances at a steady rate, rather than a "wall" clock
// that may fluctuate to reflect real time adjustments.
// ```
//   /// This class encapsulates the data and state associated with a
//   /// connection and provides a method `processData` to process the
//   /// incoming data for the connection.
//   class my_Session{
//     public:
//
//       /// Process the specified `data` of the specified `length`.  (TBD)
//       int processData(void *data, int length);
//   };
//
//   /// 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).
//   class my_Server {
//
//    struct Connection {
//        bdlmt::EventSchedulerEventHandle d_timerId; // handle for timeout
//                                                    // event
//
//        my_Session *d_session_p;                    // session for this
//                                                    // connection
//    };
//
//    bsl::vector<Connection*> d_connections; // maintained connections
//    bdlmt::EventScheduler    d_scheduler;   // timeout event scheduler
//    bsls::TimeInterval       d_ioTimeout;   // time out
//
//    /// 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 newConnection(Connection *connection);
//
//    /// Close the specified `connection` and remove it from this server.
//    void closeConnection(Connection *connection);
//
//    /// 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.
//    void dataAvailable(Connection *connection, void *data, int length);
//
//  public:
//
//    /// Create 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(const bsls::TimeInterval&  ioTimeout,
//              bslma::Allocator         *allocator = 0);
//
//    ///  Perform the required clean-up and destroy this object.
//    ~my_Server();
// };
//
// my_Server::my_Server(const bsls::TimeInterval&  ioTimeout,
//                     bslma::Allocator          *alloc)
// : d_connections(alloc)
// , d_scheduler(bsls::SystemClockType::e_MONOTONIC, alloc)
// , d_ioTimeout(ioTimeout)
// {
//     // TBD: logic to start monitoring the arriving connections or data
//
//     d_scheduler.start();
// }
//
// my_Server::~my_Server()
// {
//    // TBD: logic to clean up
//
//    d_scheduler.stop();
// }
//
// void my_Server::newConnection(my_Server::Connection *connection)
// {
//    // TBD: logic to add 'connection' to 'd_connections'
//
//    // setup the timeout for data arrival
//    d_scheduler.scheduleEvent(
//       &connection->d_timerId,
//       d_scheduler.now() + d_ioTimeout,
//       bdlf::BindUtil::bind(&my_Server::closeConnection, this, connection));
// }
//
// void my_Server::closeConnection(my_Server::Connection *connection)
// {
//    // TBD: 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
//    d_scheduler.scheduleEvent(
//       &connection->d_timerId,
//       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::EventSchedulerTestTimeSource` is
// provided to allow a test to manipulate the system-time observed by a
// `bdlmt::EventScheduler` in order to control when events are triggered.
// After a scheduler is constructed, a `bdlmt::EventSchedulerTestTimeSource`
// 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::EventSchedulerTestTimeSource` **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() {
//     // Create the scheduler
//     bdlmt::EventScheduler scheduler;
//
//     // Create the time-source.
//     // Install the time-source in the scheduler.
//     bdlmt::EventSchedulerTestTimeSource 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.scheduleRecurringEvent(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.

#include <bdlscm_version.h>

#include <bdlcc_skiplist.h>

#include <bdlm_metricsregistry.h>

#include <bdlf_bind.h>
#include <bdlf_placeholder.h>

#include <bslmt_condition.h>
#include <bslmt_lockguard.h>
#include <bslmt_mutex.h>
#include <bslmt_threadattributes.h>
#include <bslmt_threadutil.h>

#include <bslma_allocator.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_allocatorargt.h>
#include <bslmf_nestedtraitdeclaration.h>

#include <bsls_assert.h>
#include <bsls_atomic.h>
#include <bsls_libraryfeatures.h>
#include <bsls_review.h>
#include <bsls_systemclocktype.h>
#include <bsls_timeinterval.h>
#include <bsls_types.h>

#include <bsl_functional.h>
#include <bsl_memory.h>
#include <bsl_optional.h>
#include <bsl_string.h>
#include <bsl_utility.h>

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
#include <bslmt_chronoutil.h>

#include <bsl_chrono.h>
#endif

namespace BloombergLP {
namespace bdlmt {

class EventSchedulerEventHandle;
class EventSchedulerRecurringEventHandle;
class EventSchedulerTestTimeSource_Data;

                            // ====================
                            // class EventScheduler
                            // ====================

/// This class provides a thread-safe event scheduler that executes
/// callbacks in a separate "dispatcher thread."  `start` must be invoked to
/// start dispatching the callbacks.  `stop` pauses the dispatching of the
/// callbacks without removing the pending events.
class EventScheduler {

  private:
    // PRIVATE TYPES

                               // ================
                               // struct EventData
                               // ================

    /// This `struct` encapsulates all of the information for a
    /// non-recurring event.
    struct EventData {

      public:
        // DATA

        /// user-supplied callback invoked when associated event triggers
        bsl::function<void()>               d_callback;

        /// a function that returns the difference, in microseconds, between
        /// when the scheduled event is meant to occur and the current time
        bsl::function<bsls::Types::Int64()> d_nowOffset;

      private:
        // NOT IMPLEMENTED
        EventData& operator=(const EventData&);

      public:
        // TRAITS
        BSLMF_NESTED_TRAIT_DECLARATION(EventData, bslma::UsesBslmaAllocator);

        // CREATORS

        /// Create an `EventData` from the specified `callback` and
        /// `nowOffset`.  Optionally specify a `basicAllocator` used to
        /// supply memory.  If `basicAllocator` is 0, the currently
        /// installed default allocator is used.
        EventData(
                const bsl::function<void()>&                callback,
                const bsl::function<bsls::Types::Int64()>&  nowOffset,
                bslma::Allocator                           *basicAllocator = 0)
        : d_callback(bsl::allocator_arg, basicAllocator, callback)
        , d_nowOffset(bsl::allocator_arg, basicAllocator, nowOffset)
        {
        }

        /// Create an `EventData` object having the value of the specified
        /// `original` object.  Optionally specify a `basicAllocator` used
        /// to supply memory.  If `basicAllocator` is 0, the currently
        /// installed default allocator is used.
        EventData(const EventData&  original,
                  bslma::Allocator *basicAllocator = 0)
        : d_callback(bsl::allocator_arg, basicAllocator, original.d_callback)
        , d_nowOffset(bsl::allocator_arg, basicAllocator, original.d_nowOffset)
        {
        }
    };

                         // =========================
                         // struct RecurringEventData
                         // =========================

    /// This `struct` encapsulates all of the information for a recurring
    /// event.
    struct RecurringEventData {

      public:
        // DATA

        /// the time between calls (in microseconds)
        bsls::TimeInterval                     d_interval;

        /// user-supplied callback invoked when associated event triggers
        bsl::function<void()>                  d_callback;

        /// a function that returns the difference, in microseconds, between
        /// when the scheduled event is meant to occur and the current time
        bsl::function<bsls::Types::Int64(int)> d_nowOffset;

        /// the index of the recurring event (starting with 0); passed to
        /// `d_nowOffset` to determine the time of the next invocation of
        /// `d_callback`
        int                                    d_eventIdx;

      private:
        // NOT IMPLEMENTED
        RecurringEventData& operator=(const RecurringEventData&);

      public:
        // TRAITS
        BSLMF_NESTED_TRAIT_DECLARATION(RecurringEventData,
                                       bslma::UsesBslmaAllocator);

        // CREATORS

        /// Create a `RecurringEventData` from the specified `interval`,
        /// `callback`, and `nowOffset`.  Optionally specify a
        /// `basicAllocator` used to supply memory.  If `basicAllocator` is
        /// 0, the currently installed default allocator is used.
        RecurringEventData(
             const bsls::TimeInterval&                      interval,
             const bsl::function<void()>&                   callback,
             const bsl::function<bsls::Types::Int64(int)>&  nowOffset,
             bslma::Allocator                              *basicAllocator = 0)
        : d_interval(interval)
        , d_callback(bsl::allocator_arg, basicAllocator, callback)
        , d_nowOffset(bsl::allocator_arg, basicAllocator, nowOffset)
        , d_eventIdx(0)
        {
        }

        /// Create a `RecurringEventData` object having the value of the
        /// specified `original` object.  Optionally specify a
        /// `basicAllocator` used to supply memory.  If `basicAllocator` is
        /// 0, the currently installed default allocator is used.
        RecurringEventData(const RecurringEventData&  original,
                           bslma::Allocator          *basicAllocator = 0)
        : d_interval(original.d_interval)
        , d_callback(bsl::allocator_arg, basicAllocator, original.d_callback)
        , d_nowOffset(bsl::allocator_arg, basicAllocator, original.d_nowOffset)
        , d_eventIdx(original.d_eventIdx)
        {
        }
    };

    typedef bdlcc::SkipList<bsls::Types::Int64,
                            RecurringEventData>            RecurringEventQueue;

    typedef bdlcc::SkipList<bsls::Types::Int64, EventData> EventQueue;

    typedef bsl::function<bsls::TimeInterval()>            CurrentTimeFunctor;

    // FRIENDS
    friend class EventSchedulerEventHandle;
    friend class EventSchedulerRecurringEventHandle;
    friend class EventSchedulerTestTimeSource;

  public:
    // PUBLIC TYPES
    struct Event {};

    /// Pointers to the opaque structures `Event` and `RecurringEvent` are
    /// populated by the "Raw" API of `EventScheduler`.
    struct RecurringEvent {};

    typedef EventSchedulerEventHandle          EventHandle;

    typedef EventSchedulerRecurringEventHandle RecurringEventHandle;

    /// Defines a type alias for the dispatcher functor type.
    typedef bsl::function<void(const bsl::function<void()>&)>
                                               Dispatcher;

  private:
    // NOT IMPLEMENTED
    EventScheduler(const EventScheduler&);
    EventScheduler& operator=(const EventScheduler&);

  private:
    // PRIVATE CLASS DATA
    static const char     s_defaultThreadName[16];  // Thread name to use when
                                                    // none is specified.

    // PRIVATE DATA
    CurrentTimeFunctor    d_currentTimeFunctor; // when called, returns the
                                                // current time the scheduler
                                                // should use for the event
                                                // timeline

    bsls::AtomicInt64     d_cachedNow;          // cached total microseconds

    EventQueue            d_eventQueue;         // events

    RecurringEventQueue   d_recurringQueue;     // recurring events

    Dispatcher            d_dispatcherFunctor;  // dispatch events

    bslmt::ThreadUtil::Handle
                          d_dispatcherThread;   // dispatcher thread handle

    bsls::AtomicUint64    d_dispatcherThreadId; // dispatcher thread id used to
                                                // implement function
                                                // `isInDispatcherThread`

    bslmt::Mutex          d_dispatcherMutex;    // serialize starting/stopping
                                                // dispatcher thread

    mutable bslmt::Mutex  d_mutex;              // synchronizes access to
                                                // condition variables

    bslmt::Condition      d_queueCondition;     // condition variable used to
                                                // signal when the queues need
                                                // to be checked again (when
                                                // they become non-empty or get
                                                // a new front member)

    bslmt::Condition      d_iterationCondition; // condition variable used to
                                                // signal when the dispatcher
                                                // is ready to enter next
                                                // iteration (synchronizes
                                                // `wait` methods)

    bool                  d_running;            // controls the looping of the
                                                // dispatcher thread

    bool                  d_dispatcherAwaited;  // A thread is waiting for the
                                                // dispatcher to complete an
                                                // iteration

    RecurringEventQueue::Pair
                         *d_currentRecurringEvent;
                                                // Raw reference to the
                                                // scheduled event being
                                                // executed
    EventQueue::Pair     *d_currentEvent;
                                                // Raw reference to the
                                                // scheduled recurring event
                                                // being executed

    unsigned int          d_waitCount;          // count of the number of waits
                                                // performed in the main
                                                // dispatch loop, used in
                                                // `advanceTime` to determine
                                                // when to return

    bsls::SystemClockType::Enum
                          d_clockType;          // clock type used

    const bsl::string     d_eventSchedulerName; // name of this scheduler

    bdlm::MetricsRegistryRegistrationHandle
                          d_startLagHandle;     // start lag metric handle

    // PRIVATE CLASS METHODS

    /// Cast the specified `handle` to an `EventQueue::Pair`.
    static const EventQueue::Pair *castToQueuePair(const Event *handle);

    /// Cast the specified `handle` to a `RecurringEventQueue::Pair`.
    static const RecurringEventQueue::Pair *castToQueuePair(
                                                 const RecurringEvent *handle);

    /// Return 0.
    static bsls::Types::Int64 returnZero();

    /// Return 0.  The `int` argument is ignored.
    static bsls::Types::Int64 returnZeroInt(int);

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
    /// Return the number of microseconds between the current time and the
    /// specified `absTime`.  `absTime` is an *absolute* time represented as
    /// an interval from some epoch, which is determined by the clock
    /// associated with the time point.  Note that this method is used when
    /// the `t_CLOCK` type used to schedule the event differs from that of
    /// the event scheduler itself.  Also note that a negative value is
    /// returned if `absTime` is in the past.
    template <class t_CLOCK, class t_DURATION>
    static bsls::Types::Int64 timeUntilTrigger(
                  const bsl::chrono::time_point<t_CLOCK, t_DURATION>& absTime);

    /// Return the number of microseconds between the current time and the
    /// scheduled time of the specified `eventIndex`th recurring event,
    /// which starts at the specified `absTime` and repeats at the specified
    /// `interval`.  `absTime` is an *absolute* time represented as an
    /// interval from some epoch, which is determined by the clock
    /// associated with the time point.  The behavior is undefined unless
    /// `0 <= eventIndex`.  Note that this method is used when the `t_CLOCK`
    /// type used to schedule the event differs from that of the event
    /// scheduler itself.
    template <class t_CLOCK,
              class t_DURATION,
              class t_REP_TYPE,
              class t_PERIOD_TYPE>
    static bsls::Types::Int64 timeUntilTriggerRecurring(
            const bsl::chrono::time_point<t_CLOCK,
                                          t_DURATION>&             absTime,
            const bsl::chrono::duration<t_REP_TYPE,
                                        t_PERIOD_TYPE>&            interval,
            int                                                    eventIndex);
#endif

    // PRIVATE MANIPULATORS

    /// Pick either `d_currentEvent` or `d_currentRecurringEvent` as the
    /// next event to be executed, given that the current time is the
    /// specified (absolute) `now` interval, and return the (absolute)
    /// interval of the chosen event.  If both `d_currentEvent` and
    /// `d_currentRecurringEvent` are valid, release whichever one was not
    /// chosen.  If both `d_currentEvent` and `d_currentRecurringEvent` are
    /// scheduled before `now`, choose `d_currentEvent`.  The behavior is
    /// undefined if neither `d_currentEvent` nor `d_currentRecurringEvent`
    /// is valid.  Note that the argument and return value of this method
    /// are expressed in terms of the number of microseconds elapsed since
    /// some epoch, which is determined by the clock indicated at
    /// construction (see {Supported Clock Types} in the component
    /// documentation).  Also note that this method may update the value of
    /// `now` with the current system time if necessary.
    bsls::Types::Int64 chooseNextEvent(bsls::AtomicInt64 *now);

    /// While d_running is true, execute events in the event and recurring
    /// event queues at their scheduled times.  Note that this method
    /// implements the dispatching thread.
    void dispatchEvents();


    /// Initialize this event scheduler using the stored attributes and the
    /// specified `metricsRegistry` and `eventSchedulerName`.  If
    /// `metricsRegistry` is 0, `bdlm::MetricsRegistry::singleton()`  is
    /// used.
    void initialize(bdlm::MetricsRegistry   *metricsRegistry,
                    const bsl::string_view&  eventSchedulerName);

    /// Release `d_currentRecurringEvent` and `d_currentEvent`, if they
    /// refer to valid events.
    void releaseCurrentEvents();

    /// Schedule the callback of the specified `eventData` to be dispatched
    /// at the specified `epochTime` truncated to microseconds.  Load into
    /// the specified `event` pointer a handle that can be used to cancel
    /// the event (by invoking `cancelEvent`).  The `epochTime` is an
    /// absolute time represented as an interval from some epoch, which is
    /// determined by the clock indicated at construction (see {Supported
    /// Clock Types} in the component documentation).  Note that if
    /// `epochTime` is in the past, the event is dispatched immediately.
    void scheduleEvent(EventHandle               *event,
                       const bsls::TimeInterval&  epochTime,
                       const EventData&           eventData);

    /// Schedule the callback of the specified `eventData` to be dispatched
    /// at the specified `epochTime` truncated to microseconds.  The
    /// `epochTime` is an absolute time represented as an interval from some
    /// epoch, which is determined by the clock indicated at construction
    /// (see {Supported Clock Types} in the component documentation).  Note
    /// that if `epochTime` is in the past, the event is dispatched
    /// immediately.
    void scheduleEvent(const bsls::TimeInterval&   epochTime,
                       const EventData&            eventData);

    /// Schedule a recurring event that invokes the callback of the
    /// specified `eventData` with the first event dispatched at the
    /// specified `startEpochTime` truncated to microseconds.  Load into the
    /// specified `event` pointer a handle that can be used to cancel the
    /// event (by invoking `cancelEvent`).  The `startEpochTime` is an
    /// absolute time represented as an interval from some epoch, which is
    /// determined by the clock indicated at construction (see {Supported
    /// Clock Types} in the component documentation).  The behavior is
    /// undefined unless the interval of `eventData` is at least one
    /// microsecond.  Note that if `startEpochTime` is in the past, the
    /// first event is dispatched immediately, and additional
    /// `(now() - startEpochTime) / eventData.d_interval` events will be
    /// submitted serially.
    void scheduleRecurringEvent(RecurringEventHandle      *event,
                                const RecurringEventData&  eventData,
                                const bsls::TimeInterval&  startEpochTime);

    /// Schedule a recurring event that invokes the callback of the
    /// specified `eventData` with the first event dispatched at the
    /// specified `startEpochTime` truncated to microseconds.  Load into the
    /// specified `event` pointer a handle that can be used to cancel the
    /// event (by invoking `cancelEvent`).  The `startEpochTime` is an
    /// *absolute* time represented as an interval from some epoch, which is
    /// determined by the clock indicated at construction (see {Supported
    /// Clock Types} in the component documentation).  The `event` pointer
    /// must be released by invoking `releaseEventRaw` when it is no longer
    /// needed.  The behavior is undefined unless the interval of
    /// `eventData` is at least one microsecond.  Note that if
    /// `startEpochTime` is in the past, the first event is dispatched
    /// immediately, and additional
    /// `(now() - startEpochTime) / eventData.d_interval` events will be
    /// submitted serially.
    void scheduleRecurringEventRaw(RecurringEvent            **event,
                                   const RecurringEventData&   eventData,
                                   const bsls::TimeInterval&   startEpochTime);

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(EventScheduler, bslma::UsesBslmaAllocator);

    // CREATORS

    /// Create an event scheduler using the default dispatcher functor (see
    /// {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation) and using the system realtime clock
    /// to indicate the epoch used for all time intervals.  Optionally
    /// specify a `basicAllocator` used to supply memory.  If
    /// `basicAllocator` is 0, the currently installed default allocator is
    /// used.
    EventScheduler();
    explicit EventScheduler(bslma::Allocator *basicAllocator);

    /// Create an event scheduler using the default dispatcher functor (see
    /// {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation), using the system realtime clock to
    /// indicate the epoch used for all time intervals, the specified
    /// `eventSchedulerName` to be used to identify this event scheduler, and
    /// the specified `metricsRegistry` to be used for reporting metrics.
    /// If `metricsRegistry` is 0, `bdlm::MetricsRegistry::singleton()` is
    /// used.  Optionally specify a `basicAllocator` used to supply memory.
    /// If `basicAllocator` is 0, the currently installed default allocator
    /// is used.
    explicit EventScheduler(const bsl::string_view&  eventSchedulerName,
                            bdlm::MetricsRegistry   *metricsRegistry,
                            bslma::Allocator        *basicAllocator = 0);

    /// Create an event scheduler using the default dispatcher functor (see
    /// {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation) and using the specified `clockType`
    /// to indicate the epoch used for all time intervals (see {Supported
    /// Clock Types} in the component documentation).  Optionally specify a
    /// `basicAllocator` used to supply memory.  If `basicAllocator` is 0,
    /// the currently installed default allocator is used.
    explicit EventScheduler(bsls::SystemClockType::Enum  clockType,
                            bslma::Allocator            *basicAllocator = 0);

    /// Create an event scheduler using the default dispatcher functor (see
    /// {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation), using the specified `clockType` to
    /// indicate the epoch used for all time intervals (see {Supported Clock
    /// Types} in the component documentation), the specified
    /// `eventSchedulerName` to be used to identify this event scheduler, and
    /// the specified `metricsRegistry` to be used for reporting metrics.
    /// If `metricsRegistry` is 0, `bdlm::MetricsRegistry::singleton()` is
    /// used.  Optionally specify a `basicAllocator` used to supply memory.
    /// If `basicAllocator` is 0, the currently installed default allocator
    /// is used.
    explicit EventScheduler(bsls::SystemClockType::Enum  clockType,
                            const bsl::string_view&      eventSchedulerName,
                            bdlm::MetricsRegistry       *metricsRegistry,
                            bslma::Allocator            *basicAllocator = 0);

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
    /// Create an event scheduler using the default dispatcher functor (see
    /// {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation) and using the system realtime clock
    /// to indicate the epoch used for all time intervals.  Optionally
    /// specify a `basicAllocator` used to supply memory.  If
    /// `basicAllocator` is 0, the currently installed default allocator is
    /// used.
    explicit EventScheduler(
                         const bsl::chrono::system_clock&,
                         bslma::Allocator                 *basicAllocator = 0);

    /// Create an event scheduler using the default dispatcher functor (see
    /// {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation), using the system realtime clock to
    /// indicate the epoch used for all time intervals, the specified
    /// `eventSchedulerName` to be used to identify this event scheduler, and
    /// the specified `metricsRegistry` to be used for reporting metrics.
    /// If `metricsRegistry` is 0, `bdlm::MetricsRegistry::singleton()` is
    /// used.  Optionally specify a `basicAllocator` used to supply memory.
    /// If `basicAllocator` is 0, the currently installed default allocator
    /// is used.
    explicit EventScheduler(
                         const bsl::chrono::system_clock&,
                         const bsl::string_view&           eventSchedulerName,
                         bdlm::MetricsRegistry            *metricsRegistry,
                         bslma::Allocator                 *basicAllocator = 0);

    /// Create an event scheduler using the default dispatcher functor (see
    /// {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation) and using the system monotonic clock
    /// to indicate the epoch used for all time intervals.  Optionally
    /// specify a `basicAllocator` used to supply memory.  If
    /// `basicAllocator` is 0, the currently installed default allocator is
    /// used.
    explicit EventScheduler(
                         const bsl::chrono::steady_clock&,
                         bslma::Allocator                 *basicAllocator = 0);

    /// Create an event scheduler using the default dispatcher functor (see
    /// {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation), using the system monotonic clock to
    /// indicate the epoch used for all time intervals, the specified
    /// `eventSchedulerName` to be used to identify this event scheduler, and
    /// the specified `metricsRegistry` to be used for reporting metrics.
    /// If `metricsRegistry` is 0, `bdlm::MetricsRegistry::singleton()` is
    /// used.  Optionally specify a `basicAllocator` used to supply memory.
    /// If `basicAllocator` is 0, the currently installed default allocator
    /// is used.
    explicit EventScheduler(
                         const bsl::chrono::steady_clock&,
                         const bsl::string_view&           eventSchedulerName,
                         bdlm::MetricsRegistry            *metricsRegistry,
                         bslma::Allocator                 *basicAllocator = 0);
#endif // defined(BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY)

    /// Create an event scheduler using the specified `dispatcherFunctor`
    /// (see {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation) and using the system realtime clock
    /// to indicate the epoch used for all time intervals.  Optionally
    /// specify a `basicAllocator` used to supply memory.  If
    /// `basicAllocator` is 0, the currently installed default allocator is
    /// used.
    explicit EventScheduler(const Dispatcher&  dispatcherFunctor,
                            bslma::Allocator  *basicAllocator = 0);

    /// Create an event scheduler using the specified `dispatcherFunctor`
    /// (see {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation), using the system realtime clock to
    /// indicate the epoch used for all time intervals, the specified
    /// `eventSchedulerName` to be used to identify this event scheduler, and
    /// the specified `metricsRegistry` to be used for reporting metrics.
    /// If `metricsRegistry` is 0, `bdlm::MetricsRegistry::singleton()` is
    /// used.  Optionally specify a `basicAllocator` used to supply memory.
    /// If `basicAllocator` is 0, the currently installed default allocator
    /// is used.
    explicit EventScheduler(const Dispatcher&        dispatcherFunctor,
                            const bsl::string_view&  eventSchedulerName,
                            bdlm::MetricsRegistry   *metricsRegistry,
                            bslma::Allocator        *basicAllocator = 0);

    /// Create an event scheduler using the specified `dispatcherFunctor`
    /// (see {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation) and using the specified `clockType`
    /// to indicate the epoch used for all time intervals (see {Supported
    /// Clock Types} in the component documentation).  Optionally specify a
    /// `basicAllocator` used to supply memory.  If `basicAllocator` is 0,
    /// the currently installed default allocator is used.
    EventScheduler(const Dispatcher&            dispatcherFunctor,
                   bsls::SystemClockType::Enum  clockType,
                   bslma::Allocator            *basicAllocator = 0);

    /// Create an event scheduler using the specified `dispatcherFunctor`
    /// (see {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation), using the specified `clockType` to
    /// indicate the epoch used for all time intervals (see {Supported Clock
    /// Types} in the component documentation), the specified
    /// `eventSchedulerName` to be used to identify this event scheduler, and
    /// the specified `metricsRegistry` to be used for reporting metrics.
    /// If `metricsRegistry` is 0, `bdlm::MetricsRegistry::singleton()` is
    /// used.  Optionally specify a `basicAllocator` used to supply memory.
    /// If `basicAllocator` is 0, the currently installed default allocator
    /// is used.
    EventScheduler(const Dispatcher&            dispatcherFunctor,
                   bsls::SystemClockType::Enum  clockType,
                   const bsl::string_view&      eventSchedulerName,
                   bdlm::MetricsRegistry       *metricsRegistry,
                   bslma::Allocator            *basicAllocator = 0);

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
    /// Create an event scheduler using the specified `dispatcherFunctor`
    /// (see {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation) and using the system realtime clock
    /// to indicate the epoch used for all time intervals.  Optionally
    /// specify a `basicAllocator` used to supply memory.  If
    /// `basicAllocator` is 0, the currently installed default allocator is
    /// used.
    EventScheduler(const Dispatcher&                 dispatcherFunctor,
                   const bsl::chrono::system_clock&,
                   bslma::Allocator                 *basicAllocator = 0);

    /// Create an event scheduler using the specified `dispatcherFunctor`
    /// (see {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation), using the system realtime clock to
    /// indicate the epoch used for all time intervals, the specified
    /// `eventSchedulerName` to be used to identify this event scheduler, and
    /// the specified `metricsRegistry` to be used for reporting metrics.
    /// If `metricsRegistry` is 0, `bdlm::MetricsRegistry::singleton()` is
    /// used.  Optionally specify a `basicAllocator` used to supply memory.
    /// If `basicAllocator` is 0, the currently installed default allocator
    /// is used.
    EventScheduler(const Dispatcher&                 dispatcherFunctor,
                   const bsl::chrono::system_clock&,
                   const bsl::string_view&           eventSchedulerName,
                   bdlm::MetricsRegistry            *metricsRegistry,
                   bslma::Allocator                 *basicAllocator = 0);

    /// Create an event scheduler using the specified `dispatcherFunctor`
    /// (see {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation) and using the system monotonic clock
    /// to indicate the epoch used for all time intervals.  Optionally
    /// specify a `basicAllocator` used to supply memory.  If
    /// `basicAllocator` is 0, the currently installed default allocator is
    /// used.
    EventScheduler(const Dispatcher&                 dispatcherFunctor,
                   const bsl::chrono::steady_clock&,
                   bslma::Allocator                 *basicAllocator = 0);

    /// Create an event scheduler using the specified `dispatcherFunctor`
    /// (see {The Dispatcher Thread and the Dispatcher Functor} in the
    /// component-level documentation), using the system monotonic clock to
    /// indicate the epoch used for all time intervals, the specified
    /// `eventSchedulerName` to be used to identify this event scheduler, and
    /// the specified `metricsRegistry` to be used for reporting metrics.
    /// If `metricsRegistry` is 0, `bdlm::MetricsRegistry::singleton()` is
    /// used.  Optionally specify a `basicAllocator` used to supply memory.
    /// If `basicAllocator` is 0, the currently installed default allocator
    /// is used.
    EventScheduler(const Dispatcher&                 dispatcherFunctor,
                   const bsl::chrono::steady_clock&,
                   const bsl::string_view&           eventSchedulerName,
                   bdlm::MetricsRegistry            *metricsRegistry,
                   bslma::Allocator                 *basicAllocator = 0);
#endif // defined(BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY)

    /// Discard all unprocessed events and destroy this object.  The
    /// behavior is undefined unless the scheduler is stopped.
    ~EventScheduler();

    // MANIPULATORS

    /// Cancel all recurring and one-time events scheduled in this
    /// EventScheduler.
    void cancelAllEvents();

    /// Cancel all recurring and one-time events scheduled in this
    /// EventScheduler.  Block until all events have either been cancelled
    /// or dispatched before this call returns.  The behavior is undefined
    /// if this method is invoked from the dispatcher thread.
    void cancelAllEventsAndWait();

    /// Cancel the event having the specified `handle`.  Return 0 on
    /// successful cancellation, and a non-zero value if the `handle` is
    /// invalid *or* if the event has already been dispatched or canceled.
    /// Note that due to the implicit conversion from Handle types, these
    /// methods also match the following:
    /// ```
    /// int cancelEvent(const EventHandle&          handle);
    /// int cancelEvent(const RecurringEventHandle& handle);
    /// ```
    /// Compared to the version taking a pointer to Handle, the managed
    /// reference to the event is not released until the Handle goes out of
    /// scope.
    int cancelEvent(const Event          *handle);
    int cancelEvent(const RecurringEvent *handle);

    /// Cancel the event having the specified `handle` and release the
    /// handle.  Return 0 on successful cancellation, and a non-zero value
    /// if the `handle` is invalid *or* if the event has already been
    /// dispatched or canceled.  Note that `handle` is released whether this
    /// call is successful or not.
    int cancelEvent(EventHandle          *handle);
    int cancelEvent(RecurringEventHandle *handle);

    /// Cancel the event having the specified `handle`.  Block until the
    /// event having `handle` (if it is valid) is either successfully
    /// canceled or dispatched before the call returns.  Return 0 on
    /// successful cancellation, and a non-zero value if `handle` is invalid
    /// *or* if the event has already been dispatched or canceled.  The
    /// behavior is undefined if this method is invoked from the dispatcher
    /// thread.  Note that if the event is being executed when this method
    /// is invoked, this method will block until it is completed and then
    /// return a nonzero value.
    int cancelEventAndWait(const Event          *handle);
    int cancelEventAndWait(const RecurringEvent *handle);

    /// Cancel the event having the specified `handle` and release
    /// `*handle`.  Block until the event having `handle` (if it is valid)
    /// is either successfully canceled or dispatched before the call
    /// returns.  Return 0 on successful cancellation, and a non-zero value
    /// if `handle` is invalid *or* if the event has already been dispatched
    /// or canceled.  The behavior is undefined if this method is invoked
    /// from the dispatcher thread.  Note that if the event is being
    /// executed when this method is invoked, this method will block until
    /// it is completed and then return a nonzero value.  Also note that it
    /// is guaranteed that `*handle` will be released whether this call is
    /// successful or not.
    int cancelEventAndWait(EventHandle          *handle);
    int cancelEventAndWait(RecurringEventHandle *handle);

    /// Release the specified `handle`.  Every handle reference added by
    /// `scheduleEventRaw`, `addEventRefRaw`, `scheduleRecurringEventRaw`,
    /// or `addRecurringEventRefRaw` must be released using this method to
    /// avoid leaking resources.  The behavior is undefined if the value of
    /// `handle` is used for any purpose after being released.
    void releaseEventRaw(Event          *handle);
    void releaseEventRaw(RecurringEvent *handle);

    /// Reschedule the event referred to by the specified `handle` at the
    /// specified `newEpochTime` truncated to microseconds.  Return 0 on
    /// successful reschedule, and a non-zero value if the `handle` is
    /// invalid *or* if the event has already been dispatched.  The
    /// `newEpochTime` is an absolute time represented as an interval from
    /// some epoch, which is determined by the clock indicated at
    /// construction (see {Supported Clock Types} in the component
    /// documentation).
    int rescheduleEvent(const Event               *handle,
                        const bsls::TimeInterval&  newEpochTime);

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
    /// Reschedule the event referred to by the specified `handle` at the
    /// specified `newEpochTime` truncated to microseconds.  Return 0 on
    /// successful reschedule, and a non-zero value if the `handle` is
    /// invalid *or* if the event has already been dispatched.  The
    /// `newEpochTime` is an absolute time represented as an interval from
    /// some epoch, determined by the clock associated with the time point.
    template <class t_CLOCK, class t_DURATION>
    int rescheduleEvent(
            const Event                                         *handle,
            const bsl::chrono::time_point<t_CLOCK, t_DURATION>&  newEpochTime);
#endif

    /// Reschedule the event referred to by the specified `handle` at the
    /// specified `newEpochTime` truncated to microseconds.  Block until the
    /// event having `handle` (if it is valid) is either successfully
    /// rescheduled or dispatched before the call returns.  Return 0 on
    /// successful reschedule, and a non-zero value if `handle` is invalid
    /// *or* if the event has already been dispatched.  The `newEpochTime`
    /// is an absolute time represented as an interval from some epoch,
    /// which is determined by the clock indicated at construction (see
    /// {Supported Clock Types} in the component documentation).  The
    /// behavior is undefined if this method is invoked from the dispatcher
    /// thread.
    int rescheduleEventAndWait(const Event               *handle,
                               const bsls::TimeInterval&  newEpochTime);

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
    /// Reschedule the event referred to by the specified `handle` at the
    /// specified `newEpochTime` truncated to microseconds.  Block until the
    /// event having `handle` (if it is valid) is either successfully
    /// rescheduled or dispatched before the call returns.  Return 0 on
    /// successful reschedule, and a non-zero value if `handle` is invalid
    /// *or* if the event has already been dispatched.  The `newEpochTime`
    /// is an absolute time represented as an interval from some epoch,
    /// which is determined by the clock associated with the time point.
    /// The behavior is undefined if this method is invoked from the
    /// dispatcher thread.
    template <class t_CLOCK, class t_DURATION>
    int rescheduleEventAndWait(
            const Event                                         *handle,
            const bsl::chrono::time_point<t_CLOCK, t_DURATION>&  newEpochTime);
#endif

    /// Schedule the specified `callback` to be dispatched at the specified
    /// `epochTime` truncated to microseconds.  Load into the optionally
    /// specified `event` a handle that can be used to cancel the event (by
    /// invoking `cancelEvent`).  The `epochTime` is an absolute time
    /// represented as an interval from some epoch, which is determined by
    /// the clock indicated at construction (see {Supported Clock Types} in
    /// the component documentation).  This method guarantees that the event
    /// will occur at or after `epochTime`.  `epochTime` may be in the past,
    /// in which case the event will be executed as soon as possible.
    void scheduleEvent(const bsls::TimeInterval&    epochTime,
                       const bsl::function<void()>& callback);
    void scheduleEvent(EventHandle                  *event,
                       const bsls::TimeInterval&     epochTime,
                       const bsl::function<void()>&  callback);

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
    /// Schedule the specified `callback` to be dispatched at the specified
    /// `epochTime` truncated to microseconds.  Load into the optionally
    /// specified `event` a handle that can be used to cancel the event (by
    /// invoking `cancelEvent`).  The `epochTime` is an absolute time
    /// represented as an interval from some epoch, which is determined by
    /// the clock associated with the time point.  This method guarantees
    /// that the event will occur at or after `epochTime`.  `epochTime` may
    /// be in the past, in which case the event will be executed as soon as
    /// possible.
    template <class t_CLOCK, class t_DURATION>
    void scheduleEvent(
                 const bsl::chrono::time_point<t_CLOCK, t_DURATION>& epochTime,
                 const bsl::function<void()>&                        callback);
    template <class t_CLOCK, class t_DURATION>
    void scheduleEvent(
                EventHandle                                         *event,
                const bsl::chrono::time_point<t_CLOCK, t_DURATION>&  epochTime,
                const bsl::function<void()>&                         callback);
#endif

    /// Schedule the specified `callback` to be dispatched at the specified
    /// `epochTime` truncated to microseconds.  Load into the specified
    /// `event` pointer a handle that can be used to cancel the event (by
    /// invoking `cancelEvent`).  The `epochTime` is an *absolute* time
    /// represented as an interval from some epoch, which is determined by
    /// the clock indicated at construction (see {Supported Clock Types} in
    /// the component documentation).  The `event` pointer must be released
    /// invoking `releaseEventRaw` when it is no longer needed.
    void scheduleEventRaw(Event                        **event,
                          const bsls::TimeInterval&      epochTime,
                          const bsl::function<void()>&   callback);

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
    /// Schedule the specified `callback` to be dispatched at the specified
    /// `epochTime` truncated to microseconds.  Load into the specified
    /// `event` pointer a handle that can be used to cancel the event (by
    /// invoking `cancelEvent`).  The `epochTime` is an absolute time
    /// represented as an interval from some epoch, which is determined by
    /// the clock associated with the time point.  The `event` pointer must
    /// be released invoking `releaseEventRaw` when it is no longer needed.
    template <class t_CLOCK, class t_DURATION>
    void scheduleEventRaw(
               Event                                               **event,
               const bsl::chrono::time_point<t_CLOCK, t_DURATION>&   epochTime,
               const bsl::function<void()>&                          callback);
#endif

    /// Schedule a recurring event that invokes the specified `callback` at
    /// every specified `interval` truncated to microseconds, with the first
    /// event dispatched at the optionally specified `startEpochTime`
    /// truncated to microseconds.  If `startEpochTime` is not specified,
    /// the first event is dispatched at one `interval` from now.  Load into
    /// the optionally specified `event` a handle that can be used to cancel
    /// the event (by invoking `cancelEvent`).  The `startEpochTime` is an
    /// absolute time represented as an interval from some epoch, which is
    /// determined by the clock indicated at construction (see {Supported
    /// Clock Types} in the component documentation).  The behavior is
    /// undefined unless `interval` is at least one microsecond.  Note that
    /// if `startEpochTime` is in the past, the first event is dispatched
    /// immediately, and additional `(now() - startEpochTime) / interval`
    /// events will be submitted serially.
    void scheduleRecurringEvent(const bsls::TimeInterval&    interval,
                                const bsl::function<void()>& callback,
                                const bsls::TimeInterval&    startEpochTime
                                                      = bsls::TimeInterval(0));
    void scheduleRecurringEvent(RecurringEventHandle         *event,
                                const bsls::TimeInterval&     interval,
                                const bsl::function<void()>&  callback,
                                const bsls::TimeInterval&     startEpochTime
                                                      = bsls::TimeInterval(0));

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
    /// Schedule a recurring event that invokes the specified `callback` at
    /// every specified `interval` truncated to microseconds, with the first
    /// event dispatched at the optionally specified `startEpochTime`
    /// truncated to microseconds.  If `startEpochTime` is not specified,
    /// the first event is dispatched at one `interval` from now.  Load into
    /// the optionally specified `event` a handle that can be used to cancel
    /// the event (by invoking `cancelEvent`).  The `startEpochTime` is an
    /// absolute time represented as an interval from some epoch, which is
    /// determined by the clock associated with the time point.  The
    /// behavior is undefined unless `interval` is at least one microsecond.
    /// Note that if `startEpochTime` is in the past, the first event is
    /// dispatched immediately, and additional
    /// `(now() - startEpochTime) / interval` events will be submitted
    /// serially.
    template <class t_CLOCK,
              class t_REP_TYPE,
              class t_PERIOD_TYPE,
              class t_DURATION>
    void scheduleRecurringEvent(
           const bsl::chrono::duration<t_REP_TYPE,
                                      t_PERIOD_TYPE>& interval,
           const bsl::function<void()>&               callback,
           const bsl::chrono::time_point<t_CLOCK,
                                         t_DURATION>& startEpochTime =
                                                               t_CLOCK::now());
    template <class t_CLOCK,
              class t_REP_TYPE,
              class t_PERIOD_TYPE,
              class t_DURATION>
    void scheduleRecurringEvent(
           RecurringEventHandle                          *event,
           const bsl::chrono::duration<t_REP_TYPE,
                                       t_PERIOD_TYPE>&    interval,
           const bsl::function<void()>&                   callback,
           const bsl::chrono::time_point<t_CLOCK,
                                         t_DURATION>&     startEpochTime =
                                                               t_CLOCK::now());
#endif

    /// Schedule a recurring event that invokes the specified `callback` at
    /// every specified `interval` truncated to microseconds, with the first
    /// event dispatched at the optionally specified `startEpochTime`
    /// truncated to microseconds.  If `startEpochTime` is not specified,
    /// the first event is dispatched at one `interval` from now.  Load into
    /// the specified `event` pointer a handle that can be used to cancel
    /// the event (by invoking `cancelEvent`).  The `startEpochTime` is an
    /// absolute time represented as an interval from some epoch, which is
    /// determined by the clock indicated at construction (see {Supported
    /// Clock Types} in the component documentation).  The `event` pointer
    /// must be released by invoking `releaseEventRaw` when it is no longer
    /// needed.  The behavior is undefined unless `interval` is at least one
    /// microsecond.  Note that if `startEpochTime` is in the past, the
    /// first event is dispatched immediately, and additional
    /// `(now() - startEpochTime) / interval` events will be submitted
    /// serially.
    void scheduleRecurringEventRaw(
                                  RecurringEvent               **event,
                                  const bsls::TimeInterval&      interval,
                                  const bsl::function<void()>&   callback,
                                  const bsls::TimeInterval&      startEpochTime
                                                      = bsls::TimeInterval(0));

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
    /// Schedule a recurring event that invokes the specified `callback` at
    /// every specified `interval` truncated to microseconds, with the first
    /// event dispatched at the optionally specified `startEpochTime`
    /// truncated to microseconds.  If `startEpochTime` is not specified,
    /// the first event is dispatched at one `interval` from now.  Load into
    /// the specified `event` pointer a handle that can be used to cancel
    /// the event (by invoking `cancelEvent`).  The `startEpochTime` is an
    /// absolute time represented as an interval from some epoch, which is
    /// determined by the clock associated with the time point.  The `event`
    /// pointer must be released by invoking `releaseEventRaw` when it is no
    /// longer needed.  The behavior is undefined unless `interval` is at
    /// least one microsecond.  Note that if `startEpochTime` is in the
    /// past, the first event is dispatched immediately, and additional
    /// `(now() - startEpochTime) / interval` events will be submitted
    /// serially.
    template <class t_CLOCK,
              class t_REP_TYPE,
              class t_PERIOD_TYPE,
              class t_DURATION>
    void scheduleRecurringEventRaw(
           RecurringEvent                              **event,
           const bsl::chrono::duration<t_REP_TYPE,
                                       t_PERIOD_TYPE>&   interval,
           const bsl::function<void()>&                  callback,
           const bsl::chrono::time_point<t_CLOCK,
                                         t_DURATION>&    startEpochTime =
                                                               t_CLOCK::now());
#endif

    /// Begin dispatching events on this scheduler using default attributes
    /// for the dispatcher thread.  Return 0 on success, and a nonzero value
    /// otherwise.  If another thread is currently executing `stop`, wait
    /// until the dispatcher thread stops before starting a new one.  If
    /// this scheduler has already started (and is not currently being
    /// stopped by another thread) then this invocation has no effect and 0
    /// is returned.  The created thread will use the `eventSchedulerName`
    /// supplied at construction if it is not empty, otherwise
    /// "bdl.EventSched". The behavior is undefined if this method is invoked
    /// in the dispatcher thread (i.e., in a job executed by this scheduler).
    /// Note that any event whose time has already passed is pending and
    /// will be dispatched immediately.
    int start();

    /// Begin dispatching events on this scheduler using the specified
    /// `threadAttributes` for the dispatcher thread (except that the
    /// DETACHED attribute is ignored).  Return 0 on success, and a nonzero
    /// value otherwise.  If another thread is currently executing `stop`,
    /// wait until the dispatcher thread stops before starting a new one.
    /// If this scheduler has already started (and is not currently being
    /// stopped by another thread) then this invocation has no effect and 0
    /// is returned.  The created thread will use the name
    /// `threadAttributes.getThreadName()` if it is not empty, otherwise
    /// `eventSchedulerName` supplied at construction if it is not empty,
    /// otherwise "bdl.EventSched".  The behavior is undefined if this method
    /// is invoked in the dispatcher thread (i.e., in a job executed by this
    /// scheduler).  Note that any event whose time has already passed is
    /// pending and will be dispatched immediately.
    int start(const bslmt::ThreadAttributes& threadAttributes);

    /// End the dispatching of events on this scheduler (but do not remove
    /// any pending events), and wait for any (one) currently executing
    /// event to complete.  If the scheduler is already stopped then this
    /// method has no effect.  This scheduler can be restarted by invoking
    /// `start`.  The behavior is undefined if this method is invoked from
    /// the dispatcher thread.
    void stop();

    // ACCESSORS

    /// Increment the reference count for the event referred to by the
    /// specified `handle` and return `handle`.  There must be a
    /// corresponding call to `releaseEventRaw` when the reference is no
    /// longer needed.
    Event *addEventRefRaw(Event *handle) const;

    /// Increment the reference count for the recurring event referred to by
    /// the specified `handle` and return `handle`.  There must be a
    /// corresponding call to `releaseEventRaw` when the reference is no
    /// longer needed.
    RecurringEvent *addRecurringEventRefRaw(RecurringEvent *handle) const;

    /// Return the value of the clock type that this object was created
    /// with.
    bsls::SystemClockType::Enum clockType() const;

    /// Return `true` if the calling thread is the dispatcher thread of this
    /// scheduler, and `false` otherwise.
    bool isInDispatcherThread() const;

    /// Return `true` if a call to `start` has finished successfully more
    /// recently than any call to `stop`, and `false` otherwise.
    bool isStarted() const;

    /// Return the current epoch time, an absolute time represented as an
    /// interval from some epoch, which is determined by the clock indicated
    /// at construction (see {Supported Clock Types} in the component
    /// documentation).
    bsls::TimeInterval now() const;

    /// Return the number of pending one-time events in this scheduler.
    int numEvents() const;

    /// Return the number of recurring events registered with this
    /// scheduler.
    int numRecurringEvents() const;

    /// Return the earliest scheduled starting time of the pending events
    /// and recurring events registered with this scheduler.  If there are
    /// no pending events or recurring events, return `INT64_MAX`
    /// microseconds.
    bsls::TimeInterval nextPendingEventTime() const;

    /// Return the scheduled starting time of the event having the specified
    /// `handle`.  If the `handle` is invalid, *or* the event has already been
    /// canceled, return an optional without a value.
    bsl::optional<bsls::TimeInterval> scheduledEventTime(
                                              const EventHandle& handle) const;
    bsl::optional<bsls::TimeInterval> scheduledEventTime(
                                     const RecurringEventHandle& handle) const;

                                  // Aspects

    /// Return the allocator used by this object to supply memory.
    bslma::Allocator *allocator() const;
};

                      // ===============================
                      // class EventSchedulerEventHandle
                      // ===============================

/// Objects of this type refer to events in the `EventScheduler` API.  They
/// are convertible to `const Event*` references and may be used in any
/// method that expects them.
class EventSchedulerEventHandle
{

    // PRIVATE TYPES
    typedef EventScheduler::EventQueue EventQueue;

    // DATA
    EventQueue::PairHandle  d_handle;

    // FRIENDS
    friend class EventScheduler;

  public:
    // PUBLIC TYPES
    typedef EventScheduler::Event Event;

    // CREATORS

    /// Create a new handle object that does not refer to an event.
    EventSchedulerEventHandle();

    /// Create a new handle object referring to the same event as the
    /// specified `rhs` handle.
    EventSchedulerEventHandle(const EventSchedulerEventHandle& original);

    /// Destroy this object and release the managed reference, if any.
    ~EventSchedulerEventHandle();

    // MANIPULATORS

    /// Release this handle's reference, if any; then make this handle refer
    /// to the same event as the specified `rhs` handle.  Return a
    /// modifiable reference to this handle.
    EventSchedulerEventHandle& operator=(const EventSchedulerEventHandle& rhs);

    /// Release the reference (if any) held by this object.
    void release();

    // ACCESSORS

    /// Return a "raw" pointer to the event managed by this handle, or 0 if
    /// this handle does not manage a reference.
    operator const Event*() const;
};

                  // ========================================
                  // class EventSchedulerRecurringEventHandle
                  // ========================================

/// Objects of this type refer to recurring events in the `EventScheduler`
/// API.  They are convertible to `const RecurringEvent*` references and may
/// be used in any method which expects these.
class EventSchedulerRecurringEventHandle
{

    // PRIVATE TYPES
    typedef EventScheduler::RecurringEventData         RecurringEventData;
    typedef bdlcc::SkipList<bsls::Types::Int64,
                            RecurringEventData>        RecurringEventQueue;

    // DATA
    RecurringEventQueue::PairHandle  d_handle;

    // FRIENDS
    friend class EventScheduler;

  public:
    // PUBLIC TYPES
    typedef EventScheduler::RecurringEvent RecurringEvent;

    // CREATORS

    /// Create a new handle object.
    EventSchedulerRecurringEventHandle();

    /// Create a new handle object referring to the same recurring event as
    /// the specified `rhs` handle.
    EventSchedulerRecurringEventHandle(
                           const EventSchedulerRecurringEventHandle& original);

    /// Destroy this object and release the managed reference, if any.
    ~EventSchedulerRecurringEventHandle();

    // MANIPULATORS

    /// Release the reference managed by this handle, if any; then make this
    /// handle refer to the same recurring event as the specified `rhs`
    /// handle.  Return a modifiable reference to this event handle.
    EventSchedulerRecurringEventHandle& operator=(
                                const EventSchedulerRecurringEventHandle& rhs);

    /// Release the reference managed by this handle, if any.
    void release();

    // ACCESSORS

    /// Return a "raw" pointer to the recurring event managed by this
    /// handle, or 0 if this handle does not manage a reference.
    operator const RecurringEvent*() const;

};

                     // ==================================
                     // class EventSchedulerTestTimeSource
                     // ==================================

/// This class provides a means to change the clock that is used by a given
/// event-scheduler to determine when events should be triggered.
/// Constructing a `EventSchedulerTestTimeSource` alters the behavior of the
/// supplied event-scheduler.  After a test time-source is created, the
/// underlying scheduler will run events according to a discrete timeline,
/// whose successive values are determined by calls to `advanceTime` on the
/// test time-source, and can be retrieved by calling `now` on that test
/// time-source.  Note that the "system-time" held by a test time-source
/// *does* *not* correspond to the current system time.  Test writers must
/// use caution when scheduling absolute-time events so that they are
/// scheduled relative to the test time-source's value for `now`.
class EventSchedulerTestTimeSource {

  private:
    // DATA
    bsl::shared_ptr<EventSchedulerTestTimeSource_Data>
                          d_data_sp;      // shared pointer to the state whose
                                          // lifetime must be as long as
                                          // `*this` and `*d_scheduler_p`

    EventScheduler       *d_scheduler_p;  // pointer to the scheduler that we
                                          // are augmenting

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(EventSchedulerTestTimeSource,
                                   bslma::UsesBslmaAllocator);

    // CREATORS

    /// Create a test time-source object that will control the "system-time"
    /// observed by the specified `scheduler`.  Initialize `now` to be an
    /// arbitrary time value.  The behavior is undefined if any methods have
    /// previously been called on `scheduler`.  Optionally specify a
    /// `basicAllocator` used to supply memory.  If `basicAllocator` is 0,
    /// the currently installed default allocator is used.  The behavior is
    /// undefined unless the supplied allocator also outlives the
    /// `EventScheduler` associated with this time source.
    explicit
    EventSchedulerTestTimeSource(EventScheduler   *scheduler,
                                 bslma::Allocator *basicAllocator = 0);

    // MANIPULATORS

    /// Advance this object's current-time value by the specified `amount`
    /// of time, notify the scheduler that the time has changed, and wait
    /// for the scheduler to process the events triggered by this change in
    /// time.  Return the updated current-time value.  The behavior is
    /// undefined unless `amount` is positive, and `now + amount` is within
    /// the range that can be represented with a `bsls::TimeInterval`.
    bsls::TimeInterval advanceTime(bsls::TimeInterval amount);

    // ACCESSORS

    /// Return this object's current-time value.  Upon construction, this
    /// method will return an arbitrary value.  Subsequent calls to
    /// `advanceTime` will adjust the arbitrary value forward.
    bsls::TimeInterval now() const;
};

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

                      // -------------------------------
                      // class EventSchedulerEventHandle
                      // -------------------------------

// CREATORS
inline
EventSchedulerEventHandle::EventSchedulerEventHandle()
{
}

inline
EventSchedulerEventHandle::EventSchedulerEventHandle(
                                     const EventSchedulerEventHandle& original)
: d_handle(original.d_handle)
{
}

inline
EventSchedulerEventHandle::~EventSchedulerEventHandle()
{
}

// MANIPULATORS
inline
EventSchedulerEventHandle&
EventSchedulerEventHandle::operator=(const EventSchedulerEventHandle& rhs)
{
    d_handle = rhs.d_handle;
    return *this;
}

inline
void EventSchedulerEventHandle::release()
{
    d_handle.release();
}
}  // close package namespace

// ACCESSORS

// The scoping of "Event" below should not be necessary, but xlc (versions 8
// and 9) requires it
inline
bdlmt::EventSchedulerEventHandle::
operator const bdlmt::EventSchedulerEventHandle::Event*() const
{
    return (const Event*)((const EventQueue::Pair*)d_handle);
}

namespace bdlmt {

                  // ----------------------------------------
                  // class EventSchedulerRecurringEventHandle
                  // ----------------------------------------

// CREATORS
inline
EventSchedulerRecurringEventHandle::EventSchedulerRecurringEventHandle()
{
}

inline
EventSchedulerRecurringEventHandle::EventSchedulerRecurringEventHandle(
                            const EventSchedulerRecurringEventHandle& original)
: d_handle(original.d_handle)
{
}

inline
EventSchedulerRecurringEventHandle::~EventSchedulerRecurringEventHandle()
{
}

// MANIPULATORS
inline
void EventSchedulerRecurringEventHandle::release()
{
    d_handle.release();
}

inline
EventSchedulerRecurringEventHandle&
EventSchedulerRecurringEventHandle::operator=(
                                 const EventSchedulerRecurringEventHandle& rhs)
{
    d_handle = rhs.d_handle;
    return *this;
}
}  // close package namespace

// ACCESSORS

// The scoping of "RecurringEvent" below should not be necessary, but xlc
// (versions 8 and 9) requires it
inline
bdlmt::EventSchedulerRecurringEventHandle::operator
       const bdlmt::EventSchedulerRecurringEventHandle::RecurringEvent*() const
{
    return (const RecurringEvent*)((const RecurringEventQueue::Pair*)d_handle);
}

namespace bdlmt {

                            // --------------------
                            // class EventScheduler
                            // --------------------

inline
const EventScheduler::EventQueue::Pair
                          *EventScheduler::castToQueuePair(const Event *handle)
{
    return reinterpret_cast<const EventQueue::Pair *>(
                                            static_cast<const void *>(handle));
}

inline
const EventScheduler::RecurringEventQueue::Pair
                 *EventScheduler::castToQueuePair(const RecurringEvent *handle)
{
    return reinterpret_cast<const RecurringEventQueue::Pair *>(
                                            static_cast<const void *>(handle));
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
// PRIVATE CLASS METHODS
template <class t_CLOCK, class t_DURATION>
// not inline because it gets put into a bsl::function
bsls::Types::Int64 EventScheduler::timeUntilTrigger(
                   const bsl::chrono::time_point<t_CLOCK, t_DURATION>& absTime)
{
    using namespace bsl::chrono;

    auto         now = t_CLOCK::now();
    microseconds offset = duration_cast<microseconds>(absTime - now);
    // If the time to fire is less than one microsecond in the future, then
    // report it as 1us.
    return 0 == offset.count() && absTime > now
           ? 1
           : static_cast<bsls::Types::Int64>(offset.count());
}

template <class t_CLOCK,
          class t_DURATION,
          class t_REP_TYPE,
          class t_PERIOD_TYPE>
// not inline because it gets put into a bsl::function
bsls::Types::Int64 EventScheduler::timeUntilTriggerRecurring(
            const bsl::chrono::time_point<t_CLOCK, t_DURATION>&     absTime,
            const bsl::chrono::duration<t_REP_TYPE, t_PERIOD_TYPE>& interval,
            int                                                     eventIndex)
{
    BSLS_ASSERT(0 <= eventIndex);

    return timeUntilTrigger(absTime + eventIndex * interval);
}
#endif

// MANIPULATORS
inline
int EventScheduler::cancelEvent(const Event *handle)
{
    const EventQueue::Pair *itemPtr = castToQueuePair(handle);

    int ret = d_eventQueue.remove(itemPtr);
    if (0 == ret) {
        // `d_callback` may contain event handles which are in reference cycles
        // which would prevent cleanup of the node and freeing of resources.

        itemPtr->data().d_callback = 0;
    }

    return ret;
}

inline
int EventScheduler::cancelEvent(const RecurringEvent *handle)
{
    const RecurringEventQueue::Pair *itemPtr = castToQueuePair(handle);

    int ret = d_recurringQueue.remove(itemPtr);
    if (0 == ret) {
        // `d_callback` may contain smart pointers which are in reference
        // cycles which would prevent cleanup of the node and freeing of
        // resources.  The dispatch loop copies `d_callback`, guard this
        // assignment clearing it with the mutex to make sure we don't collide
        // with that copy.

        bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex);
        itemPtr->data().d_callback = 0;
    }

    return ret;
}

inline
void EventScheduler::releaseEventRaw(Event *handle)
{
    d_eventQueue.releaseReferenceRaw(castToQueuePair(handle));
}

inline
void EventScheduler::releaseEventRaw(RecurringEvent *handle)
{
    d_recurringQueue.releaseReferenceRaw(castToQueuePair(handle));
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
template <class t_CLOCK, class t_DURATION>
int EventScheduler::rescheduleEvent(
             const Event                                         *handle,
             const bsl::chrono::time_point<t_CLOCK, t_DURATION>&  newEpochTime)
{
    BSLS_ASSERT(handle);

    if (bslmt::ChronoUtil::isMatchingClock<t_CLOCK>(d_clockType)) {
        return rescheduleEvent(handle, newEpochTime.time_since_epoch());
                                                                      // RETURN
    }

    const EventQueue::Pair *h = castToQueuePair(handle);

    bool                           isNewTop;
    bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex);
    bsls::TimeInterval offsetFromNow(newEpochTime - t_CLOCK::now());

    if (h) {
        h->data().d_nowOffset = bdlf::BindUtil::bind(
                                         timeUntilTrigger<t_CLOCK, t_DURATION>,
                                         newEpochTime);
    }

    int ret = d_eventQueue.updateR(h,
                                   (now() + offsetFromNow).totalMicroseconds(),
                                   &isNewTop);

    if (0 == ret && isNewTop) {
        d_queueCondition.signal();
    }
    return ret;
}

template <class t_CLOCK, class t_DURATION>
int EventScheduler::rescheduleEventAndWait(
             const Event                                         *handle,
             const bsl::chrono::time_point<t_CLOCK, t_DURATION>&  newEpochTime)
{
    BSLS_ASSERT(handle);

    if (bslmt::ChronoUtil::isMatchingClock<t_CLOCK>(d_clockType)) {
        return rescheduleEventAndWait(handle, newEpochTime.time_since_epoch());
                                                                      // RETURN
    }

    int                     ret;
    const EventQueue::Pair *h = castToQueuePair(handle);
    {
        bool                           isNewTop;
        bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex);
        bsls::TimeInterval offsetFromNow(newEpochTime - t_CLOCK::now());

        if (h) {
            h->data().d_nowOffset = bdlf::BindUtil::bind(
                                         timeUntilTrigger<t_CLOCK, t_DURATION>,
                                         newEpochTime);
        }

        ret = d_eventQueue.updateR(h,
                                   (now() + offsetFromNow).totalMicroseconds(),
                                   &isNewTop);

        if (0 == ret) {
            if (isNewTop) {
                d_queueCondition.signal();
            }
            if (d_currentEvent != h) {
                return 0;                                             // RETURN
            }
        }
    }

    // Wait until event is rescheduled or dispatched.
    bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex);
    while (1) {
        if (d_currentEvent != h) {
            break;
        }
        else {
            d_dispatcherAwaited = true;
            d_iterationCondition.wait(&d_mutex);
        }
    }

    return ret;
}
#endif

inline
void EventScheduler::scheduleEvent(const bsls::TimeInterval&    epochTime,
                                   const bsl::function<void()>& callback)
{
    scheduleEvent(epochTime,
                  EventData(callback, EventScheduler::returnZero));
}

inline
void EventScheduler::scheduleEvent(EventHandle                  *event,
                                   const bsls::TimeInterval&     epochTime,
                                   const bsl::function<void()>&  callback)
{
    scheduleEvent(event, epochTime, EventData(callback, returnZero));
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
template <class t_CLOCK, class t_DURATION>
inline
void EventScheduler::scheduleEvent(
                 const bsl::chrono::time_point<t_CLOCK, t_DURATION>& epochTime,
                 const bsl::function<void()>&                        callback)
{
    if (bslmt::ChronoUtil::isMatchingClock<t_CLOCK>(d_clockType)) {
        scheduleEvent(epochTime.time_since_epoch(), callback);
    }
    else {
        bsls::TimeInterval offsetFromNow(epochTime - t_CLOCK::now());
        scheduleEvent(now() + offsetFromNow,
                      EventData(callback,
                                bdlf::BindUtil::bind(
                                         timeUntilTrigger<t_CLOCK, t_DURATION>,
                                         epochTime)));
    }
}

template <class t_CLOCK, class t_DURATION>
void EventScheduler::scheduleEvent(
                EventHandle                                         *event,
                const bsl::chrono::time_point<t_CLOCK, t_DURATION>&  epochTime,
                const bsl::function<void()>&                         callback)
{
    BSLS_ASSERT(event);

    if (bslmt::ChronoUtil::isMatchingClock<t_CLOCK>(d_clockType)) {
        scheduleEvent(event, epochTime.time_since_epoch(), callback);
    }
    else {
        bsls::TimeInterval offsetFromNow(epochTime - t_CLOCK::now());

        scheduleEvent(event,
                      now() + offsetFromNow,
                      EventData(callback,
                                bdlf::BindUtil::bind(
                                         timeUntilTrigger<t_CLOCK, t_DURATION>,
                                         epochTime)));
    }
}

template <class t_CLOCK, class t_DURATION>
void EventScheduler::scheduleEventRaw(
               Event                                               **event,
               const bsl::chrono::time_point<t_CLOCK, t_DURATION>&   epochTime,
               const bsl::function<void()>&                          callback)
{
    BSLS_ASSERT(event);

    if (bslmt::ChronoUtil::isMatchingClock<t_CLOCK>(d_clockType)) {
        scheduleEventRaw(event, epochTime.time_since_epoch(), callback);
    }
    else {
        using namespace bsl::chrono;

        bsls::Types::Int64 startTime = (bsls::Types::Int64)
             duration_cast<microseconds>(epochTime.time_since_epoch()).count();
        if (startTime < d_cachedNow) {
            startTime = d_cachedNow;
        }

        bool newTop;
        d_eventQueue.addRawR(
                (EventQueue::Pair **)event,
                startTime,
                EventData(
                    callback,
                    bdlf::BindUtil::bind(timeUntilTrigger<t_CLOCK, t_DURATION>,
                                         epochTime)),
                &newTop);

        if (newTop) {
            bslmt::LockGuard<bslmt::Mutex> lock(&d_mutex);
            d_queueCondition.signal();
        }
    }
}
#endif

inline
void EventScheduler::scheduleRecurringEvent(
                                   const bsls::TimeInterval&    interval,
                                   const bsl::function<void()>& callback,
                                   const bsls::TimeInterval&    startEpochTime)
{
    // Note that when this review is converted to an assert, the following
    // assert is redundant and can be removed.
    BSLS_REVIEW(1 <= interval.totalMicroseconds());
    BSLS_ASSERT(0 != interval);

    scheduleRecurringEventRaw(0, interval, callback, startEpochTime);
}

inline
void EventScheduler::scheduleRecurringEvent(
                                  RecurringEventHandle         *event,
                                  const bsls::TimeInterval&     interval,
                                  const bsl::function<void()>&  callback,
                                  const bsls::TimeInterval&     startEpochTime)
{
    // Note that when this review is converted to an assert, the following
    // assert is redundant and can be removed.
    BSLS_REVIEW(1 <= interval.totalMicroseconds());
    BSLS_ASSERT(0 != interval);
    BSLS_ASSERT(event);

    scheduleRecurringEvent(
                      event,
                      RecurringEventData(interval, callback, returnZeroInt),
                      startEpochTime);
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP11_BASELINE_LIBRARY
template <class t_CLOCK,
          class t_REP_TYPE,
          class t_PERIOD_TYPE,
          class t_DURATION>
void EventScheduler::scheduleRecurringEvent(
        const bsl::chrono::duration<t_REP_TYPE, t_PERIOD_TYPE>& interval,
        const bsl::function<void()>&                            callback,
        const bsl::chrono::time_point<t_CLOCK, t_DURATION>&     startEpochTime)
{
    BSLS_ASSERT(bsl::chrono::microseconds(1) <= interval);

    if (bslmt::ChronoUtil::isMatchingClock<t_CLOCK>(d_clockType)) {
        scheduleRecurringEvent(interval,
                               callback,
                               startEpochTime.time_since_epoch());
    }
    else {
        using namespace bsl::chrono;

        bsls::TimeInterval offsetFromNow(startEpochTime - t_CLOCK::now());

        scheduleRecurringEventRaw(
             0,
             RecurringEventData(
                 interval,
                 callback,
                 bdlf::BindUtil::bind(timeUntilTriggerRecurring<t_CLOCK,
                                                                t_DURATION,
                                                                t_REP_TYPE,
                                                                t_PERIOD_TYPE>,
                                      startEpochTime,
                                      interval,
                                      bdlf::PlaceHolders::_1)),
             now() + offsetFromNow);
    }
}

template <class t_CLOCK,
          class t_REP_TYPE,
          class t_PERIOD_TYPE,
          class t_DURATION>
void EventScheduler::scheduleRecurringEvent(
       RecurringEventHandle                                    *event,
       const bsl::chrono::duration<t_REP_TYPE, t_PERIOD_TYPE>&  interval,
       const bsl::function<void()>&                             callback,
       const bsl::chrono::time_point<t_CLOCK, t_DURATION>&      startEpochTime)
{
    BSLS_ASSERT(event);
    BSLS_ASSERT(bsl::chrono::microseconds(1) <= interval);

    if (bslmt::ChronoUtil::isMatchingClock<t_CLOCK>(d_clockType)) {
        scheduleRecurringEvent(event,
                               interval,
                               callback,
                               startEpochTime.time_since_epoch());
    }
    else {
        using namespace bsl::chrono;

        bsls::TimeInterval offsetFromNow(startEpochTime - t_CLOCK::now());

        scheduleRecurringEvent(
             event,
             RecurringEventData(
                 interval,
                 callback,
                 bdlf::BindUtil::bind(timeUntilTriggerRecurring<t_CLOCK,
                                                                t_DURATION,
                                                                t_REP_TYPE,
                                                                t_PERIOD_TYPE>,
                                      startEpochTime,
                                      interval,
                                      bdlf::PlaceHolders::_1)),
             now() + offsetFromNow);
    }
}

template <class t_CLOCK,
          class t_REP_TYPE,
          class t_PERIOD_TYPE,
          class t_DURATION>
void EventScheduler::scheduleRecurringEventRaw(
      RecurringEvent                                          **event,
      const bsl::chrono::duration<t_REP_TYPE, t_PERIOD_TYPE>&   interval,
      const bsl::function<void()>&                              callback,
      const bsl::chrono::time_point<t_CLOCK, t_DURATION>&       startEpochTime)
{
    BSLS_ASSERT(event);
    BSLS_ASSERT(bsl::chrono::microseconds(1) <= interval);

    if (bslmt::ChronoUtil::isMatchingClock<t_CLOCK>(d_clockType)) {
        scheduleRecurringEventRaw(event,
                                  interval,
                                  callback,
                                  startEpochTime.time_since_epoch());
    }
    else {
        using namespace bsl::chrono;

        bsls::TimeInterval offsetFromNow(startEpochTime - t_CLOCK::now());

        scheduleRecurringEventRaw(
             event,
             RecurringEventData(
                 interval,
                 callback,
                 bdlf::BindUtil::bind(timeUntilTriggerRecurring<t_CLOCK,
                                                                t_DURATION,
                                                                t_REP_TYPE,
                                                                t_PERIOD_TYPE>,
                                      startEpochTime,
                                      interval,
                                      bdlf::PlaceHolders::_1)),
             now() + offsetFromNow);
    }
}
#endif

// ACCESSORS
inline
EventScheduler::Event*
EventScheduler::addEventRefRaw(Event *handle) const
{
    return reinterpret_cast<Event*>(d_eventQueue.addPairReferenceRaw(
                                                     castToQueuePair(handle)));

}

inline
EventScheduler::RecurringEvent*
EventScheduler::addRecurringEventRefRaw(RecurringEvent *handle) const
{
    return reinterpret_cast<RecurringEvent*>(
                                     d_recurringQueue.addPairReferenceRaw(
                                                     castToQueuePair(handle)));
}

inline
bsls::SystemClockType::Enum EventScheduler::clockType() const
{
    return d_clockType;
}

inline
bool EventScheduler::isInDispatcherThread() const
{
    return d_dispatcherThreadId.loadAcquire() ==
                                           bslmt::ThreadUtil::selfIdAsUint64();
}

inline
bsls::TimeInterval EventScheduler::now() const
{
    return d_currentTimeFunctor();
}

inline
int EventScheduler::numEvents() const
{
    return d_eventQueue.length();
}

inline
int EventScheduler::numRecurringEvents() const
{
    return d_recurringQueue.length();
}

                                  // Aspects

inline
bslma::Allocator *EventScheduler::allocator() const
{
    return d_eventQueue.allocator();
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2024 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 ----------------------------------
