Introduction
Task Scheduler is the useful and important functionality of operating systems. For Windows, it is Task Scheduler for Linux, it is Crontab, on MAC is Cron. At first sight, the stuff is doing a simple job, but it is important to enable you to automatically perform routine tasks on a computer. It does this by monitoring whatever criteria you choose to initiate the tasks (referred to as triggers) and then executing the tasks when the criteria is met. The most common tasks which can be scheduled are starting an application, sending an email message, showing a message, or backing up sensitive user data.
Tasks can be scheduled to run in different ways, for example:
- When a specific system event occurs
- At a specific time
- At a specific time on a daily schedule
- At a specific time on a weekly schedule
- At a specific time on a monthly schedule
- At a specific time on a monthly day-of-week schedule
- When the computer enters an idle state
I would introduce the library which provides the task scheduling for web application and based on JavaScript. Now, the most of web applications have thick client and the most of the stuff is running on client and making request to server just to update data. Such clients are making a lot to handle user input and managing of background tasks, so it becomes closer by functionality to operating systems. Where task scheduler has an important role.
At the beginning of the project, it was a simple part of replication system. And the main goal was to create and edit existence Crontab scheduling pattern. But as usual, it happens the project was growing by adding to them new feature and now it is the whole task scheduler.
Contents
The data structure is composed of two parts, scheduler and task management. The scheduler system is making all stuff to parse, create, edit scheduling Crontab format based string, and also it is calculating next and previous scheduled date according to the pattern, as well, preparing human readable string of occurrences.
As an extra feature, it calculates all next and previous scheduled dates till any specified by user.
The scheduler data structure consists of scheduler class and six helper classes: minuteContainer
, hourContainer
, dayContainer
, monthContainer
, weekContainer
and baseContainer
which encapsulates common functionality and is super class for them.
The scheduler operates with Crontab based string
on his input of the following format:
.------------------- minute (0 - 59)
| .---------------- hour (0 - 23)
| | .------------- day of month (1 - 31)
| | | .---------- month (1 - 12)
| | | | .------- day of week (0 - 7) (Sunday=0,7)
| | | | |
* * * * *
According to the pattern, the minutes, hours, days, months and days of week containers will be created with memorized values. The container is an array of values formed on the basis of input data and a set of methods to manipulate them. The array is formed in such a manner so the value of array and his distribution should correspond to input pattern and value of the array should be easily retrieved by index. For example, if we have pattern “*/5 2/3 * * *
” so we will have minutes container according to first parameter */5
equals to [0, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 15, 15, 15, 15, 15, …, 55, 55, 55, 55, 55]
. It allows to avoid loops during calculating next occurrence. In order to get next occurrence, we need simply to retrieve value from array by index for current minute of the date, for example for date: 08/12/2015 (15:03)
we have current minute 03
and in the container by the index 03
we will have 5
value, so the next occurrence is going to be 08/12/2015 (17:05)
, because it happens every 3 hours starting at 2 and every 5 minutes starting at 0.
The question may arise, why it operates with arrays of hashed values of minutes, hours, etc. and not with just milliseconds. Both ways are possible. Probably, the current solution first comes to mind and appeared simpler.
The current implementation of scheduler takes almost all formats of Crontab string, so it can be "*/5 2/3 * * *
" , "20/5 2/3 1-2,4-7 * *
", "1,2,3,4,5 2/3 1-2,4-7 * *
". Also it has methods to form containers by adding or removing elements from array, for example like: addMinute(m)
, removeMinute(m)
, ...
Additionally, the scheduler prints out human readable string something like that: “Occurs every 3 hours starting at 02:00, every day, every month.” Unfortunately, it is not localized for now.
One more feature which can be interesting is calculating all occurrences till the specific date. And to avoid freezing of browser during calculation dates for very detailed pattern (e.g. “*/5 * * * *” ) during one year, the operation is divided into small chunks and a timer is used to delay the processing of each chunk to allow browser for the time to switch to different task. The default size of chunk is 100 elements or you can specify own by passing any number at third parameter of the function getAllNextTill(...)
.
this.getAllNextTill = function (endDate, callback, maxSyncLoadEl)
{
var _allDates = Array();
var _maxSyncLoadEl = typeof maxSyncLoadEl != 'undefined' &&
scheduler.isNumber(maxSyncLoadEl) ? maxSyncLoadEl : maxSyncLoadElDef;
var _callback = callback;
var _startDate = startDate;
var _syncLoadedEl = 0;
if (_startDate != null && _startDate instanceof Date)
{
var scope = this;
(function loop()
{
try
{
while(_maxSyncLoadEl == -1 || _syncLoadedEl <= _maxSyncLoadEl)
{
if (scope.previous())
{
var curDate = scope.getDateStamp();
if (_startDate < curDate)
{
_allDates.push(curDate);
}
else
{
if (callback)
{
callback(_allDates);
}
return;
}
}
_syncLoadedEl++;
}
_syncLoadedEl = 0;
setTimeout(loop, 1);
}
catch (e)
{
throw e;
}
})();
}
else
{
throw new InvalidArgumentException();
}
}
The taskManger
and task
objects are making all jobs by the scheduling and triggering tasks. Task manager encapsulates methods to manage tasks. The task object encapsulates methods to run job at specific time which is set by scheduler or it can be forcibly launched now.
Example of scheduling of next occurrence of task:
this.start = function ()
{
_isStarted = true;
continuouslyRun(true);
};
function continuouslyRun(skip)
{
if (_isStarted)
{
if (!skip)
{
runOnce();
}
var currDate = new Date();
_scheduler.setCurrentTimeExt(currDate);
if (_scheduler.next())
{
var nextDate = _scheduler.getDateStamp();
var timeout = nextDate.getTime() - currDate.getTime();
_timerId = setTimeout(continuouslyRun, timeout);
}
}
};
function runOnce()
{
_isRunning = true;
if (_action)
{
if (!_args)
{
_action(scope);
}
else
{
_action(scope, _args);
}
}
_isRunning = false;
};
Data Model
Sample of Use
For now, it is just set of methods to add/remove tasks, enumerate existence and start/stop manager. But I have plans to extend the taskManager
interface, add more methods to manage tasks, enable/disable methods and methods to accumulate statistics. Also, it will be more properly to load dictionary and formats of human readable string
of scheduler on-the-fly according to language selected at browser or specified by user.
var tasks = new scheduler.taskManager();
tasks.addTask(new scheduler.task("0/2 * * * *", function (task, args)
{
}, "done"));
tasks.addTask(new scheduler.task("0/10 * * * *", function (task, args)
{
}, "done"));
var _task = new scheduler.task("0/5 * * * *", function (task, args)
{
}, "done");
tasks.addTask(_task);
tasks.enumerate(function (task, args)
{
});
tasks.start();
tasks.removeTask(_task);
How to Use
The source taskScheduler_src.zip contains not obfuscated or minimized source code. To see how it works, just to run index.html file. At source, you will find files of task management system and scheduler itself, and utils file, also there are couple files to demonstrate how it works. It can be wrapped and used as jQuery addon or used directly, it doesn't have any dependencies and can be used as is.