blob: da0ac6bfa54857541321d2d4097dc6122425a8c4 [file] [log] [blame]
%% @copyright 2014, Takeru Ohta <phjgt308@gmail.com>
%%
%% @doc Local Name Server
%% @private
-module(local_name_server).
-behaviour(gen_server).
%%----------------------------------------------------------------------------------------------------------------------
%% Exported API
%%----------------------------------------------------------------------------------------------------------------------
-export([start_link/1]).
-export([register_name/3]).
-export([unregister_name/2]).
-export([whereis_name/2]).
-export([which_processes/1, which_processes/2]).
%%----------------------------------------------------------------------------------------------------------------------
%% 'gen_server' Callback API
%%----------------------------------------------------------------------------------------------------------------------
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
%%----------------------------------------------------------------------------------------------------------------------
%% Records
%%----------------------------------------------------------------------------------------------------------------------
-record(state,
{
name :: local:name_server_name(),
pid_to_name = gb_trees:empty() :: gb_trees:tree(pid(), local:process_name())
}).
%%----------------------------------------------------------------------------------------------------------------------
%% Exported Functions
%%----------------------------------------------------------------------------------------------------------------------
%% @doc Starts a new local name server process
-spec start_link(local:name_server_name()) -> {ok, pid()} | {error, Reason} when
Reason :: {already_started, pid()}.
start_link(Name) ->
gen_server:start_link({local, Name}, ?MODULE, [Name], []).
%% @see local:register_name/2
-spec register_name(local:name_server_name(), local:process_name(), pid()) -> yes | no.
register_name(Server, Name, Pid) ->
gen_server:call(Server, {register_name, {Name, Pid}}).
%% @see local:unregister_name/2
-spec unregister_name(local:name_server_name(), local:process_name()) -> ok.
unregister_name(Server, Name) ->
gen_server:call(Server, {unregister_name, Name}).
%% @see local:whereis_name/2
-spec whereis_name(local:name_server_name(), local:process_name()) -> pid() | undefined.
whereis_name(Server, Name) ->
try ets:lookup(Server, Name) of
[] -> undefined;
[{_, Pid}] -> Pid
catch
_:_ -> undefined
end.
%% @see local:which_processes/1
-spec which_processes(local:name_server_name()) -> [{local:process_name(), pid()}].
which_processes(Server) ->
try
ets:tab2list(Server)
catch
_:_ -> []
end.
%% @see local:which_processes/2
-spec which_processes(local:name_server_name(), ets:match_pattern()) -> [{local:process_name(), pid()}].
which_processes(Server, ProcNamePattern) ->
try
ets:match_object(Server, {ProcNamePattern, '_'})
catch
_:_ -> []
end.
%%----------------------------------------------------------------------------------------------------------------------
%% 'gen_server' Callback Functions
%%----------------------------------------------------------------------------------------------------------------------
%% @private
init([Name]) ->
_ = process_flag(trap_exit, true),
State =
#state{
name = Name
},
_ = ets:new(Name, [named_table, protected, set, {read_concurrency, true}]),
{ok, State}.
%% @private
handle_call({register_name, Arg}, _From, State) ->
handle_register_name(Arg, State);
handle_call({unregister_name, Arg}, _From, State) ->
handle_unregister_name(Arg, State);
handle_call(_Request, _From, State) ->
{noreply, State}.
%% @private
handle_cast(_Request, State) ->
{noreply, State}.
%% @private
handle_info({'EXIT', Pid, Reason}, State) ->
handle_exit(Pid, Reason, State);
handle_info(_Info, State) ->
{noreply, State}.
%% @private
terminate(_Reason, _State) ->
ok.
%% @private
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%----------------------------------------------------------------------------------------------------------------------
%% Internal Functions
%%----------------------------------------------------------------------------------------------------------------------
-spec handle_register_name(Arg, #state{}) -> {reply, Result, #state{}} when
Arg :: {local:process_name(), pid()},
Result :: yes | no.
handle_register_name({Name, Pid}, State) ->
case ets:lookup(State#state.name, Name) =:= [] andalso not gb_trees:is_defined(Pid, State#state.pid_to_name) of
false -> {reply, no, State};
true ->
true = ets:insert(State#state.name, {Name, Pid}),
true = link(Pid),
PidToName = gb_trees:insert(Pid, Name, State#state.pid_to_name),
{reply, yes, State#state{pid_to_name = PidToName}}
end.
-spec handle_unregister_name(local:process_name(), #state{}) -> {reply, ok, #state{}}.
handle_unregister_name(Name, State) ->
case ets:lookup(State#state.name, Name) of
[] -> {reply, ok, State};
[{_, Pid}] ->
true = local_lib:unlink_and_flush(Pid),
true = ets:delete(State#state.name, Name),
PidToName = gb_trees:delete(Pid, State#state.pid_to_name),
{reply, ok, State#state{pid_to_name = PidToName}}
end.
-spec handle_exit(pid(), term(), #state{}) -> {noreply, #state{}} | {stop, term(), #state{}}.
handle_exit(Pid, Reason, State) ->
case gb_trees:lookup(Pid, State#state.pid_to_name) of
none -> {stop, Reason, State};
{value, Name} ->
true = ets:delete(State#state.name, Name),
PidToName = gb_trees:delete(Pid, State#state.pid_to_name),
{noreply, State#state{pid_to_name = PidToName}}
end.