Packages

Throttling library

Current section

Files

Jump to
choke src throttle_counter.erl
Raw

src/throttle_counter.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_counter).
%% Includes
-behaviour(gen_server).
%% Exports
-export([start_link/5, start_link_name/5, check/1, check/2,
peek/1, restore/1, stop/1, get/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3, terminate/2]).
%% Module records.
-record(state, {id :: atom(),
parent :: pid(),
limit :: integer(),
count :: integer(),
timeout :: integer(),
die :: integer(),
blocked :: boolean()}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Public functions
%% @doc Create the counter gen_server process, the function receive the Id
%% of the counter, the Limit of times you can call the check proces in a
%% time definite by the Timeout.
-spec start_link_name(Id :: atom(), Parent :: pid(), Limit :: integer(),
Timeout :: atom() | integer(), Die :: integer()) ->
{ok, pid()} | ignore | {error, term()}.
start_link_name(Id, Parent, Limit, Timeout, Die) ->
gen_server:start_link({local, Id}, ?MODULE, {Id, Parent, Limit, Timeout, Die}, []).
%% @doc Create the counter gen_server process, the function receive the Id
%% of the counter, the Limit of times you can call the check proces in a
%% time definite by the Timeout.
-spec start_link(Id :: atom(), Parent :: pid(), Limit :: integer(),
Timeout :: atom() | integer(), Die :: integer()) ->
{ok, pid()} | ignore | {error, term()}.
start_link(Id, Parent, Limit, Timeout, Die) ->
gen_server:start_link(?MODULE, {Id, Parent, Limit, Timeout, Die}, []).
%% @doc Update the internal counter 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() | pid()) -> {ok, integer()} | {warning | error, integer(), integer()}.
check(Id) ->
check(Id, [{strict, false}]).
%% @doc Update the internal counter 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() | pid(), [{atom(), boolean()}]) -> {ok, integer()} | {warning | error, integer(), integer()}.
check(Id, [{strict, true}]) ->
gen_server:call(Id, update_counter_strict);
check(Id, [{strict, false}]) ->
gen_server:call(Id, update_counter).
%% @doc Get the internal counter 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() | pid()) -> {ok, integer()} | {warning | error, integer(), integer()}.
peek(Id) ->
gen_server:call(Id, get_counter).
%% @doc Restore the internal counter and return a pair {ok, count}.
-spec restore(atom() | pid()) -> {ok, integer()}.
restore(Id) ->
gen_server:call(Id, restore_counter).
%% @doc Stop the counter gen_server process independently of the state.
-spec stop(atom() | pid()) -> ok.
stop(Id) ->
gen_server:call(Id, stop).
%% @doc Get information about a Counter
-spec get(atom() | pid()) -> #{id => atom(),
count => integer(),
blocked => boolean()}.
get(Id) ->
gen_server:call(Id, get).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% gen_server functions
%% @doc Constructor of the counter gen_server process.
init({Id, Parent, Limit, Timeout, Die}) ->
process_flag(trap_exit, true),
{ok, #state{id = Id, parent = Parent, limit = Limit, count = 0,
timeout = Timeout, die = Die, blocked = false}, Die}.
%% @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, _From, State) ->
Limit = State#state.limit,
Count = State#state.count,
Die = State#state.die,
Blocked = State#state.blocked,
if Limit > Count ->
erlang:send_after(State#state.timeout, self(), sub_counter),
UpdateCount = Count + 1,
NewState = if Blocked ->
State#state{count = UpdateCount, blocked = false};
true ->
State#state{count = UpdateCount}
end,
{reply, {ok, UpdateCount}, NewState, Die};
not Blocked ->
NewState = State#state{blocked = true},
{reply, {warning, Limit, State#state.timeout}, NewState, Die};
true ->
{reply, {error, Limit, State#state.timeout}, State, Die}
end;
%% @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_strict, _From, State) ->
Limit = State#state.limit,
Count = State#state.count,
Die = State#state.die,
Blocked = State#state.blocked,
%% Update the counter
erlang:send_after(State#state.timeout, self(), sub_counter),
UpdateCount = Count + 1,
if Limit > Count ->
NewState = if Blocked ->
State#state{count = UpdateCount, blocked = false};
true ->
State#state{count = UpdateCount}
end,
{reply, {ok, UpdateCount}, NewState, Die};
not Blocked ->
NewState = State#state{blocked = true, count = UpdateCount},
{reply, {warning, Limit, State#state.timeout}, NewState, Die};
true ->
NewState = State#state{count = UpdateCount},
{reply, {error, Limit, State#state.timeout}, NewState, Die}
end;
%% @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, _From, State) ->
Limit = State#state.limit,
Count = State#state.count,
Die = State#state.die,
Blocked = State#state.blocked,
if Limit > Count ->
{reply, {ok, Count + 1}, State, Die};
not Blocked ->
{reply, {warning, Limit, State#state.timeout}, State, Die};
true ->
{reply, {error, Limit, State#state.timeout}, State, Die}
end;
%% @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, _From, State) ->
{reply, {ok, 0}, State#state{count = 0}, State#state.die};
%% @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) ->
Data = #{id => State#state.id,
count => State#state.count,
blocked => State#state.blocked},
{reply, Data, State, State#state.die};
%% @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 unddefine"},
{reply, Reply, State, State#state.die}.
%% @doc Process the timeout request.
handle_info(timeout, State) ->
{stop, normal, State};
%% @doc Restore the internal counter also at the end set the die timeout of
%% the process if not receive any call.
handle_info(restore_counter, State) ->
{noreply, State#state{count = 0}, State#state.die};
%% @doc Restore the internal counter also at the end set the die timeout of
%% the process if not receive any call.
handle_info(sub_counter, State) ->
Count = State#state.count,
{noreply, State#state{count = Count - 1}, State#state.die};
%% @doc Process the undefine call.
handle_info(_Message, State) ->
{noreply, State, State#state.die}.
%% @doc Get the state of the counter
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% @doc Stop the counter independently the type.
terminate(_, State) ->
Id = State#state.id,
Parent = State#state.parent,
throttle_context:kick(Parent, Id, self()),
ok.
%% @doc Process the undefine call.
handle_cast(_Message, State) ->
{noreply, State, State#state.die}.