├── .gitignore ├── src ├── etsgive.erl ├── etsgive.app.src ├── etsgive_app.erl ├── etsgive_sup.erl ├── etsgive_srv.erl └── etsgive_mgr.erl └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | *.beam 3 | ebin 4 | doc 5 | *.swp 6 | erl_crash.dump 7 | log 8 | deps 9 | -------------------------------------------------------------------------------- /src/etsgive.erl: -------------------------------------------------------------------------------- 1 | -module(etsgive). 2 | 3 | -export([start/0]). 4 | 5 | start() -> 6 | application:start(?MODULE). 7 | -------------------------------------------------------------------------------- /src/etsgive.app.src: -------------------------------------------------------------------------------- 1 | {application, etsgive, 2 | [ 3 | {description, "ETS Give"}, 4 | {vsn, "0.1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {mod, { etsgive_app, []}}, 11 | {env, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/etsgive_app.erl: -------------------------------------------------------------------------------- 1 | -module(etsgive_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | etsgive_sup:start_link(). 14 | 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /src/etsgive_sup.erl: -------------------------------------------------------------------------------- 1 | -module(etsgive_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% Supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% Helper macro for declaring children of supervisor 12 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 13 | 14 | %% =================================================================== 15 | %% API functions 16 | %% =================================================================== 17 | 18 | start_link() -> 19 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 20 | 21 | %% =================================================================== 22 | %% Supervisor callbacks 23 | %% =================================================================== 24 | 25 | init([]) -> 26 | Child1 = ?CHILD(etsgive_srv, worker), 27 | Child2 = ?CHILD(etsgive_mgr, worker), 28 | {ok, { {one_for_one, 5, 10}, [Child1, Child2]} }. 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Demonstration of [Don't lose your ETS Tables](http://steve.vinoski.net/blog/2011/03/23/dont-lose-your-ets-tables/) 2 | 3 | ---- 4 | ## Let It Crash...Except When You Shouldn't 5 | 6 | > When you create an ets table you can also name a process to inherit the table should the creating process die 7 | 8 | ---- 9 | ### Example Session 10 | 11 | > rebar co && erl -pz ebin -s etsgive 12 | 13 | Eshell V5.9.2 (abort with ^G) 14 | 1> MGR(<0.38.0>) -> SRV(<0.37.0>) getting TableId: 16400 15 | etsgive_srv:count(). 16 | ok 17 | Counter: 1 18 | 2> etsgive_srv:count(). 19 | ok 20 | 2> etsgive_srv:count(). 21 | Counter: 2 22 | ok 23 | 3> etsgive_srv:count(). 24 | Counter: 3 25 | ok 26 | 4> etsgive_srv:count(). 27 | Counter: 4 28 | ok 29 | 5> etsgive_srv:count(). 30 | Counter: 5 31 | ok 32 | 6> exit(whereis(etsgive_srv), kill). 33 | true 34 | 7> Warning TableId: 16400 OwnerPid: <0.37.0> is dying 35 | SRV(<0.37.0>) => MGR(<0.38.0>) handing TableId: 16400 36 | SRV(<0.37.0>) !! is now dead, farewell TableId: 16400 37 | MGR(<0.38.0>) -> SRV(<0.44.0>) getting TableId: 16400 38 | 7> etsgive_srv:count(). 39 | ok 40 | Counter: 6 41 | 8> etsgive_srv:count(). 42 | ok 43 | Counter: 7 44 | 9> etsgive_srv:die(). 45 | ok 46 | 10> Warning TableId: 16400 OwnerPid: <0.44.0> is dying 47 | SRV(<0.44.0>) => MGR(<0.38.0>) handing TableId: 16400 48 | 49 | =ERROR REPORT==== 24-Apr-2013::11:01:48 === 50 | ** Generic server etsgive_srv terminating 51 | ** Last message in was {'$gen_cast',die} 52 | ** When Server state == {state,true,16400} 53 | ** Reason for termination == 54 | ** killed 55 | SRV(<0.44.0>) !! is now dead, farewell TableId: 16400 56 | MGR(<0.38.0>) -> SRV(<0.48.0>) getting TableId: 16400 57 | 10> etsgive_srv:count(). 58 | Counter: 8 59 | ok 60 | 11> [ exit(whereis(etsgive_srv), kill) || _ <- lists:seq(1, 20) ]. 61 | [true,true,true,true,true,true,true,true,true,true,true, 62 | true,true,true,true,true,true,true,true,true] 63 | 12> Warning TableId: 16400 OwnerPid: <0.48.0> is dying 64 | SRV(<0.48.0>) => MGR(<0.38.0>) handing TableId: 16400 65 | SRV(<0.48.0>) !! is now dead, farewell TableId: 16400 66 | MGR(<0.38.0>) -> SRV(<0.51.0>) getting TableId: 16400 67 | 12> etsgive_srv:count(). 68 | ok 69 | 13> Counter: 9 70 | -------------------------------------------------------------------------------- /src/etsgive_srv.erl: -------------------------------------------------------------------------------- 1 | -module(etsgive_srv). 2 | 3 | -behaviour(gen_server). 4 | 5 | %% API 6 | -export([start_link/0, 7 | check/0, 8 | count/0, die/0]). 9 | 10 | %% gen_server callbacks 11 | -export([init/1, 12 | handle_call/3, 13 | handle_cast/2, 14 | handle_info/2, 15 | terminate/2, 16 | code_change/3]). 17 | 18 | -define(SERVER, ?MODULE). 19 | 20 | -record(state, {init=true, table_id}). 21 | 22 | %%%=================================================================== 23 | %%% API 24 | %%%=================================================================== 25 | check() -> 26 | gen_server:cast(?MODULE, check). 27 | 28 | count() -> 29 | gen_server:cast(?MODULE, count). 30 | 31 | die() -> 32 | gen_server:cast(?MODULE, die). 33 | 34 | %%-------------------------------------------------------------------- 35 | %% @doc 36 | %% Starts the server 37 | %% 38 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 39 | %% @end 40 | %%-------------------------------------------------------------------- 41 | start_link() -> 42 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 43 | 44 | %%%=================================================================== 45 | %%% gen_server callbacks 46 | %%%=================================================================== 47 | 48 | %%-------------------------------------------------------------------- 49 | %% @private 50 | %% @doc 51 | %% Initializes the server 52 | %% 53 | %% @spec init(Args) -> {ok, State} | 54 | %% {ok, State, Timeout} | 55 | %% ignore | 56 | %% {stop, Reason} 57 | %% @end 58 | %%-------------------------------------------------------------------- 59 | init([]) -> 60 | {ok, #state{}}. 61 | 62 | %%-------------------------------------------------------------------- 63 | %% @private 64 | %% @doc 65 | %% Handling call messages 66 | %% 67 | %% @spec handle_call(Request, From, State) -> 68 | %% {reply, Reply, State} | 69 | %% {reply, Reply, State, Timeout} | 70 | %% {noreply, State} | 71 | %% {noreply, State, Timeout} | 72 | %% {stop, Reason, Reply, State} | 73 | %% {stop, Reason, State} 74 | %% @end 75 | %%-------------------------------------------------------------------- 76 | handle_call(_Request, _From, State) -> 77 | Reply = ok, 78 | {reply, Reply, State}. 79 | 80 | %%-------------------------------------------------------------------- 81 | %% @private 82 | %% @doc 83 | %% Handling cast messages 84 | %% 85 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 86 | %% {noreply, State, Timeout} | 87 | %% {stop, Reason, State} 88 | %% @end 89 | %%-------------------------------------------------------------------- 90 | handle_cast(die, State) -> 91 | exit(killed), 92 | {noreply, State}; 93 | handle_cast(count, State) -> 94 | TableId = State#state.table_id, 95 | Result = ets:update_counter(TableId, count, 1), 96 | io:format("Counter: ~p~n", [Result]), 97 | {noreply, State}; 98 | handle_cast(check, State) -> 99 | TableId = State#state.table_id, 100 | io:format("TableId: ~p Data: ~p~n", [TableId, ets:tab2list(TableId)]), 101 | {noreply, State}; 102 | handle_cast(_Msg, State) -> 103 | {noreply, State}. 104 | 105 | %%-------------------------------------------------------------------- 106 | %% @private 107 | %% @doc 108 | %% Handling all non call/cast messages 109 | %% 110 | %% @spec handle_info(Info, State) -> {noreply, State} | 111 | %% {noreply, State, Timeout} | 112 | %% {stop, Reason, State} 113 | %% @end 114 | %%-------------------------------------------------------------------- 115 | handle_info({'ETS-TRANSFER', TableId, Pid, _Data}, State) -> 116 | io:format("MGR(~p) -> SRV(~p) getting TableId: ~p~n", [Pid, self(), TableId]), 117 | {noreply, State#state{table_id=TableId}}; 118 | handle_info(_Info, State) -> 119 | {noreply, State}. 120 | 121 | %%-------------------------------------------------------------------- 122 | %% @private 123 | %% @doc 124 | %% This function is called by a gen_server when it is about to 125 | %% terminate. It should be the opposite of Module:init/1 and do any 126 | %% necessary cleaning up. When it returns, the gen_server terminates 127 | %% with Reason. The return value is ignored. 128 | %% 129 | %% @spec terminate(Reason, State) -> void() 130 | %% @end 131 | %%-------------------------------------------------------------------- 132 | terminate(_Reason, _State) -> 133 | ok. 134 | 135 | %%-------------------------------------------------------------------- 136 | %% @private 137 | %% @doc 138 | %% Convert process state when code is changed 139 | %% 140 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 141 | %% @end 142 | %%-------------------------------------------------------------------- 143 | code_change(_OldVsn, State, _Extra) -> 144 | {ok, State}. 145 | 146 | %%%=================================================================== 147 | %%% Internal functions 148 | %%%=================================================================== 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/etsgive_mgr.erl: -------------------------------------------------------------------------------- 1 | -module(etsgive_mgr). 2 | 3 | -behaviour(gen_server). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% gen_server callbacks 9 | -export([init/1, 10 | handle_call/3, 11 | handle_cast/2, 12 | handle_info/2, 13 | terminate/2, 14 | code_change/3]). 15 | 16 | -define(SERVER, ?MODULE). 17 | 18 | -record(state, {table_id}). 19 | 20 | %%%=================================================================== 21 | %%% API 22 | %%%=================================================================== 23 | 24 | gift() -> 25 | gen_server:cast(?MODULE, {gift, {count, 0}}). 26 | 27 | %%-------------------------------------------------------------------- 28 | %% @doc 29 | %% Starts the server 30 | %% 31 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 32 | %% @end 33 | %%-------------------------------------------------------------------- 34 | start_link() -> 35 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 36 | 37 | %%%=================================================================== 38 | %%% gen_server callbacks 39 | %%%=================================================================== 40 | 41 | %%-------------------------------------------------------------------- 42 | %% @private 43 | %% @doc 44 | %% Initializes the server 45 | %% 46 | %% @spec init(Args) -> {ok, State} | 47 | %% {ok, State, Timeout} | 48 | %% ignore | 49 | %% {stop, Reason} 50 | %% @end 51 | %%-------------------------------------------------------------------- 52 | init([]) -> 53 | process_flag(trap_exit, true), 54 | gift(), 55 | {ok, #state{}}. 56 | 57 | %%-------------------------------------------------------------------- 58 | %% @private 59 | %% @doc 60 | %% Handling call messages 61 | %% 62 | %% @spec handle_call(Request, From, State) -> 63 | %% {reply, Reply, State} | 64 | %% {reply, Reply, State, Timeout} | 65 | %% {noreply, State} | 66 | %% {noreply, State, Timeout} | 67 | %% {stop, Reason, Reply, State} | 68 | %% {stop, Reason, State} 69 | %% @end 70 | %%-------------------------------------------------------------------- 71 | handle_call(_Request, _From, State) -> 72 | Reply = ok, 73 | {reply, Reply, State}. 74 | 75 | %%-------------------------------------------------------------------- 76 | %% @private 77 | %% @doc 78 | %% Handling cast messages 79 | %% 80 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 81 | %% {noreply, State, Timeout} | 82 | %% {stop, Reason, State} 83 | %% @end 84 | %%-------------------------------------------------------------------- 85 | handle_cast({gift, Data}, State) -> 86 | SRV = whereis(etsgive_srv), 87 | link(SRV), 88 | TableId = ets:new(?MODULE, [private]), 89 | ets:insert(TableId, Data), 90 | ets:setopts(TableId, {heir, self(), Data}), 91 | ets:give_away(TableId, SRV, Data), 92 | {noreply, State#state{table_id=TableId}}; 93 | handle_cast(_Msg, State) -> 94 | {noreply, State}. 95 | 96 | %%-------------------------------------------------------------------- 97 | %% @private 98 | %% @doc 99 | %% Handling all non call/cast messages 100 | %% 101 | %% @spec handle_info(Info, State) -> {noreply, State} | 102 | %% {noreply, State, Timeout} | 103 | %% {stop, Reason, State} 104 | %% @end 105 | %%-------------------------------------------------------------------- 106 | handle_info({'EXIT',Pid,killed}, State) -> 107 | TableId = State#state.table_id, 108 | io:format("SRV(~p) !! is now dead, farewell TableId: ~p~n", [Pid, TableId]), 109 | {noreply, State}; 110 | handle_info({'ETS-TRANSFER', TableId, Pid, Data}, State) -> 111 | SRV = wait_for_srv(), 112 | io:format("Warning TableId: ~p OwnerPid: ~p is dying~n" 113 | "SRV(~p) => MGR(~p) handing TableId: ~p~n", [TableId, Pid, Pid, self(), TableId]), 114 | link(SRV), 115 | ets:give_away(TableId, SRV, Data), 116 | {noreply, State#state{table_id=TableId}}. 117 | 118 | wait_for_srv() -> 119 | case whereis(etsgive_srv) of 120 | undefined -> 121 | timer:sleep(1), 122 | wait_for_srv(); 123 | Pid -> Pid 124 | end. 125 | 126 | 127 | 128 | %%-------------------------------------------------------------------- 129 | %% @private 130 | %% @doc 131 | %% This function is called by a gen_server when it is about to 132 | %% terminate. It should be the opposite of Module:init/1 and do any 133 | %% necessary cleaning up. When it returns, the gen_server terminates 134 | %% with Reason. The return value is ignored. 135 | %% 136 | %% @spec terminate(Reason, State) -> void() 137 | %% @end 138 | %%-------------------------------------------------------------------- 139 | terminate(_Reason, _State) -> 140 | ok. 141 | 142 | %%-------------------------------------------------------------------- 143 | %% @private 144 | %% @doc 145 | %% Convert process state when code is changed 146 | %% 147 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 148 | %% @end 149 | %%-------------------------------------------------------------------- 150 | code_change(_OldVsn, State, _Extra) -> 151 | {ok, State}. 152 | 153 | %%%=================================================================== 154 | %%% Internal functions 155 | %%%=================================================================== 156 | 157 | 158 | 159 | --------------------------------------------------------------------------------