Achieving code swapping in Erlang's gen_server

前端 未结 5 757
北恋
北恋 2020-12-04 09:02

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

相关标签:
5条回答
  • 2020-12-04 09:29

    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.

    0 讨论(0)
  • 2020-12-04 09:43

    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/

    0 讨论(0)
  • 2020-12-04 09:46

    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
    
    0 讨论(0)
  • 2020-12-04 09:50

    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.

    0 讨论(0)
  • 2020-12-04 09:52

    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

    0 讨论(0)
提交回复
热议问题