Достижение замены кода в gen_server Erlang

Я хочу использовать функцию горячей замены кода Erlang на gen_server, чтобы мне не приходилось ее перезапускать. Как мне это сделать? Когда я искал, все, что я мог найти, это одна статья, в которой упоминалось, что мне нужно использовать обратный вызов gen_server:code_change.

Однако я не смог найти никакой документации/примеров того, как это использовать. Любая помощь или ссылки на ресурсы с благодарностью!


person jeffreyveon    schedule 03.12.2009    source источник


Ответы (5)


Как я уже упоминал, обычный способ обновления заключается в создании соответствующих файлов .appup и .relup, и пусть release_handler сделает то, что нужно сделать. Однако вы можете вручную выполнить соответствующие шаги, как описано здесь. Извините за длинный ответ.

Следующий фиктивный 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
person Zed    schedule 03.12.2009
comment
В версии 1 z.erl init должен возвращать {ok, {tschak, 0}} в качестве начального состояния. - person jmah; 24.01.2010
comment
Зачем вызывать code:purge, если виртуальная машина использует новую версию после ее загрузки? - person spockwang; 17.11.2013
comment
Эм-м-м. Почему sys:change_code требует остановки процесса? Если бы я просто поместил ?MODULE:loop (вместо использования gen_server) где-нибудь в своем коде, в этом не было бы необходимости... - person allyourcode; 10.02.2015

Вам не нужно использовать этот обратный вызов в поведении gen_server. Он есть, если вы измените внутреннее представление состояния при обновлении кода.

Вам нужно только загрузить новый модуль, и gen_server, работающий со старой версией, обновится, так как он вызывает новый модуль. Просто у вас нет возможности изменить представление, если это необходимо.

person Christian    schedule 03.12.2009
comment
Когда вы имеете в виду загрузку нового модуля, вы имеете в виду, что я просто перекомпилирую модуль, использующий genserver, и работающий сервер автоматически обновится? - person jeffreyveon; 03.12.2009
comment
Вот что должно произойти, если вы скомпилируете его из оболочки Erlang (скажем, с помощью c()). В противном случае используйте code:load_file/2 или code:load_binary/2, чтобы получить аналогичные эффекты. - person I GIVE TERRIBLE ADVICE; 03.12.2009
comment
Если вы загрузите новую версию модуля без приостановки процесса gen_server, то при следующем запуске обратного вызова он будет выполняться с использованием нового кода и старого состояния. Все вызовы в ваш модуль обратного вызова являются внешними и поэтому всегда используют самую новую загруженную версию модуля. Следовательно, приостановка, загрузка, изменение_кода, возобновление процесса обновления. Подписка на событие обновления волшебного кода не проводится. - person archaelus; 27.01.2010
comment
@archaelus Ugg ... Итак, если я правильно понял, если бы я НЕ использовал gen_server, мне не нужно было бы приостанавливать работу, потому что тогда я смог бы сохранять все вызовы локальными (кроме ?MODULE:code_change) . Эти изменения кода gen_server кажутся супер некрутыми по сравнению с наивным способом ›:( - person allyourcode; 10.02.2015
comment
Да, это взаимодействие довольно тонкое и на самом деле не идея, но преимущества использования фреймворка gen_server намного перевешивают эти хлопоты. Я настоятельно рекомендую использовать gen_server и иметь дело с циклом приостановки, загрузки, изменения кода и возобновления. (Что вам нужно, только если вы измените #state{} — вы можете просто выполнить загрузку, если вы не изменили #state{}) - person archaelus; 19.02.2015

Самый простой способ сделать это — заменить файл .beam и запустить l(my_server_module). в оболочке. Это обходит функцию code_change и поэтому требует, чтобы представление состояния не изменилось.

Как уже упоминалось, правильный способ сделать это — создать новую версию со сценариями appup и repup. Затем этот новый выпуск устанавливается с помощью release_handler.

person legoscia    schedule 03.12.2009

Если вы хотите сделать это правильно, что настоятельно рекомендуется, вам нужно прочитать об использовании OTP Supervisors и Applications.

Вы могли бы сделать хуже, чем прочитать руководство пользователя OTP Design Principles здесь:

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

person Tim    schedule 03.12.2009
comment
Спасибо, я читал о различном поведении одноразовых паролей, но не смог найти никакой информации, связанной с этим. - person jeffreyveon; 03.12.2009

Если вы используете rebar3, часть этой ручной обработки была автоматизирована (например, генерация приложений и обновлений), вы можете найти дополнительную информацию здесь: http://lrascao.github.io/automatic-release-upgrades-in-erlang/

person lrascao    schedule 03.09.2016