2017-09-15 4 views
3

J'ai un processus gen_server qui enregistre un nom global comme celui-ci:gen_server: appel à un nom global qui est pas inscrit

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

Un autre processus tente d'envoyer ce processus, un message en utilisant gen_server:call comme ceci:

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

Si le second appel se produit avant que le premier processus enregistre le nom global, il meurt avec:

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

Quelle est la manière correcte de passer un appel uniquement si le nom global est enregistré, et faire autre chose si ce n'est pas le cas?

Répondre

3

Trois choses:

  1. Comment garder cet appel (mécanique).
  2. Pourquoi vous généralement ne devrait pas vouloir pour garder l'appel (architecture robuste).
  3. 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.