Last active
February 1, 2018 03:50
-
-
Save bsingh/ed2f496b07d0b29f0698fca5b5f1619c to your computer and use it in GitHub Desktop.
line_mgr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-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