views:

409

answers:

2

After seeing this article, I have been tinkering with mochiweb. While trying to replicate what's done in the article - basically setting up a mochiweb server, having two erlang nodes, and then calling a function defined in one node in the other (after setting net_adm:ping() between the two nodes so they know each other).

I was able to follow everything till that function call part. In n1@localhost, which is the mochiweb server, I call (just as done in the article):

router:login(IdInt, self()).

And then, in n2@localhost, which is the router.erl script, I have defined the login function:

login(Id, Pid) when is_pid(Pid) ->
    gen_server:call(?SERVER, {login, Id, Pid}).

handle_call({login, Id, Pid}, _From, State) when is_pid(Pid) ->
          ets:insert(State#state.pid2id, {Pid, Id}),
          ets:insert(State#state.id2pid, {Id, Pid}),
          link(Pid), % tell us if they exit, so we can log them out
          io:format("~w logged in as ~w\n",[Pid, Id]),
          {reply, ok, State};

I have pasted only the relevant parts of the code. However, when I now access the webserver on the browser - I get this error report on n1@localhost:

=CRASH REPORT==== 11-Jun-2009::12:39:49 ===
  crasher:
    initial call: mochiweb_socket_server:acceptor_loop/1
    pid: <0.62.0>
    registered_name: []
    exception error: undefined function router:login/2
      in function  mochiconntest_web:loop/2
      in call from mochiweb_http:headers/5
    ancestors: [mochiconntest_web,mochiconntest_sup,<0.59.0>]
    messages: []
    links: [<0.61.0>,#Port<0.897>]
    dictionary: [{mochiweb_request_path,"/test/123"}]
    trap_exit: false
    status: running
    heap_size: 1597
    stack_size: 24
    reductions: 1551
  neighbours:

=ERROR REPORT==== 11-Jun-2009::12:39:49 ===
{mochiweb_socket_server,235,{child_error,undef}}

After googling around, I got a basic gist of what the error is trying to say - basically it says that the login function being called in n1@localhost is not defined - but it is defined in n2@localhost (and both the nodes know each other - I did nodes(). to check) !! Please tell me where I am going wrong!

A: 

You are right - the code for router:login is not actually available on your host n1@localhost - it is the code within that function (the gen_server:call function) which routes the call to n2@localhost (via that ?SERVER macro) and that's where the real implementation is. The top level function is simply a way of wrapping up that call to the appropriate node.

But you do need at least the implementation of login

login(Id, Pid) when is_pid(Pid) ->
    gen_server:call(?SERVER, {login, Id, Pid}).

available on n1@localhost.

(Updated)

You'd need to define, replace or us a ?SERVER macro as well. In the sample code in the article this is

-define(SERVER, global:whereis_name(?MODULE)).

but this uses the ?MODULE macro which would be wrong in your case. Basically when the gen_server process (router) is started it registers itself as ?MODULE, in this case that maps to the atom 'router' which other nodes can see (using global:whereis_name(router) ). So you should be able to just write:

login(Id, Pid) when is_pid(Pid) ->
    gen_server:call(global:whereis_name(router), {login, Id, Pid}).

So the effect of calling login on n1@localhost would make a gen_server call to the router:handle_call method on n2@localhost, assuming the router gen_server process is running and has registered itself. The return value of that call comes back to your process on n1@localhost.

Alan Moore
Does this mean I have to just get the above two lines up in the mochiweb/erl script (and the declaration -export([login/2]).) ? When I do that, I get "undefined macro ''SERVER''" error. If I use "%-behaviour(gen_server).", then I have to over-ride the default functions..
thomas55
Or am I missing something else obvious? Do I need to place the router.erl on the same folder as where the mochiweb script is?? Currenly, I am not doing that...
thomas55
I'll edit my original answer....
Alan Moore
GREAT! :) It works. This has been holding me up for a day! Thanks a lot. But, I am just curious, does this mean that the code given in that article is wrong as it is? Is there anyway to make the router module global, so that calling router:func() from n1@localhost would work, instead of duplicating the function definition(s) again??
thomas55
The code would work fine if the router.beam files where simply available on both nodes (which is maybe what happened for the article). You could do rpc:call(n2@localhost, router, login, [Id, Pid]) but I believe the technique above looks (and smells...) better.
Alan Moore
A: 

In the examples in your question it looks like you only loaded the router module on one node. Nodes do not by default automatically load code from eachother, so just because the function is defined on n2 doesn't mean you can call it locally on n1 (n1 would need to be able to load it in the normal way).

The code given looks like it copes properly with running in a distributed system (you can start the router server on one node and calling the API functions on other nodes will send router requests to the correct place). So you just need to put a copy of the router module on n1 and it should just work. It's possible to have n1 load the router module from n2, but it's a bit of a hassle compared to just giving n1 a copy of the module in its code path.

Interestingly, in the router code it's unnecessary to do gen_server:call(global:whereis_name(?MODULE, Message)). as the gen_server:call/2 function knows how to lookup global registrations itself. -define(SERVER, {global, ?MODULE}). would work fine if you changed the start_link function to start_link() -> gen_server:start_link(?SERVER, ?MODULE, [], []).

archaelus
Alright thanks. When you mean "So you just need to put a copy of the router module on n1 and it should just work." - do you mean just copying the definitions like Alan has written above? This works well.
thomas55
On n2 you have router.beam, the exact same beam file (and exact same erlang source) should work fine on n1 too - provided that router:start[_link] is called on n2 only while router:login can be called from either node. (To ensure the router server runs on n2, but requests can be made from anywhere).
archaelus