Erlang - 主管和 gen_fsm 上的异常退出

Posted

技术标签:

【中文标题】Erlang - 主管和 gen_fsm 上的异常退出【英文标题】:Erlang - Exception exit on supervisor and gen_fsm 【发布时间】:2015-05-24 05:47:53 【问题描述】:

我有 3 个模块:calculadoralog_calculadorasupervisor_calculadoraCalculadora 只是一个简单的计算器,它使用 gen_fsm 进行求和、减法、乘法和除法,supervisor 实现了supervisor 行为。 Calculadora 运行良好,但是当我尝试使用主管时,当您进行除法 0/0 或异常时必须重新启动 calculadora 模块时,它不起作用。为什么?

PD:模块log_calculadora 只是将我在calculadora 中所做的操作写入log.txt 文件中。 TEST 模块是给我异常退出的模块。

计算器:

-module(calculadora).
-author("BreixoCF").
-behaviour(gen_fsm).

%% Public API
-export([on/0, off/0, modo/1, acumular/1]).

%% Internal API (gen_fsm)
-export([init/1, handle_event/3, terminate/3]).

%% Internal API (estados)
-export([suma/2, suma/3, resta/2, resta/3, producto/2, producto/3, division/2, division/3]).

-define(CALC, calculadora).
-define(LOG_MODULE, log_calculadora).
-define(LOG_FILE, "log.txt").

%%%===================================================================
%%% Public API
%%%===================================================================
-spec on() -> ok.
on() ->
    gen_fsm:start_link(local, ?CALC, ?CALC, 0, []).

-spec off() -> ok.
off() ->
    gen_fsm:send_all_state_event(?CALC, stop).

-spec modo(O::atom())-> ok.
modo(suma) ->
    gen_fsm:send_event(?CALC, modoSuma);
modo(resta) ->
    gen_fsm:send_event(?CALC, modoResta);
modo(producto) ->
    gen_fsm:send_event(?CALC, modoProducto);
modo(division) ->
    gen_fsm:send_event(?CALC, modoDivision).

-spec acumular(N::number()) -> number().
acumular(N) ->
    gen_fsm:sync_send_event(?CALC, numero, N).

%%%===================================================================
%%% Internal API (gen_fsm)
%%%===================================================================
init(Ac) ->
    ok, _Pid = gen_event:start_link(local, logcalc),
    gen_event:add_handler(logcalc, ?LOG_MODULE, ?LOG_FILE),
    gen_event:notify(logcalc, on, []),
    ok, suma, Ac.

handle_event(stop, _Estado, _DatosEstado) ->
    stop, normal, [].

terminate(normal, _Estado, _DatosEstado) ->
    gen_event:notify(logcalc, off),
    gen_event:stop(logcalc),
    ok.

%%%===================================================================
%%% Internal API (estados)
%%%===================================================================
% Cambio Modo SUMA
suma(modoSuma, Ac) ->
    gen_event:notify(logcalc, cambio, suma, suma),
    next_state, suma, Ac;
suma(modoResta, Ac) ->
    gen_event:notify(logcalc, cambio, suma, resta),
    next_state, resta, Ac;
suma(modoProducto, Ac) ->
    gen_event:notify(logcalc, cambio, suma, producto),
    next_state, producto, Ac;
suma(modoDivision, Ac) ->
    gen_event:notify(logcalc, cambio, suma, division),
    next_state, division, Ac.
% Cálculo Modo SUMA
suma(numero, N, _From, Ac) ->
    gen_event:notify(logcalc, operacion, suma, N, Ac, Ac+N),
    reply, Ac+N, suma, Ac+N.

% Cambio Modo RESTA
resta(modoSuma, Ac) ->
  gen_event:notify(logcalc, cambio, resta, suma),
  next_state, suma, Ac;
resta(modoResta, Ac) ->
  gen_event:notify(logcalc, cambio, resta, resta),
  next_state, resta, Ac;
resta(modoProducto, Ac) ->
  gen_event:notify(logcalc, cambio, resta, producto),
  next_state, producto, Ac;
resta(modoDivision, Ac) ->
  gen_event:notify(logcalc, cambio, resta, division),
  next_state, division, Ac.
% Cálculo Modo RESTA
resta(numero, N, _From, Ac) ->
  gen_event:notify(logcalc, operacion, resta, N, Ac, Ac-N),
  reply, Ac-N, resta, Ac-N.

% Cambio Modo PRODUCTO
producto(modoSuma, Ac) ->
  gen_event:notify(logcalc, cambio, producto, suma),
  next_state, suma, Ac;
producto(modoResta, Ac) ->
  gen_event:notify(logcalc, cambio, producto, resta),
  next_state, resta, Ac;
producto(modoProducto, Ac) ->
  gen_event:notify(logcalc, cambio, producto, producto),
  next_state, producto, Ac;
producto(modoDivision, Ac) ->
  gen_event:notify(logcalc, cambio, producto, division),
  next_state, division, Ac.
% Cálculo Modo PRODUCTO
producto(numero, N, _From, Ac) ->
  gen_event:notify(logcalc, operacion, producto, N, Ac, Ac*N),
  reply, Ac*N, producto, Ac*N.

% Cambio Modo DIVISION
division(modoSuma, Ac) ->
  gen_event:notify(logcalc, cambio, division, suma),
  next_state, suma, Ac;
division(modoResta, Ac) ->
  gen_event:notify(logcalc, cambio, division, resta),
  next_state, resta, Ac;
