System Scheduler specification

Last Update: Jan. 12, 1998

The Scheduler is responsible for keeping track of when events such as scheduled updates are supposed to occur, and to cause those events to occur.  In the interest of reusable components, the scheduler itself doesn't have any knowledge of the client or of scheduled updates; rather, it will be implemented as an API level service that will execute a specific callback when the designated time occurs.  The scheduling service will not have any UI of its own but will rely on its clients to provide user indication of scheduled events.

Functionality

The scheduling service provides the following functionality: The service does not In order to use the services that the scheduler provides, a scheduler instance must first be created; subsequent calls to the scheduler can then reference this instance of the scheduler.  The scheduler does not make use of global variables and therefore is not limited to a single instance, however, it is expected that there will be only scheduler instance of the scheduler in the client.

APIs - Application Programming Interfaces

Interfaces for the scheduler fall into three categories.  The first set controls the operation of the scheduler itself, including starting and stopping the scheduler; the second type of interface allows clients to add and remove events and observers from the scheduler.  The third type covers the interfaces to the callbacks that the scheduler issues in order to fire an event or to notify its observers.

Scheduler Types and Structures

SchedulerPtr
typedef void *SchedulerPtr;

All Scheduler APIs (with the exception of SchedulerStart) require a reference to a scheduler instance in the form of a reference of type SchedulerPtr.  A SchedulerPtr is returned from SchedulerStart, and its value is used as a handle for a scheduler.  The structure referenced by a SchedulerPtr should be considered an opaque entity; do not directly manipulate any data referenced by a SchedulerPtr reference.

SchedulerErr
typedef enum SchedulerErr {
        SCHED_ERR_BEGIN = -5,
        SCHED_ERR_BAD_EVENT = -5,
        SCHED_ERR_BAD_TIME,
        SCHED_ERR_BAD_PARAMETER,
        SCHED_ERR_OUT_OF_MEMORY,
        SCHED_ERR_INVALID_SCHEDULER,
        SCHED_ERR_NOERR = 0,
        SCHED_WARN_EVENTS_DROPPED,
        SCHED_WARN_END = 1
} SchedulerErr;

Most APIs for the scheduler return a SchedulerErr type.  SchedulerErr is a typedef for a signed integer value; zero is defined as no error (successful operation).  Negative values are errors (could not complete operation), and positive values are warnings (operation completed with comments). All functions (even those which below indicate that they "cannot fail") can return a ERR_SCHEDULER_INVALID if the passed in scheduler reference is NULL.

SchedulerTime
typedef struct  _SchedulerTime {
        PRUint32        repeating;
        PRInt32         range;
        PRTime          baseTime;
        PRTime          start;
        PRTime          end;
} SchedulerTime;

In order to instruct the scheduler to execute an event at a particular time, clients specify a scheduled time expressed in a SchedulerTime structure.  The SchedulerTime structure has values which correspond to the event's firing time (baseTime), and a start and end time from which the event is valid (start and end, respectively).  Additionally, SchedulerTime specifies a range, which acts as a randomization value within which the event's scheduled time can "drift," and a repeating interval, both of whch are expressed in seconds.

The exact time at which an event fires is compued as follows.

Each time an event is to fire, a random value (-range < value < range) is computed.  This value, given as seconds, is added to the specified baseTime to derive the time that the event is to occur.  If the computed firing time is before the start or after the end times, the firing time is pinned to the start or end time.

For repeating events, the event's next firing time is computed by adding the value of repeating expressed in seconds, to the event's baseTime.  If the new computed time is falls outside the time interval specified by the start and end times, the event "expires" and is removed from the scheduler.  Upon removal from the scheduling queue, expired events If the event has not expired, a random value is selected as above and added to this time to derive the new firing time.  Note that repeating events are computed based on the pre-randomized times; that is, the time around which the range is computed will always be an integral number of repeating intervals past the original baseTime.

