Можно ли поймать внешний выход в Эрланге?

У меня есть два связанных процесса; скажем, они A а также B, с A установить ловушку выходов. Я хочу быть в состоянии восстановить часть Bобрабатывать данные, если кто-то звонит exit/2 на нем, например exit(B, diediedie),

В Bмодуль, назовем его bmod.erlУ меня есть код, который выглядит следующим образом:

-module(bmod).
-export([b_start/2]).

b_start(A, X) ->
    spawn(fun() -> b_main(A, X) end).

b_main(A, X) ->
      try
        A ! {self(), doing_stuff},
        do_stuff()
      catch
        exit:_ -> exit({terminated, X})
      end,
      b_main(A, X).

do_stuff() -> io:format("doing stuff.~n",[]).

И в Aмодуль, назовем его amod.erlУ меня есть код, который выглядит следующим образом:

-module(amod).
-export([a_start/0]).

a_start() ->
  process_flag(trap_exit, true),
  link(bmod:b_start(self(), some_stuff_to_do)),
  a_main().

a_main() ->
  receive
    {Pid, doing_stuff} ->
      io:format("Process ~p did stuff.~n",[Pid]),
      exit(Pid, diediedie),
      a_main();
    {'EXIT', Pid, {terminated, X}} ->
      io:format("Process ~p was terminated, had ~p.~n", [Pid,X]),
      fine;
    {'EXIT', Pid, _Reason} ->
      io:format("Process ~p was terminated, can't find what it had.~n", [Pid]),
      woops
  end.

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

Теперь, когда я запускаю код, я получаю это.

2> c(amod).
{ok,amod}
3> c(bmod).
{ok,bmod}
4> amod:a_start().
doing stuff.
Process <0.44.0> did stuff.
doing stuff.
Process <0.44.0> did stuff.
Process <0.44.0> was terminated, can't find what it had.
woops
5> 

Как я могу получить b_main() поймать этот внешний выход, чтобы он мог сообщить о своем состоянии X?

2 ответа

Решение

За b_main() чтобы поймать внешний выход, он должен перехватить выход, вызвав process_flag(trap_exit, true), Это приведет к сообщению в процесс, где он может выйти с состоянием X, Код как ниже

b_start(A, X) ->
    spawn(fun() -> process_flag(trap_exit, true), b_main(A, X) end).

b_main(A, X) ->
    try
        A ! {self(), doing_stuff},
        do_stuff()
    catch
        exit:_ -> 
            io:format("exit inside do_stuff() . ~n"),
            exit({terminated, X})
    end,

    receive
        {'EXIT',Pid, Reason} ->
            io:format("Process received exit ~p ~p.~n",[Pid, Reason]),
            exit({terminated, X})
    after 0 ->
            ok
    end,
    b_main(A, X).

Краткий ответ: вы должны сделать trap_exit в b_main/2 а так и получи {'EXIT', ...} Сообщения. Это было изложено @vinod прямо перед моей попыткой. Вместо этого я попытаюсь объяснить кое-что о том, что происходит.

Если процесс перехватывает выходы и он умирает, например, когда кто-то звонил exit(Pid, die) или какой-то связанный процесс заканчивается exit(die) тогда он получит {'EXIT', ...} сообщение в свой почтовый ящик вместо того, чтобы молча умереть по той же причине. Это система времени выполнения, которая выдает сигналы на выход каждому связанному процессу, и его можно перехватить вместо смерти.

Единственное исключение из этого правила - это когда exit(Pid, kill) вызов выполнен, тогда независимо от того, перехватывает ли процесс выходы или нет, он просто умирает с разумом kill,

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

Таким образом, нет никакого эффекта от вашего try ... catch exit:_ -> ... заявление в вопросе улавливания выходов.

В общем-то trap_exit считается плохой практикой. Есть простой пример, который показывает, почему:

18> self().
<0.42.0>
19> Pid = spawn_link(fun () -> process_flag(trap_exit, true), 
  Loop = fun (F) -> receive Any -> io:format("Any: ~p~n", [Any]) end, F(F) end, 
    Loop(Loop) end).
<0.58.0>
20> exit(Pid, grenade).                                         
Any: {'EXIT',<0.42.0>,grenade}
true
21> exit(Pid, grenade).
Any: {'EXIT',<0.42.0>,grenade}
true
...

Как вы можете видеть, какой-то процесс связан, перехватывает выходы и отказывается выходить нормально. Это неожиданно и, очевидно, потенциально опасно. И это может нарушить цепочку выходов, созданных для набора связанных процессов, поскольку ссылки являются транзитивными.

Есть множество тонких особенностей, которые чудесно изложены в этой главе книги.

Другие вопросы по тегам