Search code examples
erlangelixirerlang-otpmnesia

Erlang database server nodedown error, can not get mnesia database data through database logic


I am implementing an Erlang/OTP basic chat application with a chat server and database logic(for Mnesia). I want a database server as the middle person. This chat server and database both are distributed. So, I can spawn new chat users(by using chat server) with their node name. I can initialize the database for each available user nodes and so, db table will be replicated to every node. I can access data through database logic code. But can not do any database operations with database server. I started db server,output:

(john@DESKTOP-RD414DV)7> database_server:start_link([node()|nodes()]).
{local,database_server} (<0.129.0>) starting...

This is database logic code

-export([install/1, get_db/1, get_all_dbe/1, delete_db/1, store_db/4]).
-include_lib("stdlib/include/qlc.hrl").
-record(userDetails, {node,username, location, gender}).
%%initialize database
install(Nodes) ->
  ok = mnesia:create_schema(Nodes),
  rpc:multicall(Nodes, application, start, [mnesia]),
  try
      mnesia:table_info(type,userDetails)
  catch
      exit:_  ->
        mnesia:create_table(userDetails, [{attributes, record_info(fields, userDetails)},
          {type, bag},
          {disc_copies, Nodes}])
  end.


store_db(Node, Username, Location, Gender) ->
  F = fun() ->
    mnesia:write(#userDetails{node =Node, username = Username, location = Location, gender = Gender})
      end,
  mnesia:transaction(F).

get_db(Node) ->
  F = fun() ->
    Query = qlc:q([X || X <- mnesia:table(userDetails),
      X#userDetails.node =:= Node]),
    Results = qlc:e(Query),
    lists:map(fun(Item) -> Item#userDetails.username end, Results)
      end,
  mnesia:transaction(F).

get_all_dbe(Node) ->
  F = fun() ->
    Query = qlc:q([X || X <- mnesia:table(userDetails),
      X#userDetails.node =:= Node]),
    Results = qlc:e(Query),
    lists:map(fun(Item) -> {Item#userDetails.username, Item#userDetails.location, Item#userDetails.gender} end, Results)
      end,
  mnesia:transaction(F).

delete_db(Node) ->
  F = fun() ->
    Query = qlc:q([X || X <- mnesia:table(userDetails),
      X#userDetails.node =:= Node]),
    Results = qlc:e(Query),

    FF = fun() ->
      lists:foreach(fun(Result) -> mnesia:delete_object(Result) end, Results)
         end,
    mnesia:transaction(FF)
      end,
  mnesia:transaction(F).

This is database server code:

-behaviour(gen_server).

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

-define(SERVER, ?MODULE).

-record(database_server_state, {}).
%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

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


init(Nodes) ->
  process_flag(trap_exit, true),
  io:format("~p (~p) starting...~n", [{local, ?MODULE}, self()]),
  database_logic:install(Nodes),
  {ok, #database_server_state{}}.

store(Node, Username, Location, Gender) ->
  gen_server:call({local, ?MODULE}, {store_db, Node, Username, Location, Gender}).
%%getdb(Node) ->
%%  gen_server:call({local, ?MODULE}, {get_db, Node}).

getalldb(Node) ->
  gen_server:call({local, ?MODULE}, {get_all_db, Node}).
delete(Node) ->
  gen_server:call({local, ?MODULE}, {delete, Node}).

handle_call({store_db, Node, Username, Location, Gender}, _From, State = #database_server_state{}) ->
  database_logic:store_db(Node, Username, Location, Gender),
  io:format("userdetails are saved!"),
  {reply, ok, State};

%%handle_call({get_db, Node}, _From, State = #database_server_state{}) ->
%%  X = database_logic:get_db(Node),
%%  {_, [Name]} = X,
%%  io:format("SENDER NAME: ~p~n", [Name]),
%%  {reply, ok, State};

handle_call({get_all_db, Node}, _From, State = #database_server_state{}) ->
  Y = database_logic:get_all_dbe(Node),
  {_, [{Name, Location, Gender}]} = Y,
  io:format("SENDER NAME: ~p\t LOCATION: ~p\t GENDER: ~p~n", [Name,Location,Gender]),
  {reply, ok, State};

handle_call({delete, Node},  _From, State = #database_server_state{}) ->
  database_logic:delete_db(Node),
  io:format("~p node data deleted!", [Node]),
  {reply, ok, State}.

handle_cast(_Request, State = #database_server_state{}) ->
  {noreply, State}.

handle_info(_Info, State = #database_server_state{}) ->
  {noreply, State}.

terminate(_Reason, _State = #database_server_state{}) ->
  ok.

code_change(_OldVsn, State = #database_server_state{}, _Extra) ->
  {ok, State}.

This error is found when i do store, getdb etc, database operations.(it says that NODEDOWN, but all nodes are running)

(john@DESKTOP-RD414DV)9> database_server: getalldb("mary@DESKTOP-RD414DV").
** exception exit: {{nodedown,database_server},
                    {gen_server,call,
                                [{local,database_server},
                                 {get_all_db,"mary@DESKTOP-RD414DV"}]}}
     in function  gen_server:call/2 (gen_server.erl, line 367)

Solution

  • The problem is in the gen_server:call calls:

    gen_server:call({local, ?MODULE}, {get_all_db, Node}).
    

    The way you specify the name of the server process is different between start_link and call. In start_link, you specify {local, myname} in order to register the name myname on the local node, but in call you would just specify myname.

    If you pass a tuple with two elements to gen_server:call, it treats the first element as a process name, and the second element as a node name. So in the code above, it tries to make a remote call to the process registered as local on the node database_server - and this fails, because there is no node called database_server (not least because a node name without an @ sign is invalid). That's why nodedown is the error we get.

    So change the lines with gen_server:call to something like:

    gen_server:call(?MODULE, {get_all_db, Node}).
    

    You can see the difference between the two conventions in the gen_server documentation. Compare the server_name() type spec used in start_link and the server_ref() type spec used in call.