System Scheduler Spec

# 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:

- Allows clients to register an event to occur at a particular time, optionally within a range. When the time occurs, the function specified in the event is called in its own thread, which terminates after the event occurs. The scheduler can schedule events to occur only once or to repeat at a given interval.
- Allows registration of callbacks to notify observers of events that are scheduled, executed, or removed from the scheduling queue
- Allows all items in the queue to be executed immediately, without regard to the current time
- Allows all items in the queue to be held so they do not execute, regardless of whether the events' times have elapsed.

The service does not

- Guarantee any level of accuracy; the time specified in the event is the earliest time at which the event will fire, and events in an time range will not fire after the specified end time, but otherwise, the scheduler will make a best-effort attempt to match the given schedule
- Persist information across Navigator startup. Items in the queue when the scheduler is shutdown will be discarded and will not automatically be reinstated into the queue when Navigator starts up again.
- Allow clients to determine the state of their scheduled events, or whether a specific event has fired. However, a client can register itself as an observer of event execution, after which it will receive notifications of every event that was fired.

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 `eventID`s 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 `EventID`s 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.