erlang component application

13074 단어

erlang component application



Most of the third-party libraries we use daily are application, so it is very important and practical for us to understand and master some features of applicaiton.

1. What is applicaiton?, why use application


The official explanation is as follows:
When you have written code implementing some specific functionality you might want to make the code into an application, that is, a component that can be started and stopped as a unit, and which can also be reused in other systems.

http://erlang.org/doc/design_principles/applications.html
In my understanding, there are actually 2 points:

  • For specific functions
  • can be reused

  • In our daily life, there are few scenarios where a single project has multiple applications. We usually write the most module, to be precise, the callback module Callback Module, and there will also be a small number of process status modules Residence Module. The former is only A callback function switches from one state to another state, so the life cycle is only in one process, and rarely deals with the outside world process, so use link,monitor These inter-process relationships are relatively rare. In contrast application, there are many aspects to consider, not only whether the state is correct or not, but also whether the process is running abnormally or not, and even how to design the monitoring tree to keep the program robust.

    2. How to implement an application by yourself?


    2.1 Directory structure


    ─ ${application}
          ├── doc
          │   ├── internal
          │   ├── examples
          │   └── src
          ├── include
          ├── priv
          ├── src
          │   └── ${application}.app.src
          └── test
    

  • src Required, store source code (.erl)
  • priv Not necessary, store custom files, such as nif so files, etc., and resource files
  • include Optional, store some header files (hrl) for easy access by other applications
  • doc Optional, store some documents
  • test optional, test files, eunit common_test files are all placed here

  • 2.2 application callback module (callback module)


    By default it is $APP_NAME_app , of course, you can also define it yourself in ${application}.app.src, the definition method is as follows:
    {application, $APP_NAME,
     [
      {description, ""},
      {vsn, "1"},
      {registered, []},
      {applications, [
                      kernel,
                      stdlib
                     ]},
       %  
       %  
      {mod, {$CALLBACL_MODULE, Args}},
      {env, []}
     ]}.
    
    Let me make a simplest example, and then analyze the process of the source code, the name of applicaton is chapp
    %  :chapp.app.src
    {application, chapp,
     [
      {description, ""},
      {vsn, "1"},
      {registered, []},
      {applications, [
                      kernel,
                      stdlib
                     ]},
      {mod, { chapp_app, [myargs]}},
      {env, []}
     ]}.
    
    % :chapp_app,erl
    -module(chapp_app).
    -behaviour(application).
    
    -export([start/2, stop/1]).
    -record(state, {
      mod
    }).
    %%  
    start(_StartType, _StartArgs) ->
      io:format("~p,~p,~p~n", [?MODULE, ?FUNCTION_NAME, {_StartType, _StartArgs}]),
      {ok, Pid} = chapp_sup:start_link(),
      {ok, Pid, #state{mod = ?MODULE}}.
    
    %%  
    stop(_State) ->
      io:format("~p,~p,~p~n", [?MODULE, ?FUNCTION_NAME, _State]),
      ok.
    
    
    The results are as follows:
    Eshell V10.4  (abort with ^G)
    1> application:start
    start/1       start/2       start_boot/1  start_boot/2  start_type/0  
    
    
    1> application:start(chapp).
    %   myargs
    chapp_app,start,{normal,[myargs]}
    ok
    2> application:stop(chapp).
    %  pre_stop 
    chapp_app,prep_stop,{state,chapp_app}
    %   state
    chapp_app,stop,{state,chapp_app}
    =INFO REPORT==== 1-Dec-2019::14:58:57.004000 ===
        application: chapp
        exited: stopped
        type: temporary
    ok
    

    2.3 $APP_NAME.app.src file format


    % application.erl(kernel)
    start(Application, RestartType) ->
      case load(Application) of
        ok ->
          %   .app.src 
          Name = get_appl_name(Application),
          application_controller:start_application(Name, RestartType);
        {error, {already_loaded, Name}} ->
          application_controller:start_application(Name, RestartType);
        Error ->
          Error
      end.
      
    load1(Application, DistNodes) ->
      %  application
      case application_controller:load_application(Application) of
        ...
        Else ->
          Else
      end.
    %   application_controller.erl
    
    % application_controller.erl
    load_application(Application) ->
        gen_server:call(?AC, {load_application, Application}, infinity).
    
    make_appl(Name) when is_atom(Name) ->
       %   path   $APP_NANE.app ,  $APP_NAME.app.src 
      FName = atom_to_list(Name) ++ ".app",
      case code:where_is_file(FName) of
        non_existing ->
          {error, {file:format_error(enoent), FName}};
        FullName ->
          case prim_consult(FullName) of
            {ok, [Application]} ->
              {ok, make_appl_i(Application)};
            {error, Reason} ->
              {error, {file:format_error(Reason), FName}};
            error ->
              {error, "bad encoding"}
          end
      end;
      
      %  $APP_NAME.app.src 
      % {application, Name, Opts}
      %    , :description,mod,env
      %  mod , 
      make_appl_i({application, Name, Opts}) when is_atom(Name), is_list(Opts) ->
      Descr = get_opt(description, Opts, ""),
      Id = get_opt(id, Opts, ""),
      Vsn = get_opt(vsn, Opts, ""),
      Mods = get_opt(modules, Opts, []),
      Regs = get_opt(registered, Opts, []),
      Apps = get_opt(applications, Opts, []),
      Mod =
        case get_opt(mod, Opts, []) of
          {M, _A} = MA when is_atom(M) -> MA;
          [] -> [];
          Other -> throw({error, {badstartspec, Other}})
        end,
      Phases = get_opt(start_phases, Opts, undefined),
      Env = get_opt(env, Opts, []),
      MaxP = get_opt(maxP, Opts, infinity),
      MaxT = get_opt(maxT, Opts, infinity),
      IncApps = get_opt(included_applications, Opts, []),
      {#appl_data{name = Name, regs = Regs, mod = Mod, phases = Phases,
        mods = Mods, inc_apps = IncApps, maxP = MaxP, maxT = MaxT},
        Env, IncApps, Descr, Id, Vsn, Apps};
    

    2.4 Application startup steps


    % application_controller.erl
    handle_call({start_application, AppName, RestartType}, From, S) ->
      #state{running = Running, starting = Starting, start_p_false = SPF,
        started = Started, start_req = Start_req} = S,
      %% Check if the commandline environment variables are OK.
      %% Incase of erroneous variables do not start the application,
      %% if the application is permanent crash the node.
      %% Check if the application is already starting.
      case lists:keyfind(AppName, 1, Start_req) of
        false ->
          case catch check_start_cond(AppName, RestartType, Started, Running) of
            {ok, Appl} ->
                ......
                {false, undefined} ->
                  %  
                  spawn_starter(From, Appl, S, normal),
                  {noreply, S#state{starting = [{AppName, RestartType, normal, From} |
                    Starting],
                    start_req = [{AppName, From} | Start_req]}};
                ......
            {error, _R} = Error ->
              {reply, Error, S}
          end;
        {AppName, _FromX} ->
          SS = S#state{start_req = [{AppName, From} | Start_req]},
          {noreply, SS}
      end;
    
    start_appl(Appl, S, Type) ->
      ApplData = Appl#appl.appl_data,
      case ApplData#appl_data.mod of
        [] ->
          {ok, undefined};
        _ ->
          %% Name = ApplData#appl_data.name,
          ......
          %  application_master 
          case application_master:start_link(ApplData, Type) of
            {ok, _Pid} = Ok ->
              Ok;
            {error, _Reason} = Error ->
              throw(Error)
          end
      end.
     
     % application_master.erl
    start_link(ApplData, Type) ->
      Parent = whereis(application_controller),
      proc_lib:start_link(application_master, init, [Parent, self(), ApplData, Type]).
     
     start_it_old(Tag, From, Type, ApplData) ->
      {M, A} = ApplData#appl_data.mod,
      case catch M:start(Type, A) of
        {ok, Pid} ->
          %  ,  State = []
          link(Pid),
          From ! {Tag, {ok, self()}},
          loop_it(From, Pid, M, []);
        {ok, Pid, AppState} ->
        %  ,  State = AppState
          link(Pid),
          From ! {Tag, {ok, self()}},
          %  , loop 
          loop_it(From, Pid, M, AppState);
        {'EXIT', normal} ->
          From ! {Tag, {error, {{'EXIT', normal}, {M, start, [Type, A]}}}};
        {error, Reason} ->
          From ! {Tag, {error, {Reason, {M, start, [Type, A]}}}};
        Other ->
          From ! {Tag, {error, {bad_return, {{M, start, [Type, A]}, Other}}}}
      end.
    %  , 
     start_supervisor(Type, M, A) ->
      case catch M:start(Type, A) of
        {ok, Pid} ->
          {ok, Pid, []};
        {ok, Pid, AppState} ->
          {ok, Pid, AppState};
        {error, Reason} ->
          {error, {Reason, {M, start, [Type, A]}}};
        {'EXIT', normal} ->
          {error, {{'EXIT', normal}, {M, start, [Type, A]}}};
        Other ->
          {error, {bad_return, {{M, start, [Type, A]}, Other}}}
      end.
    
    The topology diagram between procs after startup is as follows:
    |application_controller | --- |(application_master:main_loop)| --- | (application_master:loop_it)| --- | chapp_sup| 
    
    At this point, we have walked through the code flow of the startup, and some callback parameters are also clear. Summarized in the following points

  • application_controller is the appcation that actually started process, or the parent process (process)
  • application The startup is asynchronous, and the result cast is given to application_controller
  • after the startup ends.
  • Load before starting (load)

  • 2.5 How does the application stop (stop) ?


    % application_controller.erl
    handle_call({stop_application, AppName}, _From, S) ->
      #state{running = Running, started = Started} = S,
      case lists:keyfind(AppName, 1, Running) of
        {_AppName, Id} ->
          {_AppName2, Type} = lists:keyfind(AppName, 1, Started),
          stop_appl(AppName, Id, Type),
          NRunning = keydelete(AppName, 1, Running),
          NStarted = keydelete(AppName, 1, Started),
          cntrl(AppName, S, {ac_application_stopped, AppName}),
          {reply, ok, S#state{running = NRunning, started = NStarted}};
        false ->
          case lists:keymember(AppName, 1, Started) of
            true ->
              NStarted = keydelete(AppName, 1, Started),
              cntrl(AppName, S, {ac_application_stopped, AppName}),
              {reply, ok, S#state{started = NStarted}};
            false ->
              {reply, {error, {not_started, AppName}}, S}
          end
    % application_master.erl
    stop(AppMaster) -> call(AppMaster, stop).
    
    main_loop(Parent, State) ->
      receive
        ......
        Other ->
          NewState = handle_msg(Other, State),
          main_loop(Parent, NewState)
      end.
    
    handle_msg({stop, Tag, From}, State) ->
      catch terminate(normal, State),
      From ! {Tag, ok},
      %  
      exit(normal);
      
      loop_it(Parent, Child, Mod, AppState) ->
      receive
         ......
        {'EXIT', Parent, Reason} ->
          %  stop pre_stop 
          % application_master:main_loop , application_master:loop_it 
          %  
          NewAppState = prep_stop(Mod, AppState),
          exit(Child, Reason),
          receive
            {'EXIT', Child, Reason2} ->
              exit(Reason2)
          end,
          % stop  
          catch Mod:stop(NewAppState);
           ......
        _ ->
          ......
      end.
    
    At this point, the stop logic of application has been analyzed, and we can also find a hook:pre_stop that is not in the document by reading the code

    3. Summary


    This article demonstrates the implementation of application through examples and reading the source code, hoping to deepen the reader's understanding of application and lay a solid foundation for the rational use of application.

    4. References:



  • http://erlang.org/doc/design_principles/applications.html
  • https://github.com/erlang/otp/blob/master/lib/kernel/src/application.erl
  • https://github.com/erlang/otp/blob/master/lib/kernel/src/application_controller.erl
  • https://github.com/erlang/otp/blob/master/lib/kernel/src/application_master.erl
  • https://github.com/erlang/otp/blob/master/lib/kernel/src/application_starter.erl
  • 좋은 웹페이지 즐겨찾기