Current section
Files
Jump to
Current section
Files
src/ecrontab_worker.erl
-module(ecrontab_worker).
-behaviour(gen_server).
-include("ecrontab.hrl").
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([
start_link/1,
add/2,
del/2
]).
-record(state, {name, task_count = 0, max_task_count = 0, now_seconds}).
-record(server_task, {name, spec, mfa, options}).
-define(DICTIONARY_KEY(Seconds), {ecrontab, Seconds}).
%%%===================================================================
%%% API
%%%===================================================================
start_link(Args) ->
gen_server:start_link(?MODULE, Args, []).
add(Pid, Task) ->
try
gen_server:call(Pid, {add, Task})
catch
{'EXIT', Reason} ->
{error, Reason}
end.
del(_Pid, undefined) -> {error, undefined_name};
del(Pid, Name) ->
try
gen_server:cast(Pid, {del, Name})
catch
{'EXIT', Reason} ->
{error, Reason}
end.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([WorkerName]) ->
init([WorkerName, ?ONE_PROCESS_MAX_TASKS_COUNT]);
init([WorkerName, MaxTaskCount]) ->
case ets:insert_new(?ETS_WORKER_NAME_INDEX, {WorkerName, self()}) of
true ->
process_flag(trap_exit, true),
gproc:reg(?GROUP_NAME),
{ok, #state{name = WorkerName, now_seconds = ?TIMESTAMP, max_task_count = MaxTaskCount}};
false ->
{stop, duplicate_name}
end.
handle_call({add, _Task}, _From, State) when State#state.task_count == State#state.max_task_count ->
{reply, {error, worker_over}, State};
handle_call({add, Task}, _From, State) ->
{Reply, NewState} = do_add(State, Task),
{reply, Reply, NewState};
handle_call(_Msg, _From, State) ->
{reply, noknow, State}.
handle_cast({del, Name}, State) ->
NewState = do_del(State, Name),
{noreply, NewState};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({ecrontab_tick, Seconds}, State) ->
NewState = do_tick(State#state{now_seconds = Seconds}),
{noreply, NewState};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, State) ->
ets:delete(?ETS_WORKER_NAME_INDEX, State#state.name),
ok.
code_change(_Old, State, _Extra) ->
{ok, State}.
%% ====================================================================
%% internal API
%% ====================================================================
do_tick(State) ->
case erase(?DICTIONARY_KEY(State#state.now_seconds)) of
undefined ->
State;
[] ->
State;
STasks ->
NowDatetime = ?TIMESTAMP_TO_DATETIME(State#state.now_seconds),
loop_tasks(NowDatetime, STasks, State)
end.
loop_tasks(_, [], State) ->
State;
loop_tasks(NowDatetime, [STask | STasks], State) ->
do_spawn_task(STask#server_task.mfa),
case ecrontab_next_time:next_seconds(STask#server_task.spec, NowDatetime) of
{ok, NextSeconds} ->
put_in_list(NextSeconds, STask),
loop_tasks(NowDatetime, STasks, State);
_ ->
loop_tasks(NowDatetime, STasks, State#state{task_count = State#state.task_count - 1})
end.
do_spawn_task({M, F, A}) ->
erlang:spawn(M, F, A);
do_spawn_task({Node, M, F, A}) ->
rpc:cast(Node, M, F, A);
do_spawn_task(Fun) ->
erlang:spawn(Fun).
do_add(State, Task) ->
NowDatetime = ?TIMESTAMP_TO_DATETIME(State#state.now_seconds),
case ecrontab_next_time:next_seconds(Task#task.spec, NowDatetime) of
{ok, NextSeconds} ->
STask = task_to_server_task(Task),
put_in_list(NextSeconds, STask),
{ok, State#state{task_count = State#state.task_count + 1}};
Err ->
{Err, State}
end.
task_to_server_task(Task) ->
#server_task{name = Task#task.name, spec = Task#task.spec, mfa = Task#task.mfa, options = Task#task.options}.
put_in_list(Seconds, STask) ->
List =
case get(?DICTIONARY_KEY(Seconds)) of
undefined ->
[STask];
List0 ->
[STask | List0]
end,
put(?DICTIONARY_KEY(Seconds), List).
do_del(State, Name) ->
lists:foldl(
fun({?DICTIONARY_KEY(Seconds), List}, Acc) ->
case del_name(Name, Seconds, List) of
false -> Acc;
ok ->
Acc#state{task_count = State#state.task_count - 1}
end;
(_, Acc) -> Acc
end, State, get()).
del_name(Name, Seconds, List) ->
case lists:keytake(Name, #server_task.name, List) of
false ->
false;
{value, _, NewList} ->
put(?DICTIONARY_KEY(Seconds), NewList),
ok
end.