Current section
Files
Jump to
Current section
Files
src/throttle_context.erl
%% @author Coowry Developers Team
%%
%% @copyright 2018 Coowry Ltd. - All rights reserved.
%%
%% See LICENSE.txt file for detailed information.
%%
%% @doc This module is an individula counter process,
%% this proccess will be the child of a context supervisor.
-module(throttle_context).
%% Includes
-behaviour(gen_server).
%% Exports
-export([start_link/2, check/2, check/3, peek/2, restore/2,
stop/1, kick/3, get/2, get/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3, terminate/2]).
%% Module records.
-record(counter, {limit :: integer(),
timeout :: integer()}).
-record(state, {id :: atom(),
child :: map(),
counterInit = #counter{}}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Public functions
%% @doc Create the context gen_server process, the function receive the Id
%% of the context receive an Id and the parameters to create throttle_counter.
-spec start_link(Id :: atom(),
CounterInit :: {integer(), integer()}) -> {ok, pid()} | ignore | {error, term()}.
start_link(Id, CounterInit) ->
gen_server:start_link({local, Id}, ?MODULE, {Id, CounterInit}, []).
%% @doc Update the counter of the CounterId and return a pair {ok, count} if you are
%% between the limit or {error | warning, counter, timeout} if you exceed the limit.
-spec check(atom(), any()) -> {ok, integer()} | {warning | error, integer(), integer()}.
check(ContextId, CounterId) ->
gen_server:call(ContextId, {update_counter, CounterId, [{strict, false}]}).
%% @doc Update the counter of the CounterId and return a pair {ok, count} if you are
%% between the limit or {error | warning, counter, timeout} if you exceed the limit.
%% Also include a option parameter.
-spec check(atom(), any(), [{atom(), boolean()}]) -> {ok, integer()} | {warning | error, integer(), integer()}.
check(ContextId, CounterId, Options) ->
gen_server:call(ContextId, {update_counter, CounterId, Options}).
%% @doc Get the counter of the CounterId and return a pair {ok, count} if you are
%% between the limit or {error | warning, counter, timeout} if you exceed the limit.
-spec peek(atom(), any()) -> {ok, integer()} | {warning | error, integer(), integer()}.
peek(ContextId, CounterId) ->
gen_server:call(ContextId, {get_counter, CounterId}).
%% @doc Restore the counter of the CounterId and return a pair {ok, count}.
-spec restore(atom(), any()) -> {ok, integer()}.
restore(ContextId, CounterId) ->
gen_server:call(ContextId, {restore_counter, CounterId}).
%% @doc Stop the counter gen_server process independently of the state.
-spec stop(atom()) -> ok.
stop(Id) ->
gen_server:call(Id, stop).
%% @doc Delete a counter.
-spec kick(pid(), atom(), pid()) -> ok.
kick(Pid, Id, CounterPid) ->
gen_server:cast(Pid, {delete_counter, Id, CounterPid}).
%% @doc Get information about a Counter inside of a Context.
-spec get(atom(), any()) -> #{id => atom(),
count => integer(),
blocked => boolean()}.
get(ContextId, CounterId) ->
gen_server:call(ContextId, {counter, CounterId}).
%% @doc Get information about all the Counters inide of a context.
-spec get(atom()) -> [#{id => atom(),
count => integer(),
blocked => boolean()}].
get(Id) ->
gen_server:call(Id, get).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% gen_server functions
%% @doc Constructor of the context gen_server process.
init({Id, {LimitCounter, TimeoutCounter}}) ->
process_flag(trap_exit, false),
CounterInit = #counter{limit = LimitCounter,
timeout = TimeoutCounter},
{ok, #state{id = Id, counterInit = CounterInit, child = #{}}}.
%% @doc Update the internal counter and return a pair {ok, count} if you are
%% between the limit or {error, timeout} if you exceed the limit. Also at the
%% end set the die timeout of the process if not receive any call.
handle_call({update_counter, CounterId, Options}, _From, State) ->
{NewState, Pid} = get_child(State, CounterId),
Reply = throttle_counter:check(Pid, Options),
{reply, Reply, NewState};
%% @doc Get the internal counter and return a pair {ok, count} if you are
%% between the limit or {error, timeout} if you exceed the limit. Also at the
%% end set the die timeout of the process if not receive any call.
handle_call({get_counter, CounterId}, _From, State) ->
{NewState, Pid} = get_child(State, CounterId),
Reply = throttle_counter:peek(Pid),
{reply, Reply, NewState};
%% @doc Restore the internal counter also at the end set the die timeout of
%% the process if not receive any call.
handle_call({restore_counter, CounterId}, _From, State) ->
{NewState, Pid} = get_child(State, CounterId),
Reply = throttle_counter:restore(Pid),
{reply, Reply, NewState};
%% @doc Restore the internal counter also at the end set the die timeout of
%% the process if not receive any call.
handle_call({counter, CounterId}, _From, State) ->
{NewState, Pid} = get_child(State, CounterId),
Reply = throttle_counter:get(Pid),
{reply, Reply, NewState};
%% @doc Restore the internal counter also at the end set the die timeout of
%% the process if not receive any call.
handle_call(get, _From, State) ->
Childs = maps:values(State#state.child),
Reply = lists:map(fun(Pid) ->
throttle_counter:get(Pid)
end, Childs),
{reply, Reply, State};
%% @doc Stop the counter gen_server process independently of the state.
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
%% @doc Process the undefine call.
handle_call(Command, _, State) ->
Reply = {error, "The " ++ atom_to_list(Command) ++ " is undefine"},
{reply, Reply, State}.
%% @doc Process the undefine call.
handle_info(_Message, State) ->
{noreply, State}.
%% @doc Get the state of the counter
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% @doc Stop the counter independently the type.
terminate(_, _State) ->
ok.
%% @doc
handle_cast({delete_counter, CounterId, CounterPid}, State) ->
Map = State#state.child,
unlink(CounterPid),
{noreply, State#state{child = maps:without([CounterId], Map)}};
%% @doc Process the undefine call.
handle_cast(_Message, State) ->
{noreply, State}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Private functions
get_child(State, ChildId) ->
Map = State#state.child,
GetPid = case maps:is_key(ChildId, Map) of
false ->
Init = State#state.counterInit,
{ok, Pid} = throttle_counter:start_link(ChildId, self(), Init#counter.limit, Init#counter.timeout, 3 * Init#counter.timeout),
Pid;
_ -> Pid = maps:get(ChildId, Map),
case process_info(Pid) of
undefined -> Init = State#state.counterInit,
{ok, Pid1} = throttle_counter:start_link(ChildId, self(), Init#counter.limit, Init#counter.timeout, 3 * Init#counter.timeout),
Pid1;
_ -> Pid
end
end,
{State#state{child = Map#{ChildId => GetPid}}, GetPid}.