• [Erlang10]为什么热更新时,Shell执行2次l(Module)后会把原来用到Module的进程 kill?


    0. 问题引入:

       -module(hot_code_server).
       -compile(export_all).
        start() –>
          erlang:register(?MODULE,
             erlang:spawn_link(fun() –>
                  loop()
                 end)).
    
        loop() –>
          receive
              stop –>
                  io:format("stop~n");
             Msg –>
                 io:format("Recv:~p~n",[Msg]),
                 loop()
          end.

    在shell中执行:

       101> hot_code_server:start().
            true
       102> whereis(hot_code_server).
           <0.191.0>
       103> l(hot_code_server).      
          {module,hot_code_server}
       104> l(hot_code_server).      
          ** exception exit: killed

    试想一下,如果你的进程没有放在监控监控树(supervisor)下,那么在2次热更新后就直接被kill掉,多么可怕。。。。

    为了清楚这个问题,先来看看Erlang的代码更新原理:

    1. Erlang热更新:

    Erlang的热更新是用code_sever.erl模块来管理的,code_server基于VM进程,用一个private Ets Table来管理代码,同一个模块最多可以在code_server里面有2个不同的版本,运行里可以随意切换这2个不同的版本,当你使用c(Module)来编译Module时,新版本会自动的加载

    【调用code: purge(Module),code: load_file(Module).】。

    Tip: 必须了解的概念

      Erlang函数有local call和external call的区别,Local call 就是在函数在被定义的模块里面被调用,可以直接被调用: Func(Args); External call 就是显式的使用Module:Func(Args)来调用或import别的模块进来的调用.

    当同一个模块有2个版本被加载里,所有的local call都可以工作在当前版本状态,但是:external call只能调用到最新的版本!【所以你在模块里面使用Func(Args)和Moudle:Func(Args)还是有很大区别的!特别是在更新模块

    验证如下:

        -define(version,1).
        func_test(Name) –>
          erlang:register(Name,erlang:spawn_link(fun() -> print(Name) end )).
    
        print(Name) –>
           receive
              Msg –>
                 io:format("Name:~p Msg: ~p   Version:~p ~n",[Name,Msg,?version]),
                 ?MODULE:print(Name)
           end.

    Shell中调用如下:

       39> c(hot_code_server).
           {ok,hot_code_server}
       40> hot_code_server:func_test(test1).
           true
       41> erlang:send(test1,got). 
           Name:test1 Msg: got   Version:1

    然后修改代码如下:

    -define(version,2).

    Shell中再更新一次:【注意这时的test1进程还在运行中

       42> c(hot_code_server).
           {ok,hot_code_server}
       43> erlang:send(test1,got).
           Name:test1 Msg: got1   Version:1
    got1
       44> erlang:send(test1,got2).
           Name:test1 Msg: got2   Version:2
    got2

    可以看到,当test1进程接收到第一个信息got1时,还是使用的old code,但这个消息进入新一轮尾递归时调用的是?MODULE: print(Name),调用new code,所以就在下一轮时变成了version:2啦。

    那么把代码中的?MODULE: print(Name),改成print(Name)测试下 local call会怎么样?

        -define(version,1).
        func_test(Name) –>
          erlang:register(Name,erlang:spawn_link(fun() -> print(Name) end )).
    
        print(Name) –>
           receive
              Msg –>
                 io:format("Name:~p Msg: ~p   Version:~p ~n",[Name,Msg,?version]),
                 print(Name)
           end.

    Shell中重新更新一次:

       47> c(hot_code_server).
           {ok,hot_code_server}
       48> hot_code_server:func_test(test2).
           true
       49> erlang:send(test2,got).
           Name:test2 Msg: got   Version:1 
           got

    再次把:

    -define(version,2).

    Shell中再更新一次:【注意这时的test2进程还在运行中

       50> c(hot_code_server).
           {ok,hot_code_server}
       51> erlang:send(test2,got).
           Name:test2 Msg: got1   Version:1 
    got1
    52> erlang:send(test1,got2). Name:test2 Msg: got2 Version:1 got2

    无论再对test2发多少信息,得到的version都是1,是old code,因为这个进程没有结束过,一直调用的old code,但是如果你现在新起进程test3,那么这个print(Name)调用的就是new code,也就是说是version 2啦。

    结论:

    1. 使用?MODULE:Func(Args)调用,永远调用的最新代码;

    2. 使用Func(Args)调用的进程,并且进程已在运行中,那么就会一直使用原来的old code,无论是不是更新了new code;

    3. 使用Func(Args)调用的进程,更新了new code后,再用这个函数新启动一个进程,那么这个新进程的code就是new code。

    接下来:一起回到开始的问题:为什么2次l(Module)或c(Module)后会把运行状态还在调用这个Module中的进程kill掉呢?

    2. l(Module)原理:

    c.erl里面有它的实现:就是先清理旧版本,后加载(把当前进行的版本变成旧版本,新版本为最新改动的版本)。

    image

    为什么会被kill掉,你可以查看相关code: purge(Mod)的实现code_server: do_purge(Mod)

    image

    3. 结论:

    1. l(Mod)一次时,把运行中的代码变成old code【Version1】,最新改动的Mod变成new code【Version2】,

    2. l(Mod)第二次时,把【Version1】清理,并把所有用到Version1的进程kill,把Version2变成old code,并把最新改动【Version3】更新为new code.

    3. 以上就是在Shell中更新时,会把进程给kill的原因啦。

    4. 题外话:

    4.1 Erlang三种装载模块的方法:

    1) 直接调用模块中的函数,code server进程将会搜索相应的beam文件,然后装载之;

    2) 编译该模块(相应的函数是compile:file(Module))后会自动装载编译好的模块,在shell中是通过命令c(Module);

    3) 显式的装载模块,通过调用code:load_file(Module)转载指定的模块,在本机上可以通过命令l(Module)装载模块,在网络中可以通过命令nl(Module)将模块装载到各个节点上。

    4.2 怎么判定一个函数是local call 还是 exteral call:

     

    %%使用erlc -S opcode_test.erl 生成opcode_test.S文件 
     -module(opcode_test).
     -export([test/0]). 
     -import(hello, [hello/0]). 
     test2() –> 
        ok.
     test() –> 
        test2(), 
        ?MODULE:test2(), 
        hello(), 
        hello:hello(), 
        ok.

     

           生成的opcode_test.S文件部分如下:

    {function, test, 0, 4}.
         {label,3}.
           {line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
                           19}]}.
           {func_info,{atom,opcode_test},{atom,test},0}.
        {label,4}.
          {allocate,0,0}.
          {line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
                          20}]}.
          {call,0,{f,2}}.   %%这是local call: test2(),
          {line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
                          21}]}.
          {call_ext,0,{extfunc,opcode_test,test2,0}}.  %%这是exteral call: ?MODULE:test2(). 
          {line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
                          22}]}.
          {call_ext,0,{extfunc,hello,hello,0}}.
          {line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
                          23}]}.
          {call_ext,0,{extfunc,hello,hello,0}}.
        {move,{atom,ok},{x,0}}.
        {deallocate,0}.
        return.

    相关查阅文章:

    1. Learn Some Erlang: http://learnyousomeerlang.com/designing-a-concurrent-application#hot-code-loving

    2. Erlang语法对应的opcode: http://mryufeng.iteye.com/blog/472840


    以上说的都是指Module级别的热更新,如果你想了解产品级别的请看【还没用到过……】

    Release Handle : http://www.erlang.org/doc/design_principles/release_handling.html

  • 相关阅读:
    mysql 存储过程 异常处理机制
    Maven 私服打包
    Flink(2):Flink的Source源
    Flink(1):Flink的基础案例
    最后一课
    我的获奖记录及 Important Dates in OI
    目录
    入坑 OI 三周年之际的一些感想
    洛谷 P3781
    Atcoder Typical DP Contest S
  • 原文地址:https://www.cnblogs.com/zhongwencool/p/erlang_hot_code.html
Copyright © 2020-2023  润新知