For example, if an event was scheduled with a time of noon, a range of 300 (5 minutes) and an interval of 3600 (1 hour), the first event would fire between 11:55 and 12:05 pm.  Subseqent events would be fired between :55 and :05 of every hour.  If an end time were also scheduled at 4:02 pm, then the event that was scheduled for 4 pm (noon plus three iterations of interval) may, with randomization, attempt to be scheduled for 4:04pm.  This value would be pinned to 4:02 for the actual firing time.  However, the next time through, the new base time would be 5 pm, which is beyond the end.  In this case, the event would simply cease to be scheduled and would be removed from the queue.

Because of the way in which range and repetion are computed, it generally makes sense to have repeat >> range.  That is, the interval at which events repeat should generally be greater than the amount of time within which those events are allowed to drift.  However, from a practical standpoint, the implementation prevents scheduled events from firing out of sequence, so specifying a range which is larger than an interval is not an error, however, it may resuilt in a number of events firing near-simultaneously. If an event added to the scheduler was scheduled for a time in the past, the event will be fired immediately upon being added to the scheduler.

SchedFuncPtr and SchedFuncDataPtr
typedef void *SchedFuncDataPtr;
typedef void (PR_CALLBACK *SchedFuncPtr)(SchedulerPtr pScheduler, PRUint32 eventID,
                                         SchedFuncDataPtr pData)

SchedFuncPtr is a prototype for the function which is called when the scheduler fires an event.  The function returns nothing and accepts three parameters, a reference to the scheduler from which the event was dispatched, the event ID which identifies the event that executed the function and an arbitrary argument passed in at event creation.  The third argument is opaque to the scheduler and can be used by clients to transmit or store state information, etc.  The function is called asynchronously in its own thread, which terminates when the function exits.

SchedObsPtr and SchedObsDataPtr
typedef void *SchedObsDataPtr;
typedef void (PR_CALLBACK *SchedObsPtr)(SchedulerPtr pScheduler, PRUint32 observerID,
                                        SchedObsDataPtr pData, SchedObsType type,
                                        PRUint32 eventID, const char *eventName,
                                        SchedFuncDataPtr pEventData);

Observer callbacks are specified using the prototype given as SchedObsPtr.  When an event is added or removed from the scheduler, or when an event fires, each observer who has registered with that instance of the scheduler receives a notification message.  The function returns nothing and receives information on the event that triggered the notification (its id, name, and event data), what kind of operation (add, remove, fire.) occurred, and the id and data associated with the specific observer.  Both data pointers are opaque to the scheduler.  The event data passed in (pEventData) is the actual data pointer for the event, not a copy, so observers must be aware that changing the information referred to in pEventData may have unexpected results.  Additionally, because there is no type information associated with pEventData, observers must take particular care when making assumptions about the structure or usage of pEventData.

Scheduler Operation

SchedulerPtr SchedulerStart(void)

Creates and initializes an instance of the scheduler.  SchedulerStart creates a new (local) NSPR thread in which to run the instance of the scheduler and returns a reference to a newly created scheduler object.  This object is required for all calls to the scheduler API.  The newly created scheduler object is immediately ready to register and dispatch events.  SchedulerStart allocates only enough memory for the scheduler object itself; any supplementary data structures including the event and observer queues are allocated when they are first referenced by EventAdd or ObserverAdd.  If the thread can not be created or memory can not be allocated, SchedulerStart returns NULL.   In order to properly cleanup, SchedulerStop() should be called when the scheduler is to cease operation.

SchedulerErr SchedulerStop(SchedulerPtr pScheduler);

SchedulerStop halts scheduling of the specified scheduler instance, deletes any memory referenced by the scheduler's event and observer queues, and frees the instance of the Scheduler.  After calling SchedulerStop, the instance of the scheduler referenced by the specified SchedulerPtr is invalid and must not be used.  SchedulerStop can not fail and will always return a successful result code.

SchedulerStop will instruct the scheduler's thread to halt, but due to thread synchronization issues, the target thread may take some time to get around to completing.  Halting the scheduler does not have any impact on any threads running as a result of scheduled events; once the scheduler fires an event, it has no further control over that event.

