2009-12-03 70 views
30

我正在寻找在gen_server上使用Erlang的热代码交换功能,以便我不必重新启动它。我应该怎么做?当我搜索时,我能找到的只有一篇文章提到我需要使用gen_server:code_change回调。在Erlang的gen_server中实现代码交换

但是,我无法真正找到任何关于如何使用它的文档/示例。任何帮助或资源的链接非常感谢!

回答

44

正如我已经提到的,正常的升级方式是创建正确的.appup和.relup文件,并让release_handler完成需要完成的任务。但是,您可以手动执行涉及的步骤,如此处所述。很抱歉,很长的答案。

下面的dummy gen_server实现了一个计数器。旧版本(“0”)仅存储一个整数作为状态,而新版本(“1”)将{tschak,Int}存储为状态。正如我所说,这是一个虚拟的例子。

z.erl(旧):

-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(新):

-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}}. 

启动外壳,并编译旧代码。注意gen_server是以调试跟踪启动的。

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 

按预期工作:返回Int,新状态为Int + 1。

现在将z.erl替换为新的,然后执行以下步骤。

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 

你刚刚做了什么:5:编译新的代码。 6:暂停服务器。 7:清除旧代码(以防万一)。 8:加载新代码。 9:模块'z'的进程'z'中的代码更改从版本“0”以[]作为“额外”传递给code_change进行调用。 10:恢复服务器。

现在,如果你运行一些测试,你可以看到,服务器用新状态格式的工作原理:

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

在z.erl的版本1中,init应该返回{ok,{tschak,0}}作为初始状态。 – jmah 2010-01-24 03:28:02

+0

哇,谢谢!固定。 – Zed 2010-01-24 07:42:16

+0

为什么要麻烦调用'code:purge'如果虚拟机在加载后使用新版本? – spockwang 2013-11-17 08:01:21

5

您不需要在gen_server行为中使用该回调。如果通过代码升级更改状态的内部表示形式,那么它就在那里。

您只需要加载新模块,运行旧版本的gen_server就会升级,因为它会调用新模块。只是如果有必要,你没有机会改变表示。

+0

当你的意思是“加载新模块”,你的意思是,我只是重新编译使用genserver的模块,并且正在运行的服务器自动升级? – jeffreyveon 2009-12-03 16:02:09

+2

这就是如果你从Erlang shell(比如用c())编译它会发生什么。否则使用代码:load_file/2或代码:load_binary/2来获得类似的效果。 – 2009-12-03 16:58:12

+0

如果没有使用正确的appup和relup脚本,我不认为这有效...... – Zed 2009-12-03 17:55:08

2

如果您想以正确的方式进行操作,强烈建议,那么您需要阅读使用OTP主管和应用程序。

你可以做,而不是在这里阅读OTP设计原则用户指南雪上加霜:

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

+0

谢谢,我读了不同的OTP行为,我只是找不到任何与此相关的信息。 – jeffreyveon 2009-12-03 16:03:44

3

做到这一点最简单的方法是更换.beam文件,并在shell中运行l(my_server_module).。这绕过了code_change函数,因此要求状态的表示没有改变。

正如已经提到的,正确的方法是使用appup和relup脚本创建新版本。这个新版本随后与release_handler一起安装。