2
\$\begingroup\$

I wrote a some what "simple" module to retain ETS tables should the owner crash. I think it's small enough for review; big enough to make enough mistakes. Honestly I'm a hobbyist programmer aiming for production code. I will take any criticism to get me closer.

More logging? Use of spec()? Anything.

Thank you.

ets_manager.hrl

-define(MASTER_TABLE, etsmgr_master).

ets_supervisor.erl

-module(ets_supervisor).
-behaviour(supervisor).

-include("ets_manager.hrl").

-export([start_link/1, init/1]).

%% ====================================================================
%% API/Exposed functions
%% ====================================================================
-export([spawn_init/1]).

spawn_init(SupRef) ->
    register(ets_fallback, self()),
    monitor(process, SupRef),
    loop().

%% ====================================================================
%% Internal functions
%% ====================================================================

loop() ->
    receive
        {give_away,{?MASTER_TABLE, Pid}} ->
            try
                {registered_name, ets_manager} = erlang:process_info(Pid, registered_name),
                ets:give_away(?MASTER_TABLE, Pid, [])
            catch
                error:{badmatch, _} ->  logger:error("Illegal process (~p) attempting ETS Manager table ownership.",[Pid]),
                                        {error, badmatch};
                error:badarg -> gen_server:cast(ets_manager, initialize);
                Type:Reason -> logger:error("Unhandled catch -> ~p : ~p",[Type, Reason]),
                                {Type, Reason}
            end;
        {'DOWN',_,_,_,_} ->
            case proplists:get_value(owner,ets:info(?MASTER_TABLE)) =:= self() of
                true -> ets:delete(?MASTER_TABLE);
                false -> continue
            end,
            exit(shutdown);
        _ -> continue
    end,
    loop().


%% ====================================================================
%% Behavioural functions
%% ====================================================================

start_link([]) ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
    Pid = spawn(?MODULE, spawn_init, [self()]),

    {ok, { #{}, [

        % === ETS Manager: gen_server to not lose table data
        #{  id => ets_manager,
            start => {ets_manager, start_link, [Pid]}
        }
    ]}}.

ets_manager.erl

-module(ets_manager).
-behaviour(gen_server).

-include("ets_manager.hrl").

-export([start_link/1, init/1, handle_call/3, handle_cast/2, 
         handle_info/2, terminate/2, code_change/3]).

%% ====================================================================
%% API functions
%% ====================================================================
-export([request_table/1, create_table/2, create_table/3, update_pid/3]).

request_table(TableId) ->
    gen_server:call(?MODULE, {tbl_request, TableId}).

create_table(TableId, TblOpts) ->
    create_table(TableId, TblOpts, []).

create_table(TableId, TblOpts, HeirData) ->
    gen_server:call(?MODULE, {tbl_create, TableId, TblOpts, HeirData}).

update_pid(TableId, Pid, HeirData) ->
    case process_info(Pid, registered_name) of
        {registered_name, ?MODULE} ->
            ets:setopts(TableId, {heir, Pid, HeirData});
        _ ->
            {error, eperm}
    end.

%% ====================================================================
%% Behavioural functions
%% ====================================================================
start_link(Pid) when is_pid(Pid) -> gen_server:start_link({local, ?MODULE}, ?MODULE, Pid, []).


%% ====================================================================
%% Framework Functions
%% ====================================================================

%% init/1
%% ====================================================================
init(FallbackPID) ->
    FallbackPID ! {give_away,{?MASTER_TABLE, self()}},
   {ok, FallbackPID}.

%% handle_call/3
%% ====================================================================
handle_call({tbl_request, TableId}, {Pid, _}, FallbackPID) ->
    Me = self(),

    case ets:lookup(?MASTER_TABLE, TableId) of
    [{TableId, Me, Requestor, HeirData}] ->

        case process_info(FallbackPID, registered_name) of
            {registered_name, Requestor} ->
                ets:give_away(TableId, Pid, HeirData),
                {reply, {ok, TableId},FallbackPID};
            _ ->
                {reply, {error, eaccess}, FallbackPID}
        end;
    [] ->
        {reply, {error, einval}, FallbackPID};
    [{TableId, _, _, _}] ->
        {reply, {error, ebusy}, FallbackPID}
    end;

handle_call({tbl_create, TableId, TblOpts, HeirData}, {Pid, _}, FallbackPID) ->
    Opts = proplists:delete(heir, proplists:delete(named_table, TblOpts)),

    Requestor = 
        case process_info(Pid, registered_name) of
            {registered_name, Module} -> Module;
            _ -> []
        end,
        
    Reply =
        try
            ets:new(TableId,[named_table | [ {heir, self(), HeirData} | Opts]]),
            ets:insert(?MASTER_TABLE, {TableId, Pid, Requestor, HeirData}),
            ets:give_away(TableId, Pid, HeirData)
        catch
            _:_ -> 
                case ets:info(TableId) of
                    undefined -> continue;
                    _ -> ets:delete(TableId)
                end,
                ets:delete(?MASTER_TABLE, TableId),
                {error, ecanceled}
        end,

    {reply, Reply, FallbackPID}.



%% handle_info/2
%% ====================================================================
handle_info({'ETS-TRANSFER', ?MASTER_TABLE, _, _}, FallbackPID) ->
    [{?MODULE, OldPid}] = ets:lookup(?MASTER_TABLE, ?MODULE),
    ets:foldl(fun xfer_state/2,OldPid,?MASTER_TABLE),
    {noreply, FallbackPID};

handle_info({'ETS-TRANSFER', TableId, _, _}, FallbackPID) ->
    ets:update_element(?MASTER_TABLE, TableId,{2, self()}),
    {noreply, FallbackPID}.

%% handle_cast/2
%% ====================================================================
handle_cast(initialize, FallbackPID) ->
    ?MASTER_TABLE = ets:new(?MASTER_TABLE,[named_table, set, private, {heir, FallbackPID, []}]),
    ets:insert(?MASTER_TABLE, {?MODULE, self()}),
    {noreply, FallbackPID}.


%% Placeholders
%% ====================================================================
terminate(_, _) ->
    ok.

code_change(_, FallbackPID, _) ->
    {ok, FallbackPID}.

%% ====================================================================
%% Internal Functions
%% ====================================================================

xfer_state({TableId, OldPid, _, _}, OldPid) ->
    ets:delete(?MASTER_TABLE, TableId), OldPid;
xfer_state({TableId, Pid, _, HeirData}, OldPid) ->
    Pid ! {'ETS-NEWMANAGER', self(), TableId, HeirData}, OldPid;
xfer_state({?MODULE, OldPid}, OldPid) ->
    ets:insert(?MASTER_TABLE, {?MODULE, self()}), OldPid.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.