Search code examples
erlanggen-servererlang-supervisorets

Erlang: ets table does not persist data after gen_server crashes and restarts


I have a gen_server which stores positions of objects in an ets table like this

-module(my_gen_server).
-record(slot, {position, object}).
-behavior(gen_server).

%% API
-export([start_link/1, init/1, move/2, handle_call/3, handle_cast/2, get/1, delete/1]).

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

init([]) ->
  WidthX, HeightY = get_dims(),
  ets:new(my_gen_server,[named_table, {keypos, #slot.position}]),
  {ok, {WidthX, HeightY}}.

move(Object, {X,Y}) ->
  gen_server:call(?MODULE, {move, Object, {X,Y}}).

handle_call({move, Object, {X,Y}}, _From, {WidthX, HeightY}) ->
  case add_or_move(Object, X, Y) of
    {error, Reason} ->
      {reply, {error, Reason}, {WidthX, HeightY}};
    _ ->
      {reply, ok, {WidthX, HeightY}}
  end.

search_object(Object) ->
  Pos = ets:match(my_gen_server, #slot{position ='$1', object = Object, _='_'}),
  case Pos of
    [] -> {error, "Not found"};
    _ -> lists:flatten(Pos)
  end.

add_or_move(Object, X, Y) ->
   Pos = search_object(Object),
   case Pos of
      {error, _Reason} ->
          supervisor:start_child(my_object_sup, [Object, {X, Y}]),
          ets:insert(my_gen_server, #slot{position = {X,Y}, object = Object});
      _ ->
          ets:insert(my_gen_server, #slot{position = {X,Y}, object = Object})
   end.

The problem is when a supervisor starts my_gen_server and the process crashes and restarts, the ets table is gone and I lose all my object data. I searched for this problem and everywhere they say that storing data in ets table can help in making the state persist but I cannot find the code to achieve it anywhere. I also tried creating the ets table before gen_server:start_link is called instead of init, but that prevents the gen_server from restarting at all after crash. I understand that conceptually ets table should be able to persist the state but would really like some help in understanding how it works in code.


Solution

  • ets tables are linked to the process that creates them, that's why if you create the table in a gen_server process when the process terminates, the table is destroyed. If you want the table to persist, you have basically 2 options:

    • Use the ets inheritance mechanism: Check out ets:give_away/3 and the heir option for table initialization.
    • Keep a single process owning the table that is not one of your servers. In this case, the table should be created as a public and named table and no operation should be performed on the table-holding process. That process should only exist to hold the table. Then, your servers can just access the table by name.