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!
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With