gen_server:call on a global name that is not registered

800 views Asked by At

I have a gen_server process that registers a global name like this:

global:register_name(<<"CLIENT_", NAME/binary>>, self()),

Another process is trying to send this process a message using gen_server:call like this:

 gen_server:call({global, <<"CLIENT_", NAME/binary>>}, {msg, DATA}),

If the second call happens before the first process registers the global name, it dies with:

exit with reason {noproc,{gen_server,call,[{global,<<"CLIENT_122">>},{msg, <<"TEST">>}]}}

What is the correct way to make a call only if the global name is register, and do something else if it is not?

1

There are 1 answers

0
zxq9 On BEST ANSWER

Three things:

  1. How to guard this call (mechanics).
  2. Why you generally shouldn't want to guard the call (robust architecture).
  3. Where you are putting your interface to this function (code structure).

Mechanics

You can check whether a name is registered with the global registry before making the call like this:

-spec send_message(Name, Message) -> Result
    when Name    :: term(),
         Message :: term(),
         Result  :: {ok, term()}
                  | {error, no_proc}.

send_message(Name, Message) ->
    case global:whereis_name(Name) of
        undefined ->
            {error, no_proc};
        PID ->
            Value = gen_server:call(PID, Message),
            {ok, Value}
    end.

Because there will be a few nanoseconds between the return value of global:whereis_name/1 being checked and the actual call via gen_server:call/2,3, however, so you still don't know if you actually just sent a call to a dead process, but at least you sent it to a PID that won't crash the program right away.

Another way to do it would be with a try ... catch construct, but that is a very tricky habit to get into.

Robust Architecture

All that stuff above, keep it in the back of your mind, but in the front of your mind you should want to crash if this name is unregistered. Your registered process is supposed to be alive so why are you being so paranoid?!? If thing are bad you want to know they are bad in a catastrophic way and let everything related to that crash and burn straight away. Don't try to recover on your own in an unknown state, that is what supervisors are for. Let your system be restarted in a known state and give it another go. If this is a user-directed action (some user of the system, or a web page request or whatever) they will try again because they are monkeys that try things more than once. If it is an automated request (the user is a computer or robot, for example) it can retry again or not, but leave that decision up to it in the common case -- but give it some indication of failure (an error message, a closed socket, etc.).

As long as the process you are calling registers its name during its init/1 call (before it has returned its own PID to its supervisor), and this is always happening before the calling process is alive or aware of the process to be called then you shouldn't have any trouble with this. If it has crashed for some reason, then you have more fundamental problems with your program and catching the caller's crash isn't going to help you. This is a basic idea in robustness engineering.

Structure your system so that the callee is guarantee to be alive and registered before the call can occur, and if it has died you should want the caller to die also. (Yes, I'm beating a dead horse, but this is important.)

Code Structure

Most of the time you don't want to have a module that defines a process, let's say foo.erl that defines a process we will name {global, "foo"}, have a naked call to gen_server:call/2,3 or gen_server:cast/2 that is intended for a separate process defined in another module (let's say bar.erl that defines a process we will name {global, "bar"}). What we would want is that bar.erl has an interface function that it exports, and that this function is where the gen_server:call/2 but happens.

That way any special work that applies to this call (which any other calling module may also require) exists in a single spot, and you can name the interface to the process "bar" in a way that conveys some meaning aside from the message being passed to it.

For example, if the process defined by bar.erl is a connection counter (maybe we are writing a game server and we're counting connections) we might have bar.erl be in charge of maintaining the counter. So processes send a cast (asynch message) to bar any time a new user connects. Instead of having every different process that might need to do this define some complex name checking and then naked message sending, instead consider having a function exported from bar.erl that hides that mess and is named something meaningful, like bar:notify_connect(). Just calling this in your other code is much easier to understand, and you can choose how you should be dealing with this "what if bar doesn't exist?" situation right there, in one spot.

On that note, you might want to take a look at the basic Erlang "service manager -> worker" pattern. Named processes are not overwhelmingly needed in many cases.