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?
Three things:
Mechanics
You can check whether a name is registered with the global registry before making the call like this:
Because there will be a few nanoseconds between the return value of
global:whereis_name/1
being checked and the actual call viagen_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 togen_server:call/2,3
orgen_server:cast/2
that is intended for a separate process defined in another module (let's saybar.erl
that defines a process we will name{global, "bar"}
). What we would want is thatbar.erl
has an interface function that it exports, and that this function is where thegen_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 havebar.erl
be in charge of maintaining the counter. So processes send acast
(asynch message) tobar
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 frombar.erl
that hides that mess and is named something meaningful, likebar: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.