%%% The core of the app: the server in charge of tracking processes.
-module(regis_server).
-behaviour(gen_server).
-export([start_link/0, stop/0, register/2, unregister/1, whereis/1,
get_names/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
%% We have two indexes: one by name and one by pid, for
%% MAXIMUM SPEED (not actually measured).
-record(state, {pid, name}).
%%%%%%%%%%%%%%%%%
%%% INTERFACE %%%
%%%%%%%%%%%%%%%%%
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:call(?MODULE, stop).
%% Give a name to a process
register(Name, Pid) when is_pid(Pid) ->
gen_server:call(?MODULE, {register, Name, Pid}).
%% Remove the name from a process
unregister(Name) ->
gen_server:call(?MODULE, {unregister, Name}).
%% Find the pid associated with a process
whereis(Name) ->
gen_server:call(?MODULE, {whereis, Name}).
%% Find all the names currently registered.
get_names() ->
gen_server:call(?MODULE, get_names).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% GEN_SERVER CALLBACKS %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([]) ->
%% Using gb_trees to store items. gb_trees generally have
%% good overall performance.
{ok, #state{pid = gb_trees:empty(),
name = gb_trees:empty()}}.
handle_call({register, Name, Pid}, _From, S = #state{pid=P, name=N}) ->
case {gb_trees:is_defined(Pid, P), gb_trees:is_defined(Name, N)} of
{true, _} ->
{reply, {error, already_named}, S};
{_, true} ->
{reply, {error, name_taken}, S};
{false, false} ->
Ref = erlang:monitor(process, Pid),
{reply, ok, S#state{pid=gb_trees:insert(Pid, {Name,Ref}, P),
name=gb_trees:insert(Name, {Pid,Ref}, N)}}
end;
handle_call({unregister, Name}, _From, S = #state{pid=P, name=N}) ->
case gb_trees:lookup(Name, N) of
{value, {Pid,Ref}} ->
erlang:demonitor(Ref, [flush]),
{reply, ok, S#state{pid=gb_trees:delete(Pid, P),
name=gb_trees:delete(Name, N)}};
none ->
{reply, ok, S}
end;
handle_call({whereis, Name}, _From, S = #state{name=N}) ->
case gb_trees:lookup(Name, N) of
{value, {Pid,_}} ->
{reply, Pid, S};
none ->
{reply, undefined, S}
end;
handle_call(get_names, _From, S = #state{name=N}) ->
{reply, gb_trees:keys(N), S};
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call(_Event, _From, State) ->
{noreply, State}.
handle_cast(_Event, State) ->
{noreply, State}.
handle_info({'DOWN', Ref, process, Pid, _Reason}, S = #state{pid=P,name=N}) ->
{value, {Name, Ref}} = gb_trees:lookup(Pid, P),
{noreply, S#state{pid = gb_trees:delete(Pid, P),
name = gb_trees:delete(Name, N)}};
handle_info(_Event, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.