EVM 如何在进程中保存代码版本以及 OTP 在热重载方面做了什么?

how does EVM hold code version in a process and what's the OTP do in term of hot reload?

我正在探索 Elixir/Erlang 热重载并尝试了解 Erlang 热重载的工作原理。

一些post give a glance of hot swap and here, from elixir,给出了热插拔的关键步骤。

此外,我尝试使用著名的 tcp 池库 Erlang ranch 来了解热交换如何在开发和部署环境中保持 tcp 连接。代码是at github([=34=里面有一些中文,随便用mix run或者iex -S mixtelnet localhost 8000测试一下)。

热重载的一个非常有影响的事情是当持有代码的进程被删除时,进程将被杀死。在这个阶段,我应该给它一个恢复策略或者确保代码在做热时不能被删除 swap.I 认为一个好的做法是将逻辑代码删除到另一个文件,该文件与套接字连接循环代码不同。

我比较困惑的是,EVM是如何识别进程中的代码版本,并在更新新代码时去除旧版本的?

我经常听说 OTP 有助于热重载?
这个 document 描述了升级的步骤,它如何处理 运行 环境中的热重载?

谢谢。

下一个模块是超级基础服务器,用于说明没有 OTP 机制的代码更改。答案仅针对 erlang 代码。

-module (modtest).

-export ([init/0,loop/1]).

init() ->
    spawn(?MODULE, loop, [version()]).

loop(Version) ->
    receive
        reload ->
            io:format("external call state is ~p, current version is ~p~n",[Version,version()]),
            ?MODULE:loop(version());
        stop ->
            io:format("stopped~n");
        Message ->
            io:format("receive message ~p~n---> local call state is ~p, current version is ~p~n",[Message,Version,version()]),
            loop(version())
    after 10000 ->
        io:format("timeout, state is ~p, current version is ~p~n",[Version,version()]),
        loop(version())
    end.

version() -> version1.

首先试用版本1中的模块

1> c(modtest).
{ok,modtest}
2> P = modtest:init().
<0.66.0>
timeout, state is version1, current version is version1
3> P! message1.
receive message message1
---> local call state is version1, current version is version1
message1
timeout, state is version1, current version is version1
4> P ! reload.
external call state is version1, current version is version1
reload
timeout, state is version1, current version is version1

接下来,进行巨大的进化

version() -> version2.

在 VM 外部编译模块并返回到 运行 应用程序

5> % compile outside version 2
timeout, state is version1, current version is version1
5> P! message1.               
receive message message1
---> local call state is version1, current version is version1
message1
6> P ! reload.                
external call state is version1, current version is version1
reload
7> P! message1.               
receive message message1
---> local call state is version1, current version is version1
message1
timeout, state is version1, current version is version1

没有任何反应,模块没有自动加载,让我们在VM中加载模块

8> % load new version
timeout, state is version1, current version is version1
8> l(modtest).
{module,modtest}
9> P! message1.      
receive message message1
---> local call state is version1, current version is version1
message1
10> P ! reload.       
external call state is version1, current version is version1
reload
11> P! message1.
receive message message1
---> local call state is version1, current version is version2
message1
12> P! message1.
receive message message1
---> local call state is version2, current version is version2
message1
timeout, state is version2, current version is version2
13> P! stop.   
stopped
stop
14>

很好,新代码已在模块中首次 "fully qualified" 调用后更新, 不幸的是,您无法控制何时考虑新代码。在示例中,即使 有一个重新加载功能,新代码在下一个循环中使用,如果需要对状态数据进行任何修改,则为时已晚。 下一个代码使用中间完全限定调用以允许修改状态数据。

-module (modtest).

-export ([init/0,loop/1,code_change/1]).

init() ->
    spawn(?MODULE, loop, [version()]).

loop(Version) ->
    receive
        reload ->
            NewVersion = ?MODULE:code_change(Version),
            io:format("external call state is ~p, current version is ~p~n",[Version,NewVersion]),
            ?MODULE:loop(NewVersion);
        stop ->
            io:format("stopped~n");
        Message ->
            io:format("receive message ~p~n---> local call state is ~p, current version is ~p~n",[Message,Version,version()]),
            loop(version())
    after 10000 ->
        io:format("timeout, state is ~p, current version is ~p~n",[Version,version()]),
        loop(version())
    end.

version() -> version3.

code_change(Version) ->
    io:format("it is possible here to do any action on the state: ~p before the code change is completed~n",[Version]),
    % It is possible to have different adaptation depending on the current version
    version().

在 VM 中检查这个新版本

1> c(modtest).
{ok,modtest}
2> P = modtest:init().
<0.66.0>
3> P ! message.
receive message message
---> local call state is version3, current version is version3
message
4> P ! message.
receive message message
---> local call state is version3, current version is version3
message
5> P ! reload.
it is possible here to do any action on the state: version3 before the code change is completed
reload
external call state is version3, current version is version3
6> P ! reload.
it is possible here to do any action on the state: version3 before the code change is completed
reload
external call state is version3, current version is version3
timeout, state is version3, current version is version3
7> % new version

做一个新版本

...
version() -> version4.
...

然后返回虚拟机

7> c(modtest).        
{ok,modtest}
timeout, state is version3, current version is version3
8> P ! message.
receive message message
---> local call state is version3, current version is version3
message
9> P ! message.
receive message message
---> local call state is version3, current version is version3
message
10> P ! reload.  
it is possible here to do any action on the state: version3 before the code change is completed
reload
external call state is version3, current version is version4
11> P ! message.
receive message message
---> local call state is version4, current version is version4
message
12> P ! stop.   
stopped
stop
13>

很好,按预期工作。但是仍然有一个很大的限制,"server" 不能使用任何其他完全限定的调用,否则,没有任何保证 加载新代码后将立即调用函数 code_change。

这是 OTP 在版本升级或降级中带来的行为(参见 release_handling)。