2009-12-03 8 views
30

Je cherche à utiliser la fonction d'échange de code à chaud d'Erlang sur un serveur gen, de sorte que je n'ai pas besoin de le redémarrer. Comment devrais-je faire ça? Quand j'ai cherché, tout ce que j'ai pu trouver était un article qui mentionnait que je devais utiliser le rappel gen_server:code_change.Atteindre l'échange de code dans le serveur d'Erlang

Cependant, je n'ai pas vraiment trouvé de documentation/d'exemples sur la façon de l'utiliser. Toute aide ou liens vers des ressources grandement appréciés!

Répondre

44

Comme je l'ai déjà mentionné, la manière normale de mettre à niveau est de créer les fichiers .appup et .relup appropriés, et de laisser à release_handler le soin de faire ce qui doit être fait. Cependant, vous pouvez exécuter manuellement les étapes concernées, comme décrit ici. Désolé pour la longue réponse.

Le serveur factice suivant gen_server implémente un compteur. L'ancienne version ("0") stocke simplement un entier comme état, tandis que la nouvelle version ("1") stocke {tschak, Int} comme état. Comme je l'ai dit, c'est un exemple factice.

z.erl (ancien):

-module(z). 
-version("0"). 

-export([start_link/0, boing/0]). 

-behavior(gen_server). 
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 

start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]). 

boing() -> gen_server:call(?MODULE, boom). 


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

handle_call(boom, _From, Num) -> {reply, Num, Num+1}; 
handle_call(_Call, _From, State) -> {noreply, State}. 

handle_cast(_Cast, State) -> {noreply, State}. 

handle_info(_Info, State) -> {noreply, State}. 

terminate(_Reason, _State) -> ok. 

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

z.erl (nouveau):

-module(z). 
-version("1"). 

-export([start_link/0, boing/0]). 

-behavior(gen_server). 
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 

start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]). 

boing() -> gen_server:call(?MODULE, boom). 


init([]) -> {ok, {tschak, 0}}. 

handle_call(boom, _From, {tschak, Num}) -> {reply, Num, {tschak, Num+1}}; 
handle_call(_Call, _From, State) -> {noreply, State}. 

handle_cast(_Cast, State) -> {noreply, State}. 

handle_info(_Info, State) -> {noreply, State}. 

terminate(_Reason, _State) -> ok. 

code_change("0", Num, _Extra) -> {ok, {tschak, Num}}. 

Démarrez le shell et compiler l'ancien code. Notez que gen_server est démarré avec la trace de débogage.

1> c(z). 
{ok,z} 
2> z:start_link(). 
{ok,<0.38.0>} 
3> z:boing(). 
*DBG* z got call boom from <0.31.0> 
*DBG* z sent 0 to <0.31.0>, new state 1 
0 
4> z:boing(). 
*DBG* z got call boom from <0.31.0> 
*DBG* z sent 1 to <0.31.0>, new state 2 
1 

Fonctionne comme prévu: renvoie l'Int et le nouvel état est Int + 1.

Remplacez maintenant z.erl par le nouveau et exécutez les étapes suivantes.

5> compile:file(z). 
{ok,z} 
6> sys:suspend(z). 
ok 
7> code:purge(z). 
false 
8> code:load_file(z). 
{module,z} 
9> sys:change_code(z,z,"0",[]). 
ok 
10> sys:resume(z). 
ok 

Ce que vous venez de faire 5: compilé le nouveau code. 6: suspendu le serveur. 7: purgé du code plus ancien (juste au cas où). 8: chargé le nouveau code. 9: changement de code invoqué dans le processus 'z' pour le module 'z' à partir de la version "0" avec [] passé comme "Extra" à code_change. 10: a repris le serveur.

Maintenant, si vous exécutez d'autres tests, vous pouvez voir, que le serveur travaille avec le nouveau format d'état:

11> z:boing(). 
*DBG* z got call boom from <0.31.0> 
*DBG* z sent 2 to <0.31.0>, new state {tschak,3} 
2 
12> z:boing(). 
*DBG* z got call boom from <0.31.0> 
*DBG* z sent 3 to <0.31.0>, new state {tschak,4} 
3 
+1

Dans la version 1 de z.erl, init devrait renvoyer {ok, {tschak, 0}} comme état initial – jmah

+0

Wow, merci, corrigé – Zed

+0

Pourquoi s'embêter avec les comportements OTP? appeler 'code: purge' si la machine virtuelle utilise la nouvelle version une fois qu'elle est chargée? – spockwang

5

Vous n'avez pas besoin d'utiliser ce rappel dans le comportement gen_server. Il est là si vous changez la représentation interne de l'état à travers une mise à niveau de code.

Vous avez seulement besoin de charger le nouveau module et le gen_server exécutant l'ancienne version sera mis à niveau, car il appelle le nouveau module. C'est juste que vous n'avez pas la chance de changer la représentation si cela est nécessaire.

+0

Lorsque vous voulez dire par "charger le nouveau module", voulez-vous dire, je viens de recompiler le module qui utilise Genserver, et le serveur en cours de mise à niveau automatiquement ? – jeffreyveon

+2

C'est ce qui devrait arriver si vous le compilez depuis le shell Erlang (disons avec c()). Sinon, utilisez le code: load_file/2 ou le code: load_binary/2 pour obtenir des effets similaires. –

+0

Je ne pense pas que tout cela fonctionne sans utiliser les scripts appup et relup appropriés ... – Zed

2

Si vous voulez le faire dans le bon sens, ce qui est fortement recommandé, alors vous devez lire sur l'utilisation de OTP Supervisors et Applications.

Vous pourriez faire pire que de lire les principes de conception du Bureau du Procureur Guide de l'utilisateur ici:

http://www.erlang.org/doc/design_principles/users_guide.html

+0

Merci, j'ai lu sur les différents – jeffreyveon

3

La façon, il est plus simple à faire remplacer le fichier .beam et exécuter l(my_server_module). dans la coquille. Cela contourne la fonction code_change, et nécessite donc que la représentation de l'état n'a pas changé.

Comme déjà mentionné, la bonne façon de le faire est de créer une nouvelle version avec des scripts appup et relup. Cette nouvelle version est ensuite installée avec release_handler.