【问题标题】:Can I catch an external exit in Erlang?我可以在 Erlang 中找到外部出口吗?
【发布时间】:2013-04-20 06:01:10
【问题描述】:

我有两个进程链接;假设他们是ABA 设置为陷阱出口。如果有人在上面调用exit/2,我希望能够恢复B 的一部分进程数据,例如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,但在我的原始程序中,spawn 和链接之间有代码,所以我以这种方式模拟了这个示例代码。)

现在当我运行代码时,我得到了这个。

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

【问题讨论】:

    标签: concurrency erlang try-catch exit


    【解决方案1】:

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

    【讨论】:

    • 我的代码遇到了一些问题:我似乎无法在try 块中导出我的计算结果(使用of)以调用递归循环.我收到了unsafe 警告。
    • 你的意思是像try Res = do_stuff ..... end这样的东西,Res在外面是安全的吗?是的,你需要做类似Res = try do_stuff .... end 的事情。然后使用 Res。块的最后一个语句返回可以分配和使用的值。这适用于 try、甚至 catch、case 和 if 中的 stmnts。
    • 谢谢@vinod 我不知道该规则也适用于try!我只是没有在 Learn You Some Erlang 中看到示例。
    • catch 块可以捕获 throw、error 和 exit。你可以在你的 do_stuff 中调用 exit 错误或完成任务,这很可能你可能不会。但如果你这样做了,你可以再次以你想要的状态退出。是的,修改代码是一个更好的选择,因为我给出的只是一个关于如何接收退出信号的例子。
    • 还有一点,上面的规则也适用于Erlang中的receive,anything和everything。糟糕,除了 throw、error、exit 和 stuffs
    【解决方案2】:

    简短的回答:您也应该在b_main/2 中执行trap_exit,并接收{'EXIT', ...} 消息。 @vinod 在我尝试之前对其进行了概述。相反,我会尝试解释一些关于正在发生的事情。

    如果进程正在捕获退出并死掉,例如当有人调用exit(Pid, die) 或某个链接进程以exit(die) 结束时,它会在其邮箱中收到{'EXIT', ...} 消息而不是以同样的理由默默地死去。它是运行时系统向每个链接的进程发出退出信号,并且可以捕获它而不是死亡。

    这条规则的唯一例外是当exit(Pid, kill)调用发出时,那么无论进程是否捕获退出,它都会因kill而死。

    因此,为了避免外部退出信号导致的静默死亡,进程必须捕获退出。同样,如果该过程想知道为什么与他有关联的人刚刚死亡并采取一些措施来恢复,那么该过程必须设置出口。 每个被捕获的退出信号在进程邮箱中显示为一条消息。

    因此,您的try ... catch exit:_ -&gt; ... 语句在捕获出口方面没有任何影响。

    通常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
    ...
    

    您可能会看到某些进程已链接,正在捕获退出并且拒绝正常退出。这是出乎意料的,并且显然具有潜在的危险。由于链接是可传递的,因此它可能会破坏向一组链接进程发出的退出链。

    book chapter 中有很多微妙的特色。

    【讨论】:

    • 我真的很喜欢这个解释; @vinod 的代码很棒,但知道发生了什么对我的原始程序有很大帮助。
    猜你喜欢
    • 2021-08-12
    • 2010-09-13
    • 2021-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-29
    • 2012-06-25
    • 2016-10-26
    相关资源
    最近更新 更多