Sending a message from Java to a registered Erlang gen_server

996 views Asked by At

How does one go about sending a message from a Jinterface Java server to a globally registered gen_server?

For example, my gen_server was started like this:

  start_link ({global, myServerName}, Mod, Args, Options).

mbox.send("myServerName", MyMessage). doesn't work. No message arrives at myServerName:handle_info.

1

There are 1 answers

0
Steve Vinoski On

One way is to invoke global:send/2 as an RPC on the server node. The Java code would look like this:

import java.io.IOException;
import com.ericsson.otp.erlang.*;

public class JClient {
    static String server = "server";
    static String global = "myServerName";

    public static void main(String[] args) throws Exception {
        OtpSelf client = new OtpSelf("client", "java");
        OtpPeer peer = new OtpPeer(server);
        OtpConnection conn = client.connect(peer);
        conn.sendRPC("global", "send", new OtpErlangObject[] {
                new OtpErlangAtom(global),
                new OtpErlangTuple(new OtpErlangObject[] {
                        new OtpErlangAtom("publish"),
                        new OtpErlangTuple(new OtpErlangObject[] {
                                new OtpErlangAtom("all"),
                                new OtpErlangString("this is a test")})
                    })});
    }
}

The second argument to the OtpSelf constructor is the Erlang cookie, which has to match the cookie of the server node as we'll see below. The code uses the sendRPC method of OtpConnection to call the global:send/2 function on the server node, which returns the pid of the globally-registered process if it succeeds or an error if not (but the Java code doesn't check this).

The server could be a simple gen_server and look like this:

-module(svr).
-behaviour(gen_server).

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

-record(state, {}).

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

stop() ->
    gen_server:cast({global, myServerName}, stop).

init([]) ->
    {ok, #state{}}.

handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_cast(stop, State) ->
    {stop, normal, State};
handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info({publish, {Topic, Msg}}, State) ->
    io:format("publish for topic ~p: ~p~n", [Topic, Msg]),
    {noreply, State};
handle_info(_Msg, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

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

Note that I register the server with the global name myServerName to match what you used in your question, but in practice I never use mixed-case atom names like that.

To run the server, first start an Erlang shell:

erl -sname server -setcookie java

Note that the cookie matches what's in the Java code, and also that I used -sname to name the server node; I didn't try long names at all.

Then, run the svr process in the Erlang shell:

Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:8:8] [async-threads:10] [kernel-poll:false] [dtrace]

Eshell V5.10.4  (abort with ^G)
(server@myhost)1> svr:start_link().
{ok,<0.42.0>}

Then, run the Java client, and the server should emit the following message:

publish for topic all: "this is a test"

The main drawback to this approach is that it has to go through the RPC server for each message. Another alternative would be to send an RPC to global:whereis_name/1 to look up the pid of the registered process, and then send messages directly to it using send instead of sendRPC, but the drawback there is that if the globally-registered process dies and is restarted, the Java code will need to detect that the pid is no longer valid and redo the lookup.