gen_X 系モジュールの code_change を appup とは関係なく呼ぶ

gen_server や gen_fsm 等が保持する State を、サーバ停止せずに(正確には suspend するけど)入れ替える方法のメモ。
OTP から逸脱している感があるけれど、Release Handling は無視の方向で(w;
ちなみに、下記の内容を試す際は、こちら をどうぞ。

まずは、下記のようなテスト用の gen_server を準備。

-module(code_change_test).
-behaviour(gen_server).

-export([start/0, stop/0]).
-export([state/0]).

-export([
  init/1,
  handle_call/3, handle_cast/2, handle_info/2,
  terminate/2, code_change/3
]).

start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []).
stop()  -> gen_server:cast(?MODULE, stop).
state() -> gen_server:call(?MODULE, state).

init(_Args) ->
  {ok, ver1}.

handle_call(state, _From, State) ->
  io:fwrite("State=~p~n", [State]),
  {reply, State, State};
handle_call(_Message, _From, State) ->
  {reply, ok, State}.

handle_cast(stop, State) ->
  {stop, normal, State};
handle_cast(_Message, State) ->
  {noreply, State}.

handle_info(_Info, State) -> {noreply, State}.

terminate(normal, _State) -> ok;
terminate(_, _) -> ok.

code_change(_OldVsn, State, _Extra) ->
  {ok, State}.

code_change_test の動作確認。

$ erl
1> c(code_change_test).
{ok,code_change_test}
2> {ok, Pid} = code_change_test:start().
{ok,<0.38.0>}
3> code_change_test:state().
State=ver1
ver1

次に、code_change_test の Code を下記のように書き換える。

-module(code_change_test).
-behaviour(gen_server).

-export([start/0, stop/0]).
-export([state/0]).

-export([
    init/1,
    handle_call/3, handle_cast/2, handle_info/2,
    terminate/2, code_change/3
]).

start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []).
stop()  -> gen_server:cast(?MODULE, stop).
state() -> gen_server:call(?MODULE, state).

init(_Args) ->
  {ok, {ver2, [foo, bar]}}.

handle_call(state, _From, {ver2, Args}=State) ->
  io:fwrite("State=~p~n", [Args]),
  {reply, Args, State};
handle_call(_Message, _From, State) ->
  {reply, ok, State}.

handle_cast(stop, State) ->
  {stop, normal, State};
handle_cast(_Message, State) ->
  {noreply, State}.

handle_info(_Info, State) -> {noreply, State}.

terminate(normal, _State) -> ok;
terminate(_, _) -> ok.

code_change(OldVsn, State, Extra) ->
  io:fwrite(
    lists:foldl(
      fun (Name, Format) -> Format ++ Name ++ "=~p~n" end,
      "code_change~n", ["OldVsn", "State", "Extra"]
    ),
    [OldVsn, State, Extra]
  ),
  {ok, {ver2, [foo, bar]}}.

code_change_test の Code と State の形式を入れ替える。

4> c(code_change_test).
{ok,code_change_test}
5> sys:suspend(Pid).
ok
6> sys:change_code(Pid, code_change_test, 1, {}).
code_change
OldVsn=1
State=ver1
Extra={}
ok
7> sys:resume(Pid).
ok

入れ替わったか確認。

8> code_change_test:state().
State=[foo,bar]
[foo,bar]

suspend 中に受け取ったメッセージはスタックされ、resume された瞬間に一気に実行される。