Trois choses:
- Comment garder cet appel (mécanique).
- Pourquoi vous généralement ne devrait pas vouloir pour garder l'appel (architecture robuste).
- Lorsque vous placez votre interface sur cette fonction (structure de code).
Mécanique
Vous pouvez vérifier si un nom est inscrit sur le registre mondial avant de faire l'appel comme celui-ci:
-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.
Parce qu'il y aura quelques nanosecondes entre la valeur de retour de global:whereis_name/1
en cours de vérification et l'appel réel via gen_server:call/2,3
, cependant, vous toujours ne sais pas si vous avez simplement envoyé un appel à un processus mort, mais au Vous l'avez envoyé à un PID qui ne plante pas le programme tout de suite.
Une autre façon de le faire serait avec une construction try ... catch
, mais c'est une habitude très difficile à saisir.
Architecture robuste
Tout ce genre de choses ci-dessus, gardez à l'arrière de votre esprit, mais à l'avant de votre esprit, vous devriez voulez planter si ce nom est non enregistré. Votre processus enregistré est censé être en vie alors pourquoi êtes-vous si paranoïaque?!? Si les choses sont mauvaises, voulez savoir qu'ils sont mauvais d'une manière catastrophique et laisser tout ce qui est lié à ce crash et brûler tout de suite. N'essayez pas de récupérer par vous-même dans un état inconnu, c'est ce que les superviseurs sont pour. Laissez votre système être redémarré dans un état connu et recommencez. S'il s'agit d'une action dirigée par l'utilisateur (un utilisateur du système, ou une demande de page Web ou autre), ils essaieront à nouveau car ce sont des singes qui essaient les choses plusieurs fois. S'il s'agit d'une requête automatisée (l'utilisateur est un ordinateur ou un robot, par exemple), il peut réessayer ou non, mais laisser cette décision dans le cas commun - mais lui donner une indication d'échec (un message d'erreur, une prise fermée, etc.).Tant que le processus que vous appelez enregistre son nom pendant son appel init/1
(avant qu'il ait renvoyé son propre PID à son superviseur), et cela se produit toujours avant que le processus appelant soit actif ou conscient du processus pour être appelé alors vous ne devriez pas avoir de problème avec cela. S'il s'est écrasé pour une raison quelconque, alors vous avez des problèmes plus fondamentaux avec votre programme et attraper le plantage de l'appelant ne va pas vous aider. C'est une idée de base en ingénierie de robustesse.
Structurez votre système afin que l'appelé soit garanti d'être vivant et enregistré avant que l'appel ne puisse avoir lieu, et s'il est mort, voulez également que l'appelant meure également. (Oui, je suis battre un cheval mort, mais c'est important.)
Code Structure
La plupart du temps vous ne voulez pas avoir un module qui définit un processus, disons foo.erl
cela définit un processus que nous nommons {global, "foo"}
, un appel nu à gen_server:call/2,3
ou gen_server:cast/2
qui est destiné à un processus distinct défini dans un autre module (disons bar.erl
qui définit un processus que nous nommerons {global, "bar"}
). Ce que nous voudrions, c'est que bar.erl
ait une fonction d'interface qu'il exporte, et que cette fonction soit là où se trouve le gen_server:call/2
. De cette manière, tout travail spécial qui s'applique à cet appel (dont n'importe quel autre module appelant peut également avoir besoin) existe en un seul point, et vous pouvez nommer l'interface au processus "bar"
d'une manière qui donne un sens à part le message qui lui est transmis. Par exemple, si le processus défini par bar.erl
est un compteur de connexion (peut-être que nous sommes en train d'écrire un serveur de jeu et que nous comptons des connexions), nous pourrions avoir bar.erl
être responsable de la maintenance du compteur. Ainsi, les processus envoient un cast
(message asynchrone) à bar
chaque fois qu'un nouvel utilisateur se connecte. Au lieu d'avoir tous les processus différents qui pourraient avoir besoin de le faire, définissez une vérification de nom complexe et un envoi de message nu, envisagez plutôt d'avoir une fonction exportée de bar.erl
qui cache ce désordre et porte un nom significatif, comme bar:notify_connect()
. Le simple fait d'appeler cela dans votre autre code est beaucoup plus facile à comprendre, et vous pouvez choisir comment vous devriez gérer ce "si la barre n'existe pas". situation juste là, dans un endroit. Sur cette note, vous pouvez jeter un oeil à la base Erlang "service manager -> worker" pattern. Les processus nommés ne sont pas forcément nécessaires dans de nombreux cas.