Как я могу узнать, когда последний цикл моего процесса перезапущен супервизором в erlang

У меня есть руководитель simple_one_for_one, у которого gen_fsm детей. Я хочу, чтобы каждый дочерний элемент gen_fsm отправлял сообщение только в последний раз, когда он завершался. Есть ли способ узнать, когда последний цикл?

вот мой куратор:

-module(data_sup).

-behaviour(supervisor).

%% API
-export([start_link/0,create_bot/3]).

%% Supervisor callbacks
-export([init/1]).

%%-compile(export_all).


%%%===================================================================
%%% API functions
%%%===================================================================

start_link() ->
  supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
 RestartStrategy = {simple_one_for_one, 0, 1},
 ChildSpec = {cs_fsm, {cs_fsm, start_link, []},
 permanent, 2000, worker, [cs_fsm]},
 Children = [ChildSpec],
 {ok, {RestartStrategy, Children}}.

create_bot(BotId, CNPJ,Pid) ->
  supervisor:start_child(?MODULE, [BotId, CNPJ, Pid]).

Pid — это PID процесса, который запускает супервизор и отдает приказы запускать дочерние процессы.

-module(cs_fsm).

-behaviour(gen_fsm).
-compile(export_all).

-define(SERVER, ?MODULE).
-define(TIMEOUT, 5000).

-record(params, {botId, cnpj, executionId, pid}).

%%%===================================================================
%%% API
%%%===================================================================

start_link(BotId, CNPJ, Pid) ->
  io:format("start_link...~n"),
  Params = #params{botId = BotId, cnpj = CNPJ, pid = Pid},
  gen_fsm:start_link(?MODULE, Params, []).


%%%===================================================================
%%% gen_fsm callbacks
%%%===================================================================

init(Params) ->
  io:format("initializing~n"),
  process_flag(trap_exit, true),
  {ok, requesting_execution, Params, 0}.

requesting_execution(timeout,Params) ->
  io:format("erqusting execution"),
  {next_state, finished, Params,?TIMEOUT}.

finished(timeout, Params) ->
  io:format("finished :)~n"),
  {stop, normal, Params}.

terminate(shutdown, _StateName, Params) ->
  Params#params.pid ! {terminated, self(),Params},
  ok;

terminate(_Reason, _StateName, Params) ->
  ok.

я хочу сказать, что если процесс дает сбой в любом из состояний, он должен отправлять сообщение, только если это последний раз, когда он перезапускается супервизором (в соответствии с его стратегией перезапуска).

Если gen_fsm дает сбой, перезапускается ли он из того же состояния с теми же данными состояния? Если нет, как я могу заставить это произойти?


person dina    schedule 25.02.2016    source источник
comment
Хорошо, теперь, после вашего дальнейшего объяснения, я понимаю, что речь идет не о завершении gen_fsm из-за какого-то ожидаемого триггера, а о ненормальном завершении, потому что процесс умер? Вы просто хотите подсчитать, когда супервизор перезапустит cs_fsm, и отправить сообщение, когда супервизор больше не собирается его перезапускать, на случай, если он снова умрет?   -  person Greg    schedule 25.02.2016
comment
да. Есть ли способ сделать это?   -  person dina    schedule 25.02.2016
comment
и чтобы сделать мой вопрос более общим: может ли супервизор сделать другое завершение для своего ребенка в последний раз, когда он перезапустил его?   -  person dina    schedule 25.02.2016
comment
Как супервизор узнает, что он перезапустил дочерний элемент в последний раз? До тех пор, пока ребенок не выйдет из строя, супервизор не может рассчитать, что это последний сбой, потому что он не знает, когда ребенок вообще выйдет из строя. Только после аварии супервизор может вычесть, разрешено ли перезапускать дочерний элемент еще раз или нет. Смотрите мой более полный ответ в редактировании. Кстати, зачем вам знать, что ребенок был перезапущен в последний раз? Если супервизор не может перезапустить дочерний процесс из-за слишком большого количества сбоев, это неисправимая ситуация, и супервизор сам должен умереть!   -  person Greg    schedule 25.02.2016
comment
Я хочу, чтобы ребенок уведомлял о причине сбоя определенного процесса, но только в последний раз, когда он сбой. Может быть, мне следует иметь счетчик в записи состояния (поэтому я не использую таблицу ets), и я увеличим его в завершении, и тогда я смогу узнать, сколько раз он выполнялся. Кстати, когда мой дочерний элемент gen_fsm дает сбой, он перезапускается в своем предыдущем состоянии?   -  person dina    schedule 25.02.2016
comment
Ответил на ваш последний вопрос в другом редактировании. В общем, было бы лучше предотвратить сбой или как-то обработать его внутри процесса gen_fsm (я привел два примера, как это сделать), чем допустить сбой gen_fsm и пытаться добавить некоторую логику для обработки этого супервизору.   -  person Greg    schedule 25.02.2016


