Achieving code swapping in Erlang's gen_server
Asked Answered
G

5

30

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!

Gylys answered 3/12, 2009 at 15:37 Comment(0)
M
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
Millhon answered 3/12, 2009 at 18:31 Comment(3)
In version 1 of z.erl, init should return {ok, {tschak, 0}} as the initial state.Adelbert
Why bother calling code:purge if the VM uses the new version once it is loaded?Elsworth
Uh. How come sys:change_code requires process to be stopped? If I had just put ?MODULE:loop (instead of using gen_server) in my code somewhere, that wouldn't be necessary...Houseline
S
5

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.

Stahl answered 3/12, 2009 at 15:49 Comment(5)
When you mean by "load the new module", do you mean, I just recompile the module that uses genserver, and the running server automatically upgrade itself?Gylys
That's what should happen if you compile it from the Erlang shell (say with c()). Otherwise use code:load_file/2 or code:load_binary/2 to get similar effects.Kex
If you load a new version of the module without suspending the gen_server process, then the next time a callback is run, it'll run using the new code and the old state. All calls into your callback module are external and thus always use the newest loaded module version. Hence the suspend, load, change_code, resume upgrade process. There's no magic code upgrade event subscription going on.Boettcher
@Boettcher Ugg... So, if I get this right, if I were NOT using gen_server, then I wouldn't need to suspend, because then I'd be able to keep all calls local (except for ?MODULE:code_change). This gen_server code changes seem super uncool vs. the "naive" way >:(Houseline
Yep - this interaction is kinda subtle and not really idea, but the benefits of using the gen_server framework far outweigh this hassle. I strongly recommend using gen_server and dealing with the suspend, load, change_code, resume cycle. (Which you only need if you change #state{} - you can just do load if you didn't change #state{})Boettcher
S
3

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.

Soothe answered 3/12, 2009 at 18:20 Comment(0)
I
2

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

Imprison answered 3/12, 2009 at 15:49 Comment(1)
Thanks, I did read up on the different OTP behaviors, I just could not find any information related to this.Gylys
M
0

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/

Mcphail answered 3/9, 2016 at 14:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.