在Erlang的gen_server中实现代码交换

发布于 2024-08-13 12:26:38 字数 172 浏览 6 评论 0原文

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

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

I am looking to make use of Erlang's hot code swapping feature on a gen_server, so that I don't have to restart it. How should I do that? When I searched, all I could find was one article which mentioned that I need to make use of gen_server:code_change callback.

However, I could not really find any documentation/examples on how to use this. Any help or links to resources greatly appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

他夏了夏天 2024-08-20 12:26:38

正如我已经提到的,正常的升级方法是创建正确的 .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 替换为新的 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”调用代码更改,并将 [] 作为“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

As I already mentioned the normal way of upgrading is creating the proper .appup and .relup files, and let release_handler do what needs to be done. However you can manually execute the steps involved, as described here. Sorry for the long answer.

The following dummy gen_server implements a counter. The old version ("0") simply stores an integer as state, while the new version ("1") stores {tschak, Int} as state. As I said, this is a dummy example.

z.erl (old):

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

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

Start the shell, and compile the old code. Notice the gen_server is started with debug trace.

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

Works as expected: returns the Int, and new state is Int+1.

Now replace z.erl with the new one, and execute the following steps.

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

What you just did: 5: compiled the new code. 6: suspended the server. 7: purged older code (just in case). 8: loaded the new code. 9: invoked code change in process 'z' for module 'z' from version "0" with [] passed as "Extra" to code_change. 10: resumed the server.

Now if you run some more tests, you can see, that the server works with the new state format:

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
一袭水袖舞倾城 2024-08-20 12:26:38

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

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

You don't need to use that callback in the gen_server behaviour. It is there if you change the internal representation of the state across a code upgrade.

You only need to load the new module and the gen_server running the old version will upgrade, since it calls the new module. It is just that you dont have a chance to change the representation if that is necessary.

薄荷→糖丶微凉 2024-08-20 12:26:38

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

如前所述,正确的方法是使用 appup 和 relup 脚本创建新版本。然后,这个新版本将与 release_handler 一起安装。

The simplest way to do it is replace the .beam file and run l(my_server_module). in the shell. This bypasses the code_change function, and therefore requires that the representation of state hasn't changed.

As mentioned already, the proper way to do it is to create a new release with appup and relup scripts. This new release is then installed with release_handler.

勿忘心安 2024-08-20 12:26:38

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

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

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

If you want to do it the right way, which is highly recommended, then you need to read up on the use of OTP Supervisors and Applications.

You could do worse than to read the OTP Design Principles User's Guide here :

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

百善笑为先 2024-08-20 12:26:38

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

If you're on rebar3, some of this manual processing has been automated away (ie. appup and relup generation), you can find more info here: http://lrascao.github.io/automatic-release-upgrades-in-erlang/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文