Ответы (1)


Вы можете добавить отправку сообщения в функцию Module:terminate/3, которая вызывается, когда одна из функций StateName возвращает {stop,Reason,NewStateData}, чтобы указать, что gen_fsm следует остановить.

gen_fsm — это конечный автомат, поэтому вы сами решаете, как он переходит между состояниями. Что-то, что запускает последний цикл, также может установить что-то в StateData, которое передается в Module:StateName/3, чтобы функция, которая обрабатывает состояние, знала, что это последний цикл. Трудно дать более конкретный ответ, если вы не предоставите код, который мы могли бы проанализировать и прокомментировать.

ИЗМЕНИТЬ после дальнейших разъяснений:

Supervisor не уведомляет своих дочерних элементов, когда он перезапустил их, а также не может уведомить дочерний элемент о том, что это последний перезапуск. Это позже просто потому, что он не знает, что он будет последним, пока процесс супервизора действительно не выйдет из строя еще раз, что супервизор не может предсказать. Только после того, как ребенок разбился, супервизор может подсчитать, сколько раз ребенок разбился за определенный период времени, и разрешено ли ему перезапустить ребенка еще раз, или это был последний перезапуск, и теперь пришло время умереть супервизору.

Однако ничто не мешает ребенку зарегистрироваться, т.е. в таблице ETS, сколько раз она была перезапущена. Но это, конечно, не поможет с вычетом того, какой перезапуск является последним.

Редактировать 2:

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

Обратите внимание, что сбой — это исключительная ситуация, и не всегда возможно восстановить состояние, поскольку сбой мог привести к повреждению состояния. Вместо того, чтобы пытаться восстановить состояние или спрашивать супервизора, когда это будет сделано, перезапустив дочерний элемент, почему бы не предотвратить сбой в первую очередь? У вас есть два варианта:

I. Используйте try/catch, чтобы перехватывать любые исключительные ситуации и действовать соответственно. Можно отловить любую ошибку, которая в противном случае приведет к сбою процесса и заставит супервизора перезапустить его. Вы можете добавить try/catch к любой функции входа внутри процесса gen_fsm, чтобы любое состояние ошибки было обнаружено до того, как оно приведет к сбою сервера. См. пример функции 1 или пример функции 2:

read() ->
    try
        try_home() orelse try_path(?MAIN_CFG) orelse
            begin io:format("Some Error", []) end
    catch
        throw:Term -> {error, Term}
    end.

try_read(Path) ->
    try
        file:consult(Path)
    catch
        error:Error -> {error, Error}
    end.

II. Создайте новый процесс для обработки задания и перехватите EXIT сигналов, когда процесс умирает. Это позволяет gen_fsm обрабатывать задание асинхронно и обрабатывать любые ошибки особым образом (не обязательно перезапуская процесс, как это сделал бы супервизор). В этом разделе под названием Обработка ошибок объясняется, как перехватывать exit сигналы от дочерних процессов. А это пример перехвата сигналов в gen_server. Проверьте функцию handle_info, которая содержит несколько предложений для перехвата различных типов сообщений EXIT от дочерних процессов.

init([Cfg, Id, Mode]) ->
    process_flag(trap_exit, true),
    (...)


handle_info({'EXIT', _Pid, normal}, State) ->
    {noreply, State};
handle_info({'EXIT', _Pid, noproc}, State) ->
    {noreply, State};
handle_info({'EXIT', Pid, Reason}, State) ->
    log_exit(Pid, Reason),
    check_done(error, Pid, State);
handle_info(_, State) ->
    {noreply, State}.
person Greg    schedule 25.02.2016
comment
Благодарность!! На самом деле я использую таблицу ets в дочерних процессах. Что касается обработки ошибок, я предпочитаю метод «пусть произойдет сбой». - person dina; 28.02.2016
comment
Философия Let It Crash не означает, что вы не должны ловить ошибки, когда можете. Это только означает, что вы не должны программировать в обороне. Лучше использовать try-catch, чем пытаться добавить больше логики для обработки сбоев. См. этот слайд: qconlondon.com/ london-2011/qconlondon.com/dl/qcon-london-2011/ Это общий комментарий, потому что я не знаю точного дизайна вашей архитектуры :) - person Greg; 28.02.2016