Merge pull request #69 from ZachGoldberg/master
Make mod_cron behave more like unix cron
This commit is contained in:
commit
289f2f2b1d
|
@ -31,8 +31,16 @@ Each task is described with five elements:
|
||||||
* Time is an integer.
|
* Time is an integer.
|
||||||
* Units indicates the time unit you use. It can be: seconds, minutes, hours, days.
|
* Units indicates the time unit you use. It can be: seconds, minutes, hours, days.
|
||||||
* Module and * Function are the exact call you want to schedule.
|
* Module and * Function are the exact call you want to schedule.
|
||||||
* Arguments is an erlang list of arguments inside the characters "> ."
|
* Arguments is an array. Strings will be converted to binaries.
|
||||||
|
* timer_type is one of 'fixed' or 'interval'. Fixed timers occur at a fixed time
|
||||||
|
after the [minute|hour|day] e.g. every hour on the 5th minute (1:05PM, 2:05PM etc)
|
||||||
|
interval timers occur every interval (starting on an even unit) e.g. every 10 minutes
|
||||||
|
starting at 1PM, 1:10PM, 1:20PM etc.
|
||||||
|
|
||||||
|
Fixed timers are the equivalent of unix cron's comma syntax e.g. "2 * * *" and interval
|
||||||
|
timers are the / syntax e.g. "*/5 * * *".
|
||||||
|
|
||||||
|
Default timer_type is interval.
|
||||||
|
|
||||||
EXAMPLE TASKS
|
EXAMPLE TASKS
|
||||||
=============
|
=============
|
||||||
|
@ -45,12 +53,17 @@ modules:
|
||||||
units: hours
|
units: hours
|
||||||
module: mnesia
|
module: mnesia
|
||||||
function: info
|
function: info
|
||||||
arguments: "> []."
|
arguments: {}
|
||||||
|
timer_type: fixed
|
||||||
- time: 10
|
- time: 10
|
||||||
units: seconds
|
units: seconds
|
||||||
module: ejabberd_auth
|
module: ejabberd_auth
|
||||||
function: try_register
|
function: try_register
|
||||||
arguments: "> [\"user1\", \"localhost\", \"somepass\"]."
|
arguments:
|
||||||
|
- "user1"
|
||||||
|
- "localhost"
|
||||||
|
- "somepass"
|
||||||
|
timer_type: interval
|
||||||
|
|
||||||
|
|
||||||
EJABBERD COMMANDS
|
EJABBERD COMMANDS
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
run_task/3,
|
run_task/3,
|
||||||
web_menu_host/3, web_page_host/3,
|
web_menu_host/3, web_page_host/3,
|
||||||
start/2,
|
start/2,
|
||||||
|
apply_interval/3,
|
||||||
|
apply_interval1/3,
|
||||||
stop/1]).
|
stop/1]).
|
||||||
|
|
||||||
-include("ejabberd_commands.hrl").
|
-include("ejabberd_commands.hrl").
|
||||||
|
@ -27,7 +29,6 @@
|
||||||
|
|
||||||
-record(task, {taskid, timerref, host, task}).
|
-record(task, {taskid, timerref, host, task}).
|
||||||
|
|
||||||
|
|
||||||
%% ---------------------
|
%% ---------------------
|
||||||
%% gen_mod
|
%% gen_mod
|
||||||
%% ---------------------
|
%% ---------------------
|
||||||
|
@ -52,25 +53,103 @@ stop(Host) ->
|
||||||
%% Task management
|
%% Task management
|
||||||
%% ---------------------
|
%% ---------------------
|
||||||
|
|
||||||
%% Method to add new task
|
time_to_ms(IntervalUnit, IntervalNum) ->
|
||||||
add_task(Host, Task) ->
|
case IntervalUnit of
|
||||||
[Time_num, Time_unit, Mod, Fun, Args] =
|
seconds -> timer:seconds(IntervalNum);
|
||||||
[proplists:get_value(Key, Task) || Key <- [time, units, module, function, arguments]],
|
minutes -> timer:minutes(IntervalNum);
|
||||||
|
hours -> timer:hours(IntervalNum);
|
||||||
|
days -> timer:hours(IntervalNum)*24
|
||||||
|
end.
|
||||||
|
|
||||||
%% Convert to miliseconds
|
time_until_event(IntervalMS) ->
|
||||||
Time = case Time_unit of
|
{MegaSecs, Secs, MicroSecs} = erlang:now(),
|
||||||
seconds -> timer:seconds(Time_num);
|
NowMS = (MegaSecs*1000000 + Secs)*1000 + round(MicroSecs/1000),
|
||||||
minutes -> timer:minutes(Time_num);
|
MSSinceLastEvent = (NowMS rem IntervalMS),
|
||||||
hours -> timer:hours(Time_num);
|
(IntervalMS - MSSinceLastEvent).
|
||||||
days -> timer:hours(Time_num)*24
|
|
||||||
|
begin_interval_timer(TaskId, TimeUnit, TimeNum, StartParams) ->
|
||||||
|
IntervalMS = time_to_ms(TimeUnit, TimeNum),
|
||||||
|
MSToGo = time_until_event(IntervalMS),
|
||||||
|
{ok, TimerRef} = timer:apply_after(MSToGo, ?MODULE, apply_interval,
|
||||||
|
[TaskId, IntervalMS, StartParams]),
|
||||||
|
TimerRef.
|
||||||
|
|
||||||
|
begin_fixed_timer(TaskId, TimeUnit, TimeNum, StartParams) ->
|
||||||
|
%% A fixed second timer happens minutely, minute timer happens hourly, a fixed hour timer happens daily.
|
||||||
|
IntervalMS = case TimeUnit of
|
||||||
|
seconds -> timer:minutes(1);
|
||||||
|
minutes -> timer:hours(1);
|
||||||
|
hours -> timer:hours(1) * 24;
|
||||||
|
_ -> undefined
|
||||||
end,
|
end,
|
||||||
|
|
||||||
%% Start timer
|
FixedTimeMS = time_to_ms(TimeUnit, TimeNum),
|
||||||
{ok, TimerRef} = timer:apply_interval(Time, ?MODULE, run_task, [Mod, Fun, Args]),
|
|
||||||
|
%% Calculate time until the next IntervalUnit, then add FixedTimeMS
|
||||||
|
%% e.g. now is 00:00:32 wait until the next minute (28s), then keep waiting
|
||||||
|
%% 5 more seconds to get 00:01:05 (= wait 33s).
|
||||||
|
%% We then fire the event at 00:01:05 and wait a minute to fire again
|
||||||
|
%% at 00:02:05 etc.
|
||||||
|
MSToGo1 = time_until_event(IntervalMS) + FixedTimeMS,
|
||||||
|
|
||||||
|
%% If we were, for example, at 1:03PM and the event is hourly at
|
||||||
|
%% 1:05PM then we dont want to wait 57+5 minutes, we want to wait
|
||||||
|
%% 2 minutes.
|
||||||
|
MSToGo2 = if MSToGo1 > IntervalMS ->
|
||||||
|
MSToGo1 - IntervalMS;
|
||||||
|
true ->
|
||||||
|
MSToGo1
|
||||||
|
end,
|
||||||
|
|
||||||
|
?DEBUG("MS To Go Fixed: ~p ~p", [MSToGo1, MSToGo2]),
|
||||||
|
{ok, TimerRef} = timer:apply_after(MSToGo2, ?MODULE, apply_interval, [TaskId, IntervalMS, StartParams]),
|
||||||
|
TimerRef.
|
||||||
|
|
||||||
|
apply_interval(TaskId, IntervalMS, StartParams) ->
|
||||||
|
%% apply_after doesnt belong to a pid (which is needed for apply_after to stay alive), so make one
|
||||||
|
spawn(?MODULE, apply_interval1, [TaskId, IntervalMS, StartParams]).
|
||||||
|
|
||||||
|
apply_interval1(TaskId, IntervalMS, [M, F, A]=StartParams) ->
|
||||||
|
% we've already waited for the interval to expire once to get here,
|
||||||
|
% and apply_interval doesn't apply first, so run the task once then start the timer
|
||||||
|
run_task(M, F, A),
|
||||||
|
{ok, TimerRef} = timer:apply_interval(IntervalMS, ?MODULE, run_task, StartParams),
|
||||||
|
update_timer_ref(TaskId, TimerRef),
|
||||||
|
|
||||||
|
%% Wait forever so the timer process stays alive
|
||||||
|
receive
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
update_timer_ref(TaskId, NewTimerRef) ->
|
||||||
|
[Task] = ets:lookup(cron_tasks, TaskId),
|
||||||
|
NewTask = Task#task{timerref=NewTimerRef},
|
||||||
|
ets:insert(cron_tasks, NewTask).
|
||||||
|
|
||||||
|
%% Method to add new task
|
||||||
|
add_task(Host, Task) ->
|
||||||
|
[TimeNum, TimeUnit, Mod, Fun, Args, InTimerType] =
|
||||||
|
[proplists:get_value(Key, Task) || Key <- [time, units, module, function, arguments, timer_type]],
|
||||||
|
TimerType = case InTimerType of
|
||||||
|
<<"fixed">> ->
|
||||||
|
fixed;
|
||||||
|
fixed ->
|
||||||
|
fixed;
|
||||||
|
_ ->
|
||||||
|
interval
|
||||||
|
end,
|
||||||
|
|
||||||
%% Get new task identifier
|
%% Get new task identifier
|
||||||
TaskId = get_new_taskid(),
|
TaskId = get_new_taskid(),
|
||||||
|
|
||||||
|
TimerRef = case TimerType of
|
||||||
|
interval ->
|
||||||
|
begin_interval_timer(TaskId, TimeUnit, TimeNum, [Mod, Fun, Args]);
|
||||||
|
fixed ->
|
||||||
|
begin_fixed_timer(TaskId, TimeUnit, TimeNum, [Mod, Fun, Args])
|
||||||
|
end,
|
||||||
|
|
||||||
%% Store TRef
|
%% Store TRef
|
||||||
Taskr = #task{
|
Taskr = #task{
|
||||||
taskid = TaskId,
|
taskid = TaskId,
|
||||||
|
|
Loading…
Reference in New Issue