tags:

views:

467

answers:

4

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!

+2  A: 

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 itself asap, since it subscribes to system events about code upgrades for you.

Christian
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?
jeffreyveon
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.
I GIVE TERRIBLE ADVICE
I don't think any of this works without using proper appup and relup scripts...
Zed
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.
archaelus
+1  A: 

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

Tim
Thanks, I did read up on the different OTP behaviors, I just could not find any information related to this.
jeffreyveon
A: 

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.

legoscia
+14  A: 

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
Zed
In version 1 of z.erl, init should return {ok, {tschak, 0}} as the initial state.
jmah
Wow, thanks! Fixed.
Zed