Erlang - 主管和 gen_fsm 异常退出
Erlang - Exception exit on supervisor and gen_fsm
我有 3 个模块:calculadora
、log_calculadora
和 supervisor_calculadora
。 Calculadora
只是一个简单的计算器,它使用 gen_fsm 进行求和、减法、乘法和除法,而监督者实现监督者的行为。 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.
proper:quickcheck(calculadora_supervisada_statem:prop_calculadora()).
触发以下
prop_calculadora() ->
?FORALL(Cmds, commands(?MODULE),
begin
supervisor_calculadora:start(),
在上面的代码中,我看到每个测试用例都启动了 supervisor_calculadora(quickchecl ?FORALL 为每个 Cmd 执行块)。它将尝试启动主管并以相同的名称再次注册它,但失败了。
我有 3 个模块:calculadora
、log_calculadora
和 supervisor_calculadora
。 Calculadora
只是一个简单的计算器,它使用 gen_fsm 进行求和、减法、乘法和除法,而监督者实现监督者的行为。 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.
proper:quickcheck(calculadora_supervisada_statem:prop_calculadora()).
触发以下
prop_calculadora() ->
?FORALL(Cmds, commands(?MODULE),
begin
supervisor_calculadora:start(),
在上面的代码中,我看到每个测试用例都启动了 supervisor_calculadora(quickchecl ?FORALL 为每个 Cmd 执行块)。它将尝试启动主管并以相同的名称再次注册它,但失败了。