SchedulerErr SchedulerPause(SchedulerPtr pScheduler);

SchedulerPause causes the scheduler to cease firing events.  Pausing the scheduler does not affect the the addition of new events to the scheduling queue, nor does it prevent management of the observer list.  While the scheduler is paused, any events scheduled to fire will be held in the queue until the scheduler's operation is resumed with SchedulerResume.  Calling SchedulerPause multiple times will have no effect beyond the first; SchedulerPause can not fail and will always return a successful result code.

SchedulerErr SchedulerResume(SchedulerPtr pScheduler);

SchedulerResume reverses the effect of a previous SchedulerPause call.  Events that did not fire while the scheduler queue was paused will immediately fire, unless their scheduled end time (expiration) has passed, in which case those items are removed from the queue.  Items removed from the queue in this way cause an event deletion notification to be sent to the scheduler's observers.  All other conditions, including sending SchedulerResume to a scheduler which has not been paused will always return a successful result code.

Pausing and resuming the scheduler is not reference counted; if multiple threads have paused the scheduler, the first to resume it will cause the scheduler to start actively processing events.

SchedulerErr SchedulerFreeze(SchedulerPtr pScheduler);

SchedulerFreeze causes the scheduler to both stop processing events and to reject the addition of new items to the queue.  This function is typically only called internally, since clients do not have any way to unfreeze the scheduler.  The scheduler calls this routine while it is executing the FireAll function in order to make sure that the scheduling queue stays in a consistent state while it is processing all of the events.

SchedulerFreeze will not fail and will always return a successful result code.

SchedulerErr SchedulerFireAll(SchedulerPtr)    This function is not yet implemented

SchedulerFireAll instructs the scheduler to close the queue to new requests and sequentially fire all events in the current event queue without regard to their scheduled time.  Notification events will be sent to observers for each fired event.

In order to prevent new events from being added to the queue as all existing events are being fired, and therefore creating a potential infinite loop, the SchedulerFireAll function freezes the existing event queue prior to beginning operation.  It will then create a new, empty event queue for the given scheduler instance.  The event queue is ready to receive new events, but it is paused, preventing execution of any events from ocurring while the FireAll request is processed.  The scheduler then fires each event in the previous queue in sequence; when it has fired the last event, it will destroy the old event queue and resume the new one it has created.  Normal operation of the scheduler can then continue.

(insert diagram here)

Event and Observer List management

SchedulerErr SchedulerAddEvent(SchedulerPtr pScheduler, PRUint32 *pEventID, const char *szName,
                               SchedulerTime *pTime, SchedFuncPtr function, SchedFuncDataPtr pData);

SchedulerAddEvent adds an event to the scheduler's event queue.  An event is defined as the combination of a function and its data, scheduled to fire at a particular time (or set of times).  Accordingly, a client which wishes to add an event needs to specify a time at which the event should fire, a function to call when the time occurs, and any amount of data which will be passed to the specified function at the time that the event is fired.  A client may also supply a user-visible name with which to     identify the event.  Note that this name is not used by the scheduler at all and may be NULL if no user-visible name is desired.  Both the time and name are copied into the scheduler's internal structures, so the memory referenced by these events may be released after making this call.

If successful, SchedulerAddEvent adds the event to the event queue, assigns a unique eventID which can be used to later refer to this specific event, and returns a successful result code.  If NULL is passed in as the reference to the eventID, an event ID is still generated, but the client will be unable to change or delete the event in the future.  Note that eventIDs are unique only to a particular scheduler instance; they are not guaranteed to be globally unique across scheduler instances.

SchedulerAddEvent can fail for a number of reasons.  The most common is lack of memory.  If the event queue needs to be created or expanded in size to accomodate the new event, the scheduler will fail, returning SCHED_ERR_OUT_OF_MEMORY, and will return 0 as the eventID.  Event addition will also fail if the time specified is invalid, is outside the bounds specified by the event's start and end times, or if the given range is negative (SCHED_ERR_BAD_TIME); or if a NULL value is given for the function (SCHED_ERR_BAD_PARAMETER).

