Current section
Files
Jump to
Current section
Files
src/ecrontab_task_manager.erl
-module(ecrontab_task_manager).
-behaviour(gen_server2).
-include("ecrontab.hrl").
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([
start_link/0,
add/4, add/5,
remove/2,
reg_server/1,
unreg_server/1,
task_over/2,
delete_task/2,
servers_info/0
]).
-define(SERVER, ?MODULE).
-record(state, {server_ets_name_counter=0,servers=[],unlive_servers=[]}).
-record(server, {task_count=0,pid,tid}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server2:start_link({local, ?SERVER}, ?MODULE, [], []).
%% add/4
add(Name, Spec, MFA, Options) ->
NowDatetime = erlang:localtime(),
add_do(Name, Spec, MFA, NowDatetime, Options).
%% add/5
add(undefined, Spec, MFA, NowDatetime, Options) ->
Name = erlang:unique_integer(),
add_do(Name, Spec, MFA, NowDatetime, Options);
add(Name, Spec, MFA, NowDatetime, Options) ->
add_do(Name, Spec, MFA, NowDatetime, Options).
add_do(Name, Spec, MFA, NowDatetime, Options) ->
Task = #task{name = Name, spec = Spec, mfa = MFA, add_time = NowDatetime, options = Options},
case gen_server2:call(?SERVER, {add, Task}, infinity) of
ok ->
{ok, Name};
Err ->
Err
end.
remove(Name, Options) ->
gen_server2:call(?SERVER, {remove, {Name, Options}}).
reg_server(Pid) ->
gen_server2:call(?SERVER, {reg_server, Pid}).
unreg_server(Pid) ->
gen_server2:cast(?SERVER, {unreg_server, Pid}).
task_over(Name, Tid) ->
spawn(?MODULE, delete_task, [Tid, Name]),
gen_server2:cast(?SERVER, {task_over, Tid}).
delete_task(Tid, Name) ->
ets:delete(?ETS_NAME_TASK_INDEX, Name),
ets:delete(Tid, Name).
servers_info() ->
gen_server2:call(?SERVER, servers_info).
%%%===================================================================
%%% gen_server2 callbacks
%%%===================================================================
init([]) ->
process_flag(trap_exit, true),
ets:new(?ETS_NAME_TASK_INDEX,[
public,named_table,
{keypos,#task_index.name},
{write_concurrency, true},
{read_concurrency, true}]),
{ok, #state{}}.
handle_call({add, Task}, _From, State) ->
case do_add(Task, State) of
{ok, NewState} ->
{reply, ok, NewState};
Reply ->
{reply, Reply, State}
end;
handle_call({remove, {Name, Options}}, _From, State) ->
case do_remove(Name, Options, State) of
{ok, NewState} ->
{reply, ok, NewState};
Reply ->
{reply, Reply, State}
end;
handle_call({reg_server, Pid}, _From, State) ->
case State#state.unlive_servers of
[] ->
Counter = State#state.server_ets_name_counter+1,
Tid = new_server_ets(Counter),
Servers = add_server(#server{pid = Pid,tid = Tid}, State#state.servers),
{reply, Tid, State#state{server_ets_name_counter = Counter,servers = Servers}};
[Server|UnliveServers] ->
Tid = Server#server.tid,
Servers = add_server(Server#server{pid = Pid}, State#state.servers),
{reply, Tid, State#state{servers = Servers, unlive_servers = UnliveServers}}
end;
handle_call(servers_info, _From, State) ->
Servers = State#state.servers,
UnliveServers = State#state.unlive_servers,
{reply, [{servers, Servers},{unlive_servers, UnliveServers}], State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast({unreg_server, Tid}, State) ->
Servers0 = State#state.servers,
case lists:keytake(Tid, #server.tid, Servers0) of
false ->
{noreply, State};
{value, Server, Servers2} ->
UnliveServers = [Server#server{pid = undefined}|State#state.unlive_servers],
{noreply, State#state{servers = Servers2,unlive_servers = UnliveServers}}
end;
handle_cast({task_over, Tid}, State) ->
NewState =
case lists:keytake(Tid, #server.tid, State#state.servers) of
{value, Server, Servers0} ->
Servers = add_server(Server#server{task_count = Server#server.task_count-1}, Servers0),
State#state{servers = Servers};
false ->
UnliveServers = lists:keydelete(Tid, #server.tid, State#state.unlive_servers),
State#state{unlive_servers = UnliveServers}
end,
{noreply, NewState};
handle_cast(_Request, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
new_server_ets(N) ->
EtsName = erlang:binary_to_atom(<<"ets_tasks_",(integer_to_binary(N))/binary>>, utf8),
ets:new(EtsName, [public,{keypos,#task.name},{write_concurrency, true},{read_concurrency, true}]).
add_server(E, [H|Es]) when E#server.task_count > H#server.task_count -> [H|add_server(E, Es)];
add_server(E, [H|_]=Set) when E#server.task_count =< H#server.task_count -> [E|Set];
add_server(E, []) -> [E].
do_add(Task, State) ->
case State#state.servers of
[Server|Servers] when Server#server.task_count < ?ONE_PROCESS_MAX_TASKS_COUNT ->
Tid = Server#server.tid,
case ets:insert_new(?ETS_NAME_TASK_INDEX, #task_index{name = Task#task.name,tid = Tid}) of
true ->
case ecrontab_server:add(Server#server.pid, Task) of
ok ->
ets:insert(Tid, Task),
NewServer = Server#server{task_count = Server#server.task_count + 1},
NewServers = add_server(NewServer, Servers),
{ok, State#state{servers = NewServers}};
Err ->
ets:delete(?ETS_NAME_TASK_INDEX, Task#task.name),
Err
end;
false ->
{error, task_exists}
end;
[] ->
{error, no_server_use}
end.
do_remove(Name, _Options, State) ->
case ets:lookup(?ETS_NAME_TASK_INDEX, Name) of
[TaskIndex] ->
ets:delete(?ETS_NAME_TASK_INDEX, Name),
Tid = TaskIndex#task_index.tid,
[Task] = ets:lookup(Tid, Name),
ets:delete(Tid, Name),
case lists:keytake(Tid, #server.tid, State#state.servers) of
{value, Server, Servers0} ->
ecrontab_server:remove(Server#server.pid, Task),
Servers = add_server(Server#server{task_count = Server#server.task_count-1}, Servers0),
{ok, State#state{servers = Servers}};
false ->
UnliveServers = lists:keydelete(Tid, #server.tid, State#state.unlive_servers),
{ok, State#state{unlive_servers = UnliveServers}}
end;
[] ->
{error, no_such_task}
end.