%% @copyright 2014, Takeru Ohta %% %% @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.