天天看點

trap_exit與terminate

今天寫代碼的發現,關閉app的時候,監控樹下的某個程序terminate函數沒執行,是以有以下備忘記錄

1 erlang:process_flag(trap_exit,true).

When trap_exit is set to true, exit signals arriving to a process are converted to {'EXIT', From, Reason} messages, which can be received as ordinary messages. If trap_exit is set to false, the process exits if it receives an exit signal other than normal and the exit signal is propagated to its linked processes. Application processes are normally not to trap exits.

 可以了解為捕獲退出信号:如果trap_exit設定為true,該程序接收到的退出信号被轉為{'EXIT', From, Reason}格式的消息;trap_exit為false,如果接收到除normal之外的退出信号,則該程序退出并将退出信号傳遞給跟它連接配接的程序。

2 erlang:exit(Pid, Reason)

Sends an exit signal with exit reason Reason to the process or port identified by Pid.

The following behavior applies if Reason is any term, except normal or kill:

  • If Pid is not trapping exits, Pid itself exits with exit reason Reason.
  • If Pid is trapping exits, the exit signal is transformed into a message{'EXIT', From, Reason} and delivered to the message queue of Pid.
  • From is the process identifier of the process that sent the exit signal. See also process_flag/2.

If Reason is the atom normal, Pid does not exit. If it is trapping exits, the exit signal is transformed into a message {'EXIT', From, normal} and delivered to its message queue.

If Reason is the atom kill, that is, if exit(Pid, kill) is called, an untrappable exit signal is sent to Pid, which unconditionally exits with exit reason killed.

大概了解為:

A 如果退出Reason是除了normal和kill之外的任意term:

    1:如果trap_exit=false Pid程序退出

    2:如果trap_exit=true Pid程序捕獲退出信号,退出信号轉化格式為{'EXIT', From, Reason}的一條普通消息

    1:如果Reason=normal, pid程序不會退出。 如果trap_exit=true Pid程序捕獲退出信号,退出信号轉化格式為{'EXIT', From, Reason}的一條普通消息

    2:如果Reason=kill,pid程序無條件退出,無論是否設定trap_exit=true;也就是說,trap_exit無法捕獲kill的退出信号。

3 terminate:程序退出前的資料儲存操作可以在這裡進行。

一般情況下,自己寫的程序停止操作都是如下:

stop(Pid) when is_pid(Pid) ->

    gen_server:call(Pid, stop).

handle_call(stop, State) ->

    {reply, stop, normal, State};

 但是,監控樹下的程序停止操作是執行erlang:exit(Pid,Reason)的,具體代碼可以看一下:supervisor.erl的源碼

%%-----------------------------------------------------------------
%% Func: terminate_children/2
%% Args: Children = [child_rec()] in termination order
%%       SupName = {local, atom()} | {global, atom()} | {pid(),Mod}
%% Returns: NChildren = [child_rec()] in
%%          startup order (reversed termination order)
%%-----------------------------------------------------------------
terminate_children(Children, SupName) ->
    terminate_children(Children, SupName, []).

%% Temporary children should not be restarted and thus should
%% be skipped when building the list of terminated children, although
%% we do want them to be shut down as many functions from this module
%% use this function to just clear everything.
terminate_children([Child = #child{restart_type=temporary} | Children], SupName, Res) ->
    _ = do_terminate(Child, SupName), % 順序停止子程序
    terminate_children(Children, SupName, Res);
terminate_children([Child | Children], SupName, Res) ->
    NChild = do_terminate(Child, SupName),
    terminate_children(Children, SupName, [NChild | Res]);
terminate_children([], _SupName, Res) ->
    Res.

do_terminate(Child, SupName) when is_pid(Child#child.pid) ->
    case shutdown(Child#child.pid, Child#child.shutdown) of
        ok ->
            ok;
        {error, normal} when Child#child.restart_type =/= permanent ->
            ok;
        {error, OtherReason} ->
            report_error(shutdown_error, OtherReason, Child, SupName)
    end,
    Child#child{pid = undefined};
do_terminate(Child, _SupName) ->
    Child#child{pid = undefined}.

%%-----------------------------------------------------------------
%% Shutdowns a child. We must check the EXIT value 
%% of the child, because it might have died with another reason than
%% the wanted. In that case we want to report the error. We put a 
%% monitor on the child an check for the 'DOWN' message instead of 
%% checking for the 'EXIT' message, because if we check the 'EXIT' 
%% message a "naughty" child, who does unlink(Sup), could hang the 
%% supervisor. 
%% Returns: ok | {error, OtherReason}  (this should be reported)
%%-----------------------------------------------------------------
shutdown(Pid, brutal_kill) -> %如果子程序的重新開機政策是brutal_kill 
    case monitor_child(Pid) of
   ok ->
       exit(Pid, kill), % 則子程序無法執行terminate函數,因為kill退出信号無法捕獲為{'EXIT', From, Reason}
       receive
      {'DOWN', _MRef, process, Pid, killed} ->
          ok;
      {'DOWN', _MRef, process, Pid, OtherReason} ->
          {error, OtherReason}
       end;
   {error, Reason} ->      
       {error, Reason}
    end;
shutdown(Pid, Time) ->
    case monitor_child(Pid) of
   ok ->
       exit(Pid, shutdown), %% Try to shutdown gracefully % 其他重新開機政策的子程序退出信号
       receive 
      {'DOWN', _MRef, process, Pid, shutdown} ->
          ok;
      {'DOWN', _MRef, process, Pid, OtherReason} ->
          {error, OtherReason}
       after Time ->
          exit(Pid, kill),  %% Force termination.
          receive
         {'DOWN', _MRef, process, Pid, OtherReason} ->
             {error, OtherReason}
          end
       end;
   {error, Reason} ->      
       {error, Reason}
    end.

%% Help function to shutdown/2 switches from link to monitor approach
monitor_child(Pid) ->
    
    %% Do the monitor operation first so that if the child dies 
    %% before the monitoring is done causing a 'DOWN'-message with
    %% reason noproc, we will get the real reason in the 'EXIT'-message
    %% unless a naughty child has already done unlink...
    erlang:monitor(process, Pid),
    unlink(Pid),

    receive
   %% If the child dies before the unlik we must empty
   %% the mail-box of the 'EXIT'-message and the 'DOWN'-message.
   {'EXIT', Pid, Reason} -> 
       receive 
      {'DOWN', _, process, Pid, _} ->
          {error, Reason}
       end
    after 0 -> 
       %% If a naughty child did unlink and the child dies before
       %% monitor the result will be that shutdown/2 receives a 
       %% 'DOWN'-message with reason noproc.
       %% If the child should die after the unlink there
       %% will be a 'DOWN'-message with a correct reason
       %% that will be handled in shutdown/2. 
       ok   
    end.      

為什麼trap_exit=true,程序退出之前會跑到terminate函數呢?看一下gen_server.erl的源碼:

decode_msg(Msg, Parent, Name, State, Mod, Time, HibernateAfterTimeout, Debug, Hib) ->
    case Msg of
   {system, From, Req} ->
       sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
              [Name, State, Mod, Time, HibernateAfterTimeout], Hib);
   {'EXIT', Parent, Reason} ->
       terminate(Reason, ?STACKTRACE(), Name, undefined, Msg, Mod, State, Debug);
   _Msg when Debug =:= [] ->
       handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout);
   _Msg ->
       Debug1 = sys:handle_debug(Debug, fun print_event/3,
                  Name, {in, Msg}),
       handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout, Debug1)
    end.