division(modoProducto, Ac) ->
  gen_event:notify(logcalc, cambio, division, producto),
  next_state, producto, Ac;
division(modoDivision, Ac) ->
  gen_event:notify(logcalc, cambio, division, division),
  next_state, division, Ac.
% Cálculo Modo DIVISION
division(numero, N, _From, Ac) ->
  gen_event:notify(logcalc, operacion, division, N, Ac, Ac/N),
  reply, Ac/N, division, Ac/N.

主管:

-module(supervisor_calculadora).
-author("BreixoCF").

-behaviour(supervisor).

%% API
-export([start/0]).

%% Supervisor callbacks
-export([init/1]).

%%%===================================================================
%%% API functions
%%%===================================================================

start() ->
  supervisor:start_link(local, ?MODULE, ?MODULE, []).

%%%===================================================================
%%% Supervisor callbacks
%%%===================================================================

init([]) ->
  RestartStrategy = one_for_one,
  MaxRestarts = 10,
  MaxSecondsBetweenRestarts = 5,
  Flags = RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts,
  Restart = permanent,
  Shutdown = 2000,
  Type = worker,
  Calculadora = calculadora, calculadora, on, [],
  Restart, Shutdown, Type, [calculadora],
  ok, Flags, [Calculadora].

测试模块

-module(calculadora_supervisada_statem).
-behaviour(proper_statem).

-include_lib("proper/include/proper.hrl").

%% CALLBACKS from proper_statem
-export([initial_state/0, command/1, precondition/2, postcondition/3, next_state/3]).
-export([suma/2, resta/2, producto/2, division/2]).
-export([acumular/1, modo/1]).

initial_state() ->
    suma, 0.

command(_S) ->
    frequency([25, call, ?MODULE, acumular, [number()],
           20, call, ?MODULE, modo, [modo()]]).

modo() ->
    elements([suma, resta, producto, division, unknown]).


next_state(division, _S, _V, call, ?MODULE, acumular, [0]) ->
    suma, 0;
next_state(Op,  S, _V, call, ?MODULE, acumular, [N]) ->
    Op, erlang:apply(?MODULE, Op, [S, N]);
next_state(_Op, _S, _V, call, ?MODULE, modo, [unknown]) ->
    suma, 0;
next_state(_Op,  S, _V, call, ?MODULE, modo, [NewOp]) ->
    NewOp, S;
next_state(S, _V, call, _, _, _) ->
    S.

precondition(_S, call, _, _, _) ->
    true.

postcondition(division, _S, call, ?MODULE, acumular, [0], 'EXIT',_) ->
    true;
postcondition(Op, S, call, ?MODULE, acumular, [N], Res) ->
    Res == ?MODULE:Op(S, N);
postcondition(_Op, _S, call, ?MODULE, modo, [_NewOp], _Res) ->
    true;
postcondition(_S, call, _, _, _, _Res) ->
    false.



prop_calculadora() ->
    ?FORALL(Cmds, commands(?MODULE),
        begin
        supervisor_calculadora:start(),
        H, S, Res = run_commands(?MODULE,Cmds),
        cleanup(),
        ?WHENFAIL(io:format("History: ~p\nState: ~p\nRes: ~p\n", [H, S, Res]),
              aggregate(command_names(Cmds), Res == ok))
        end).



%%--------------------------------------------------------------------
%% Internal wrappers and auxiliary functions
%%--------------------------------------------------------------------

acumular(N) ->
    Acc = (catch calculadora:acumular(N)),
    timer:sleep(100),
    Acc.
modo(O) ->
    Acc = (catch calculadora:modo(O)),
    timer:sleep(100),
    Acc.
cleanup() ->
    catch calculadora:off(),
    timer:sleep(100).

suma(A, B) ->
    A + B.
resta(A, B) ->
    A - B.
producto(A, B) ->
    A * B.
division(_A, 0) ->
    0;
division(A, B) ->
    A / B.

【问题讨论】:

你得到什么错误? 正确:快速检查(calculadora_supervisada_statem:prop_calculadora())。 ..........** 异常退出:关机 如果您的主管在测试模块之外工作,您是否尝试过。我没有对其进行测试,但子规范看起来是正确的,并且 fsm 应该重新启动。该错误表明模块 calculadora_supervisada_statem 崩溃,这并不意味着主管 + fsm 没有完成他们的工作。如果没有关于proper_statem 行为、proper.hrl 和quickcheck 函数的信息,就很难走得更远。 【参考方案1】:
proper:quickcheck(calculadora_supervisada_statem:prop_calculadora()).

触发以下

 prop_calculadora() ->
    ?FORALL(Cmds, commands(?MODULE),
        begin
        supervisor_calculadora:start(),

在上面的代码中,我看到为每个测试用例启动了 supervisor_calculadora(quickcheckl ?FORALL 为每个 Cmd 执行块)。它将尝试启动主管并使用相同的名称再次注册它并失败。

【讨论】:

我修好了。谢谢你们的cmets。

以上是关于Erlang - 主管和 gen_fsm 上的异常退出的主要内容,如果未能解决你的问题,请参考以下文章

Erlang 中 gen_fsm:start/3 的唯一原子

将 Learn You Some Erlang 教程从 gen_fsm 转换为 gen_statem

在这种情况下我应该使用哪种 Erlang 行为,即 gen_server 或 gen_fsm

将动态主管添加到 ejabberd

OTP 主管可以监控远程节点上的进程吗?

在erlang中注册全局主管和本地主管有啥区别