Skip to content

Instantly share code, notes, and snippets.

@bsingh
Last active February 1, 2018 03:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bsingh/ed2f496b07d0b29f0698fca5b5f1619c to your computer and use it in GitHub Desktop.
Save bsingh/ed2f496b07d0b29f0698fca5b5f1619c to your computer and use it in GitHub Desktop.
line_mgr
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([start/0, start_link/0, get_line/0, get_line/1, free_line/1, dump/0, get_lines_info/0]).
-export([get_local_fs_node/0, get_fs_node/0]).
-include("records.hrl").
-record(data, {
total_lines :: integer(), % Represents total lines
used_lines :: integer(), % Represents used lines; for tracking purpose only
current_index :: integer(), % Temporary index for traversal
order :: atom(), % forward or reverse
lines_map :: dict(), % map type structuce to maintain all lines
%% Media Servers
fs_nodes :: [#fs_node{}] % storing multple fs_node
}).
-define(LOG(Level, Format, Args),
log4erl:log(Level, "line_mgr: " ++ Format, Args)).
% -define(DEBUG, debug). % log4erl log level: debug, info, error etc...
%%-------------------------------------------------------------------
%% Public API
%%-------------------------------------------------------------------
% start not in a supervisor tree
start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
%% @doc start in a supervisor tree
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:call(?MODULE, stop).
state() ->
gen_server:call(?MODULE, state).
dump() ->
gen_server:call(?MODULE, dump).
%% @spec get_line(Order) -> Result
%-spec(get_line/1 :: () -> {ok, integer()} | {error, integer()}).
%- Input parameter is order - forward/reverse
%- This is handle timeout also
get_line() ->
get_line(forward).
get_line(Order) ->
case catch gen_server:call(?MODULE, {get_line, Order}) of
{ok, Result} -> {ok, Result};
{'EXIT', {timeout, _Junk}} ->
?LOG(debug, "Got timeout in get_line and will try again", []),
get_line(Order);
Error ->
?LOG(error, "Unknown error in get_line: ~p", [Error]),
Error
end.
%% @spec free_line(LineNum) -> noreply
%% This is async function
free_line(LineNum) ->
gen_server:cast(?MODULE, {free_line, LineNum}).
%% crash test
crash() ->
gen_server:call(?MODULE, crash).
get_local_fs_node() ->
gen_server:call(?MODULE, get_local_fs_node).
get_fs_node() ->
gen_server:call(?MODULE, get_fs_node).
%%-------------------------------------------------------------------
%% gen_server call backs
%%-------------------------------------------------------------------
init([]) ->
process_flag(trap_exit, true),
{ok, ServerID} = tconfig:get_env(server_id),
FSNodes = get_fs_nodes_from_config(),
% wait for initi done where it ensures DB connection
sys_mgr:wait_for_init_done(),
[TelSystem] = m_sp:getTelSystem(ServerID),
MaxLines = TelSystem#telsystem.'MaxLines',
?LOG(init, "starting line_mgr with total lines ~p", [MaxLines]),
Data = #data{
total_lines = MaxLines,
used_lines = 0,
current_index = 1, % just for assignment map and runtime use
order = forward, % forward or reverse
lines_map = dict:new(),
fs_nodes = FSNodes
},
Data1 = init_lines_map(Data),
?LOG(init, " init done with Totallines: ~p, CurrentIndex: ~p, UsedLines; ~p",
[Data1#data.total_lines, Data1#data.current_index, Data1#data.used_lines]),
{ok, Data1}.
handle_call({get_line, _Order}, {_Pid, _Ref}, Data) when Data#data.total_lines =:= Data#data.used_lines ->
?LOG(warn, "get_line All lines in use Totallines: ~p, Usedlines: ~p",
[Data#data.total_lines, Data#data.used_lines]),
{reply, {error, -1}, Data};
handle_call({get_line, Order}, {_Pid, _Ref}, Data) ->
case Order of
reverse ->
Data1 = Data#data{current_index = Data#data.total_lines, order = reverse}; % setting last item
_ ->
Data1 = Data#data{current_index = 1, order = forward} % setting 1 so can be used as indexed count
end,
?LOG(info, "get_line starts while Totallines: ~p, Usedlines: ~p, CurrentIndex: ~p",
[Data1#data.total_lines, Data1#data.used_lines, Data1#data.current_index]),
LineIndex = find_free_index(Data1),
case LineIndex of
-1 ->
?LOG(warn, "get_line no free index found in map", []),
{reply, {error, -1}, Data1};
_ ->
Data2 = Data1#data{lines_map = dict:store(LineIndex, 1, Data1#data.lines_map), used_lines = Data1#data.used_lines + 1},
{reply, {ok, LineIndex}, Data2}
end;
handle_call(stop, _From, Data) ->
{stop, normal, stopped, Data};
handle_call(crash, _From, _Data) ->
crash_with_bad_return_value;
handle_call(state, _From, Data) ->
{reply, Data, Data};
handle_call(dump, _From, Data) ->
?LOG(dump, "Totallines: ~p, Usedlines: ~p, FreeLines: ~p, CurrentIndex: ~p, MapSize: ~p",
[Data#data.total_lines, Data#data.used_lines, Data#data.total_lines - Data#data.used_lines,
Data#data.current_index, dict:size(Data#data.lines_map)]),
?LOG(dump, "fs_nodes: ~p", [Data#data.fs_nodes]),
{reply, ok, Data};
%% local node returns first node in the list
%% note - it is assumed that first node always be local node in config
handle_call(get_local_fs_node, _From, Data) ->
{fs_node, _ID, _Status, NodeName} = hd(Data#data.fs_nodes),
{reply, {ok, NodeName}, Data};
handle_call(get_fs_node, _From, Data) ->
{NodeName, Data1} = get_fs_node(Data),
{reply, {ok, NodeName}, Data1};
handle_call(_Request, _From, Data) ->
?LOG(debug, "call ~p, ~p, ~p.", [_Request, _From, Data]),
{reply, ok, Data}.
%% ------ handle_cast functioons -----
% check for invalid line upper bound
handle_cast({free_line, LineNum}, Data) when LineNum > Data#data.total_lines ->
?LOG(warn, "free_line Trying to free invalid line LineNum: ~p, Totallines: ~p, Usedlines: ~p",
[LineNum, Data#data.total_lines, Data#data.used_lines]),
{noreply, Data};
% check for invalid line lower bound
handle_cast({free_line, LineNum}, Data) when LineNum < 1 ->
?LOG(warn, "free_line Trying to free invalid line LineNum: ~p, Totallines: ~p, Usedlines: ~p",
[LineNum, Data#data.total_lines, Data#data.used_lines]),
{noreply, Data};
% free valid line now
handle_cast({free_line, LineNum}, Data) ->
?LOG(info, "free_line: Before freeing Totallines: ~p, Usedlines: ~p, ToBeFreedLineNum: ~p",
[Data#data.total_lines, Data#data.used_lines, LineNum]),
% check if line is already freed or not
LinesMap = Data#data.lines_map,
case catch dict:fetch(LineNum, LinesMap) of
0 -> % 0 means free
?LOG(warn, "free_line: This line ~p is already freed",[LineNum]),
{noreply, Data};
Freed when is_integer(Freed) ->
Data1 = Data#data{lines_map = dict:store(LineNum, 0, Data#data.lines_map), used_lines = Data#data.used_lines - 1},
{noreply, Data1};
_Exception ->
{noreply, Data}
end;
handle_cast(_Msg, Data) ->
?LOG(debug, "cast ~p, ~p.", [_Msg, Data]),
{noreply, Data}.
handle_info(_Info, Data) ->
?LOG(debug, "info ~p, ~p.", [_Info, Data]),
{noreply, Data}.
terminate(_Reason, _Data) ->
?LOG(info, "terminate ~p", [_Reason]),
ok.
code_change(_OldVsn, Data, _Extra) ->
?LOG(debug, "code_change ~p, ~p, ~p", [_OldVsn, Data, _Extra]),
{ok, Data}.
%%-------------------------------------------------------------------
%% Private functions
%%-------------------------------------------------------------------
init_lines_map(Data) when Data#data.current_index =< Data#data.total_lines ->
% ?LOG(debug, "Adding new line number ~p", [Data#data.current_index]),
Data1 = Data#data{lines_map = dict:store(Data#data.current_index, 0, Data#data.lines_map), current_index = Data#data.current_index + 1},
init_lines_map(Data1);
init_lines_map(Data) ->
?LOG(info, "Done initializing lines_map with total lines ~p, CurrentIndex ~p", [Data#data.total_lines, Data#data.current_index]),
Data.
find_free_index(Data) when Data#data.current_index > Data#data.total_lines->
?LOG(warn, "find_free_line:search exhausted can't find any free line index", []),
-1;
find_free_index(Data) when Data#data.current_index < 1 ->
?LOG(warn, "find_free_line:search exhausted reaching to index 1 and can't find any free line index", []),
-1;
% just return free line index
find_free_index(Data) ->
IndexVal = dict:fetch(Data#data.current_index, Data#data.lines_map),
%?LOG(debug, "find_free_line:LineIndex ~p has value ~p", [Data#data.current_index, IndexVal]),
case IndexVal of
0 -> % found free
?LOG(debug, "find_free_line:Found line number ~p free", [Data#data.current_index]),
Data#data.current_index;
_ -> % busy and is 1 hopefully
%?LOG(debug, "find_free_line:line number ~p not free so try next", [Data#data.current_index]),
case Data#data.order of
forward ->
Data1 = Data#data{current_index = Data#data.current_index + 1};
reverse ->
Data1 = Data#data{current_index = Data#data.current_index - 1}
end,
find_free_index(Data1)
end.
get_lines_info() ->
try state() of
Data ->
"Total:" ++ integer_to_list(Data#data.total_lines) ++
",Used:" ++ integer_to_list(Data#data.used_lines) ++
",Free:" ++ integer_to_list(Data#data.total_lines - Data#data.used_lines)
catch
_:Reason ->
{struct, [{error, list_to_binary(io_lib:format("~p", [Reason]))}]}
end.
%%-------------------------------------------------------------------
%% Test codes
%%-------------------------------------------------------------------
get_line_test() ->
get_line().
test_get_line(0) ->
ok;
test_get_line(N) ->
T = get_line(),
io:format("~p~n", [T]),
test_get_line(N-1).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment