经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Erlang » 查看文章
gen_server terminate与trap_exit
来源:cnblogs  作者:写着写着就懂了  时间:2018/9/25 20:31:47  对本文有异议

不论是新手还是熟手,写gen_server时常会遇到terminate/2,有时执行,有时却不执行的困惑。
比如stackoverflow中的Handling the cleanup of the gen_server state,因为terminate的文档写得比较模糊,并没有给出如何让terminate/2一定会被执行的方案。

为了理顺各种情形,做了个小实验,结论如下:
让进程退出的来源有二种:

  • 内部原因,自己运行完退出或发生异常crash退出。
  • 外部原因,使用erlang:exit/2强制退出正在进行的进程。
gen_server退出原因 启动函数 trap_exit terminate
内部自身发生crash 无关 无关 执行
exit(P,kill) 无关 无关 不执行
exit(P,Reason) 无关 true 执行
exit(P,Reason) 无关 false 不执行
Pid!{'EXIT',F,Reason} gen_server:start_link 无关 执行
Pid!{'EXIT',F,Reason} gen_server:start 无关 不执行
  1. 特别注意kill是非常霸道的exit信号,直接强制退出,不会执行terminate,这也是supervisor在退出brutal_kill方式启动进程所使用的方法。
  2. 我们不执行terminate最常见情况:
  1. 1. 使用监控树把进程挂载在Application下(gen_server:start_link/3-4)。
  2. 2. Application关闭时会调用supervisor:terminate_child/2来依次关闭进程。
  3. 3. terminate_child/2是使用exit(Pid, shutdown)来关闭工作进程。
  4. 4. 所以如果我们trap_exit: false,则不会执行terminate/2
  1. 确保进程执行terminate的方案是init/1中加上process_flag(trap_exit, true)

接下来,我们将分情况一步步分析下。

trap_exit 的作用

  1. erlang:process_flage(trap_exit, true).
  • 设置为false时:
    link的进程 异常 退出(exit(whatever)),本进程也会直接异常退出。
    link的进程 正常 退出(运行结束或使用exit(normal)),则本进程完全没有影响。不会收到任何信息,也不会退出。
  1. 1> erlang:process_flag(trap_exit, false), self().
  2. <0.64.0>
  3. 2> erlang:spawn_link(fun() -> exit(whatever) end). ## 子进程exit的原因除了normal以外的其它原因。
  4. ** exception exit: whatever
  5. 3> self(). ## 父进程也异常退出了,变成了一个新的shell进程。
  6. <0.68.0>
  7. 4> erlang:process_flag(trap_exit,false),self().
  8. <0.68.0>
  9. 5> erlang:spawn_link(fun() -> exit(normal) end). ## 子进程exit的原因为normal
  10. <0.79.0>
  11. 6> erlang:spawn_link(fun() -> {ok, true} end). ## 子进程正常结束,与5)中等同
  12. <0.80.0>
  13. 11> flush(). ##没有收到任何消息
  14. ok
  15. 12> self(). ## 进程没有退出
  16. <0.68.0>
  • 设置为true时,
    link的进程 异常 退出(比如exit(whatever)),那么本进程不会退出,只是会收到{’EXIT‘, FromPid, whatever}的消息。
    link的进程 正常 退出(直接正常结束),那么本进程会收到{'EXIT',FromPid,normal}
  1. 13> erlang:process_flag(trap_exit, true),self().
  2. <0.68.0>
  3. 14> erlang:spawn_link(fun() -> exit(whatever) end).
  4. <0.72.0>
  5. 15> flush().
  6. Shell got {'EXIT',<0.72.0>,whatever}
  7. ok
  8. 16> erlang:spawn_link(fun() -> {ok, true} end).
  9. <0.90.0>
  10. 17> flush().
  11. Shell got {'EXIT',<0.90.0>,normal}

总结:

  • 进程默认的trap_exit为false,如果link进程crash,则自己也会被用exit/2crash掉,link进程正常退出,则本进程不受影响,且收不到任何消息。
  • 进程trap_exit为true时,只要link进程退出(正常退出或crash),本进程都会收到{’EXIT‘, FromPid, Reason}的消息。

那么我们再来看一看一个单独的gen_server进程出错了,会发生什么?

gen_server内部出错,会发生什么?

如果gen_server内部逻辑发生错误导致crash,比如除零,原子使用++导致进程自己crash掉,会不会执行termniate/2 ?
结论是: 一定会!
写一个简单的gen_server验证一下:

  1. -module(gen_server_test).
  2. -behaviour(gen_server).
  3. -export([start_link/1, start/1]).
  4. -export([divide/2, stop/1, crash/1]).
  5. %% gen_server callbacks
  6. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  7. %% API
  8. start(TrapExit) ->
  9. gen_server:start({local, ?MODULE}, ?MODULE, [TrapExit], []).
  10. start_link(TrapExit) ->
  11. gen_server:start_link({local, ?MODULE}, ?MODULE, [TrapExit], []).
  12. stop(Reason) ->
  13. gen_server:call(?MODULE, {stop, Reason}).
  14. crash(Reason) ->
  15. gen_server:call(?MODULE, {crash, Reason}).
  16. divide(X, Y) ->
  17. gen_server:call(?MODULE, {divide, X, Y}).
  18. init([TrapExit]) ->
  19. erlang:process_flag(trap_exit, TrapExit),
  20. {ok, undefined}.
  21. handle_call({divide, X, Y}, _From, State) ->
  22. io:format("[line:~p] Divide:~p/~p ~n", [?LINE, X, Y]),
  23. {reply, X/Y, State};
  24. handle_call({stop, Reason}, _From, State) ->
  25. io:format("[line:~p] Got stop by ~p ~n", [?LINE, Reason]),
  26. {stop, Reason, ok, State};
  27. handle_call({crash, Reason}, _From, State) ->
  28. io:format("[line:~p] Got crash: error(~p).~n", [?LINE, Reason]),
  29. erlang:error(Reason),
  30. {reply, ok, State};
  31. handle_call(_Msg, _From, State) -> {reply, ignore, State}.
  32. handle_info(Msg, State) ->
  33. io:format("[line:~p] Got ~p~n", [?LINE, Msg]),
  34. {noreply, State}.
  35. terminate(Reason, _State) ->
  36. io:format("[line:~p] Terminate reason: ~p~n", [?LINE, Reason]),
  37. ok.
  38. handle_cast(_Msg, State) -> {noreply, State}.
  39. code_change(_Old, State, _Extra) -> {ok, State}.
  • 如果内部crash退出结果:
  1. 1> c(gen_server_test).
  2. {ok,gen_server_test}
  3. 2> gen_server_test:start_link(false).
  4. {ok,<0.71.0>}
  5. 3> gen_server_test:divide(1,0).
  6. [line:31] Divide:1/0
  7. %% crash后执行terminate/2callback
  8. [line:47] Terminate reason: {badarith,
  9. [{gen_server_test,handle_call,3,
  10. [{file,"gen_server_test.erl"},{line,32}]},
  11. {gen_server,try_handle_call,4,
  12. [{file,"gen_server.erl"},{line,636}]},
  13. {gen_server,handle_msg,6,
  14. [{file,"gen_server.erl"},{line,665}]},
  15. {proc_lib,init_p_do_apply,3,
  16. [{file,"proc_lib.erl"},{line,247}]}]}
  17. %% 因为gen_server_test:divide(1,0)使用的是gen_server:call/2,
  18. %% 它会先linkgen_server_test进程,gen_server_test导常退出
  19. %% 会把这个错再抛出给调用者(shell进程)
  20. %% shell进程trap_exit默认false,所以一起挂掉
  21. ** exception exit: badarith
  22. in function gen_server_test:handle_call/3 (gen_server_test.erl, line 32)
  23. in call from gen_server:try_handle_call/4 (gen_server.erl, line 636)
  24. in call from gen_server:handle_msg/6 (gen_server.erl, line 665)
  25. in call from proc_lib:init_p_do_apply/3 (proc_lib.erl, line 247)
  26. 4>
  27. %% 如果gen_server进程被不是normalReason结束掉,默认会使用error_logger记录一条日志。
  28. =ERROR REPORT==== 21-May-2018::17:03:34 ===
  29. ** Generic server gen_server_test terminating
  30. ** Last message in was {divide,1,0}
  31. ** When Server state == undefined
  32. ** Reason for termination ==
  33. ** {badarith,[{gen_server_test,handle_call,3,
  34. [{file,"gen_server_test.erl"},{line,32}]},
  35. {gen_server,try_handle_call,4,
  36. [{file,"gen_server.erl"},{line,636}]},
  37. {gen_server,handle_msg,6,[{file,"gen_server.erl"},{line,665}]},
  38. {proc_lib,init_p_do_apply,3,
  39. [{file,"proc_lib.erl"},{line,247}]}]}
  40. ** Client <0.64.0> stacktrace
  41. ** [{gen,do_call,4,[{file,"gen.erl"},{line,169}]},
  42. {gen_server,call,2,[{file,"gen_server.erl"},{line,202}]},
  43. {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,674}]},
  44. {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
  45. {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
  46. {shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]
  • 如果进程内部正常退出结果:
  1. 4> gen_server_test:start_link(false).
  2. {ok,<0.75.0>}
  3. 5> gen_server_test:stop(normal).
  4. [line:34] Got stop by normal
  5. [line:47] Terminate reason: normal
  6. ok
  7. 6> gen_server_test:start_link(false).
  8. {ok,<0.78.0>}
  9. 7> gen_server_test:stop(whatever).
  10. [line:34] Got stop by whatever
  11. [line:47] Terminate reason: whatever
  12. %% 正常stop 但是reason不为normal时,会使用error_log打印信息
  13. =ERROR REPORT==== 21-May-2018::17:07:02 ===
  14. ** Generic server gen_server_test terminating
  15. ** Last message in was {stop,whatever}
  16. ** When Server state == undefined
  17. ** Reason for termination ==
  18. ** whatever
  19. ** Client <0.73.0> stacktrace
  20. ** [{gen,do_call,4,[{file,"gen.erl"},{line,169}]},
  21. {gen_server,call,2,[{file,"gen_server.erl"},{line,202}]},
  22. {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,674}]},
  23. {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
  24. {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
  25. {shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]
  26. ** exception exit: whatever

总结:

  • gen_server 内部 自己退出或发生crash退出,都会执行terminate/2
  • 如果stop的原因不是normal,error_log会记录本次退出信息。

gen_server 外部强制退出,会发生什么

  • 使用exit(Pid, kill) 强制发送退出信号,terminate/2并不会执行。
  • trap_exit: fasle 使用exit(Pid, Reason) 强制发送退出信号,terminate/2并不会执行。
  • trap_exit: true 使用exit(Pid, Reason) 强制发送退出信号,terminate/2会执行。
  • {'EXIT',Pid,Reason}消息发送给gen_server:start/3启动的进程,消息被当成普通的消息被handle_info/2处理。
  • {'EXIT',Pid,Reason}消息发送给gen_server:start_link/3启动的进程,消息被当成退出信号被terminate/2处理。
  1. 8> {ok, Pid} = gen_server_test:start_link(false). %% trap_exit false
  2. {ok,<0.86.0>}
  3. 9> erlang:exit(Pid, whatever).
  4. ** exception exit: whatever
  5. 10> {ok, Pid1} = gen_server_test:start_link(true). %% trap_exit true
  6. {ok,<0.90.0>}
  7. 11> erlang:exit(Pid1, whatever).
  8. [line:47] Terminate reason: whatever
  9. true
  10. =ERROR REPORT==== 21-May-2018::17:10:24 ===
  11. ** Generic server gen_server_test terminating
  12. ** Last message in was {'EXIT',<0.88.0>,whatever}
  13. ** When Server state == undefined
  14. ** Reason for termination ==
  15. ** whatever
  16. ** exception exit: whatever
  17. 12> gen_server_test:start(true). ## 此进程使用gen_server:start/3启动,所以只把{'EXIT',self(), whatever}消息当成一个普通的消息给gen_server进程hanle_info/2处理
  18. {ok,<0.94.0>}
  19. 13> gen_server_test ! {'EXIT',self(), whatever}.
  20. [line:43] Got {'EXIT',<0.92.0>,whatever}
  21. {'EXIT',<0.92.0>,whatever}.
  22. 14> > gen_server_test:stop(normal).
  23. [line:34] Got stop by normal
  24. [line:47] Terminate reason: normal
  25. ok
  26. 15> gen_server_test:start_link(false). ## 此进程使用gen_server:start_link/3启动,所以只把{'EXIT',self(), whatever}消息当成特殊的退出的消息给gen_server进程terminate/3处理
  27. {ok,<0.98.0>}
  28. 16> gen_server_test ! {'EXIT',self(), whatever}.
  29. > gen_server_test ! {'EXIT',self(), whatever}.
  30. [line:47] Terminate reason: whatever
  31. {'EXIT',<0.92.0>,whatever}
  32. =ERROR REPORT==== 21-May-2018::17:14:56 ===
  33. ** Generic server gen_server_test terminating
  34. ** Last message in was {'EXIT',<0.92.0>,whatever}
  35. ** When Server state == undefined
  36. ** Reason for termination ==
  37. ** whatever
  38. ** exception exit: whatever
  39. 17> gen_server_test:start_link(false).
  40. {ok,<0.102.0>}
  41. 18> exit(<0.102.0>, whatever).
  42. ** exception exit: whatever
  43. 19> gen_server_test:start_link(true).
  44. {ok,<0.111.0>}
  45. 20> exit(<0.111.0>, kill).
  46. ** exception exit: killed
  47. 21> gen_server_test:start_link(true).
  48. {ok,<0.106.0>}
  49. 22> exit(<0.106.0>, whatever).
  50. [line:47] Terminate reason: whatever
  51. true
  52. =ERROR REPORT==== 21-May-2018::17:20:24 ===
  53. ** Generic server gen_server_test terminating
  54. ** Last message in was {'EXIT',<0.104.0>,whatever}
  55. ** When Server state == undefined
  56. ** Reason for termination ==
  57. ** whatever
  58. ** exception exit: whatever

terminate里面crash会发生什么?

会把crash继续住上抛出去,大多数情况都给exit给了supervisor,让他处理。gen_server源码中处理如下

  1. terminate(ExitReason, ReportReason, Name, Msg, Mod, State, Debug) ->
  2. Reply = try_terminate(Mod, ExitReason, State),
  3. case Reply of
  4. {'EXIT', ExitReason1, ReportReason1} ->
  5. FmtState = format_status(terminate, Mod, get(), State),
  6. error_info(ReportReason1, Name, Msg, FmtState, Debug),
  7. exit(ExitReason1);
  8. _ ->
  9. case ExitReason of
  10. normal ->
  11. exit(normal);
  12. shutdown ->
  13. exit(shutdown);
  14. {shutdown,_}=Shutdown ->
  15. exit(Shutdown);
  16. _ ->
  17. FmtState = format_status(terminate, Mod, get(), State),
  18. error_info(ReportReason, Name, Msg, FmtState, Debug),
  19. exit(ExitReason)
  20. end
  21. end.
  22. try_terminate(Mod, Reason, State) ->
  23. try
  24. {ok, Mod:terminate(Reason, State)}
  25. catch
  26. throw:R ->
  27. {ok, R};
  28. error:R ->
  29. Stacktrace = erlang:get_stacktrace(),
  30. {'EXIT', {R, Stacktrace}, {R, Stacktrace}};
  31. exit:R ->
  32. Stacktrace = erlang:get_stacktrace(),
  33. {'EXIT', R, {R, Stacktrace}}
  34. end.

人不了解自己时是最糟糕的。--李小龙

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号