Upon successful addition of the event, the scheduler will send notification messages to its observers indicating that an event was added to the queue.  Included in the notification will be a reference to the scheduler and the eventID, from which the observers can determine additional information about the event.  Because notification messages are synchronous, the observers should not perform any lengthy operation when notified, as that will impact the performance of event addition.  Internally, the scheduler adds the new event into its event queue, sorted by when the event is scheduled to fire.

SchedulerErr SchedulerRemoveEvent(SchedulerPtr pScheduler, PRUint32 eventID);

SchedulerRemoveEvent removes a previously added event from the scheduler's event queue.  The event to remove is specified by its eventID, previously assigned by SchedulerAddEvent.  If the specified event ID is invalid, the scheduler will return an error, SCHED_ERR_BAD_EVENT, and no operation will be performed.

Upon successful removal of an event, the scheduler will send notification messages to its observers indicating that an event was removed.  Including in the notification will be the eventID, from which the observers can determine additional information about the event.  Because notification messages are synchronous, the observers should not perform any lengthy operation when notified, as that will impact the performance of event removal.  After all observers have been notified, the memory associated with the event will be destroyed and the eventID will no longer be valid.

SchedulerErr SchedulerFindEvent(SchedulerPtr, uint eventID, &time, &name, &function, &data)
   This function is not yet implemented

SchedulerErr SchedulerAddObserver(SchedulerPtr pScheduler, PRUint32 *pObserverID, SchedObsType type,
                                  SchedObsPtr function, SchedObsDataPtr pData);

SchedulerAddObserver adds an observer to the specified scheduler's observer list.  An observer is a function which is called when the scheduler adds, removes, or fires an event.  Which of these actions trigger a notification for a given observer is specified in the observer's type, which is a bitfield made up of the union of the following values:

#define SCHED_OBSERVE_ADD    1
#define SCHED_OBSERVE_FIRE   2
#define SCHED_OBSERVE_REMOVE 4

When an observer is notified that something has happened, the observer's function is called with the id and name of the event that fired, along with the event's data pointer and the observer's id and the data pointer specified in the AddObserver call.  Note that the event data that is passed in is not a copy of the event's data, but a pointer to it, so altering the data will cause the data to be altered for the next iteration of the event, as well as for subsequent observers which are notified.  However, since observers will receive notification for all events on an event queue, observers must take care that they actually know the datatype of the information passed in the event's data fields.  In no case should the event data be freed.

Observer lists are not terribly efficient; the mechanism used to determine which observers receive a given notification is done by linearly searching the list of observers, so for performance reasons, callers should not specify a large number of observers for any given scheduler instance.  Indeed, the scheduler was designed with only one specific observer in mind -- the code which handles the display of status information within the client user interface.

Like the corresponding AddEvent function, above, adding an observer causes the scheduler to generate a unique observerID, which can later be used to remove the observer.  Also like AddEvent, the observerID is unique only within a scheduler instance.  ObserverIDs and EventIDs are not interchangeable; their namespace may overlap.  Therefore, it's important that the caller keep track of which identifiers represent events and which represent observers. If NULL is passed in for the observerID, an observer ID is generated, but it is not returned to the caller.

SchedulerAddObserver can fail if memory can not be allocated to store the observer or create the observer queue, or if the function specified is NULL.  In each failure case, the observerID returned is undefined.

SchedulerErr SchedulerRemoveObserver(SchedulerPtr pScheduler, PRUint32 observerID)

SchedulerRemoveObserver removes an observer from the scheduer's observer list.  That observer no longer receives notification messages and the scheduler frees any memory it has allocated to track the specified observer.  If no observer with the given ID exists, the function returns SCHED_ERR_BAD_PARAMETER, and no operation is performed.