在Erlang的gen_server中实现代码交换

时间:2009-12-03 15:38:00

标签: erlang otp

我希望在gen_server上使用Erlang的热代码交换功能,这样我就不必重新启动它了。我该怎么办?当我搜索时,我能找到的只有一篇文章提到我需要使用gen_server:code_change回调。

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

5 个答案:

答案 0 :(得分:44)

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

以下虚拟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}}.

启动shell,编译旧代码。请注意,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:从版本“0”调用模块'z'的进程'z'中的代码更改,[]作为“Extra”传递给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 :(得分:5)

您无需在gen_server行为中使用该回调。如果您在代码升级中更改状态的内部表示,那就是存在的。

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

答案 2 :(得分:3)

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

如上所述,正确的方法是使用appup和relup脚本创建新版本。然后,此新版本随release_handler一起安装。

答案 3 :(得分:2)

如果你想以正确的方式去做,这是强烈推荐的,那么你需要阅读使用OTP主管和应用程序。

你可能比在这里阅读OTP设计原则用户指南更糟糕:

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

答案 4 :(得分:0)

如果您使用的是rebar3,这部分手动处理已经自动化(即appup和relup generation),您可以在此处找到更多信息:http://lrascao.github.io/automatic-release-upgrades-in-erlang/