2009-12-03 64 views
30

我正在尋找在gen_server上使用Erlang的熱代碼交換功能,以便我不必重新啓動它。我應該怎麼做?當我搜索時,我能找到的只有一篇文章提到我需要使用gen_server:code_change回調。在Erlang的gen_server中實現代碼交換

但是,我無法真正找到任何關於如何使用它的文檔/示例。任何幫助或資源的鏈接非常感謝!

回答

44

正如我已經提到的,正常的升級方式是創建正確的.appup和.relup文件,並讓release_handler完成需要完成的任務。但是,您可以手動執行涉及的步驟,如此處所述。很抱歉,很長的答案。

下面的dummy gen_server實現了一個計數器。舊版本(「0」)僅存儲一個整數作爲狀態,而新版本(「1」)將{tschak,Int}存儲爲狀態。正如我所說,這是一個虛擬的例子。

z.erl(舊):

-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(新):

-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}}. 

啓動外殼,並編譯舊代碼。注意gen_server是以調試跟蹤啓動的。

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 

按預期工作:返回Int,新狀態爲Int + 1。

現在將z.erl替換爲新的,然後執行以下步驟。

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 

你剛剛做了什麼:5:編譯新的代碼。 6:暫停服務器。 7:清除舊代碼(以防萬一)。 8:加載新代碼。 9:模塊'z'的進程'z'中的代碼更改從版本「0」以[]作爲「額外」傳遞給code_change進行調用。 10:恢復服務器。

現在,如果你運行一些測試,你可以看到,服務器用新狀態格式的工作原理:

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 
+1

在z.erl的版本1中,init應該返回{ok,{tschak,0}}作爲初始狀態。 – jmah 2010-01-24 03:28:02

+0

哇,謝謝!固定。 – Zed 2010-01-24 07:42:16

+0

爲什麼要麻煩調用'code:purge'如果虛擬機在加載後使用新版本? – spockwang 2013-11-17 08:01:21

5

您不需要在gen_server行爲中使用該回調。如果通過代碼升級更改狀態的內部表示形式,那麼它就在那裏。

您只需要加載新模塊,運行舊版本的gen_server就會升級,因爲它會調用新模塊。只是如果有必要,你沒有機會改變表示。

+0

當你的意思是「加載新模塊」,你的意思是,我只是重新編譯使用genserver的模塊,並且正在運行的服務器自動升級? – jeffreyveon 2009-12-03 16:02:09

+2

這就是如果你從Erlang shell(比如用c())編譯它會發生什麼。否則使用代碼:load_file/2或代碼:load_binary/2來獲得類似的效果。 – 2009-12-03 16:58:12

+0

如果沒有使用正確的appup和relup腳本,我不認爲這有效...... – Zed 2009-12-03 17:55:08

2

如果您想以正確的方式進行操作,強烈建議,那麼您需要閱讀使用OTP主管和應用程序。

你可以做,而不是在這裏閱讀OTP設計原則用戶指南雪上加霜:

http://www.erlang.org/doc/design_principles/users_guide.html

+0

謝謝,我讀了不同的OTP行爲,我只是找不到任何與此相關的信息。 – jeffreyveon 2009-12-03 16:03:44

3

做到這一點最簡單的方法是更換.beam文件,並在shell中運行l(my_server_module).。這繞過了code_change函數,因此要求狀態的表示沒有改變。

正如已經提到的,正確的方法是使用appup和relup腳本創建新版本。這個新版本隨後與release_handler一起安裝。