Big state machines
Vance Shipley
vances@REDACTED
Fri Apr 22 18:31:12 CEST 2005
Ulf,
Having read your paper "Programming Models for Concurrency"(*)
I think you have won me over to the pure erlang side. I have
some FSMs which require SDL saves and are pretty ugly in gen_fsm.
Having selective receives will really simplify things for me.
However I'm not convinced that plain_fsm is solving the problem
the way I want it solved.
In your plain_fsm documentation you say:
"... figure out where you really want to handle system messages.
Normally, it will suffice to do it in a fairly stable state."
In one of your examples you didn't handle system messages while
in the transient states waiting for a reply. Is that a good idea?
This suggests that the coder should have control over how system
messages are processed. Given that I want to do my own receiving,
so that I can do so selectively, it seems to me that I want to
manage how the system events appear in the receives.
So I wrote a behaviour module which uses some of the gen_fsm API
but takes out the main loop. I built macros to make the handling
of system messages a little less verbose so that the real code is
more of a focus. I think I quite like this.
The new sdl_fsm looks like this:
-module(sdl_fsm).
-export([init/1, terminate/4, code_change/4]).
-export([s/2, y/2, x/2]).
-include("sys_fsm.hrl").
-record(state, {}).
init(_Args) ->
process_flag(trap_exit, true),
{ok, s, #state{}}.
%% ___
%% (_s_)
%% |
%% +--+--+
%% __|_ __|_
%% >_a_| >_b_|
%% _|_ _|_
%% (_x_) (_y_)
%%
s(SysData, StateData) ->
receive
?SYSTEM(s, SysData, StateData);
?EXIT(s, SysData, StateData);
a ->
?DEBUG(a, s, SysData, StateData),
x(SysData, StateData);
b ->
?DEBUG(b, s, SysData, StateData),
y(SysData, StateData);
Msg ->
?DEBUG(Msg, s, SysData, StateData),
s(SysData, StateData)
end.
%% ___
%% (_x_)
%% |
%% +-----+------+
%% __|_ __|_ __|_
%% >_a_| >_b_| /_*_/
%% _|_ _|_
%% (_x_) (_y_)
%%
x(SysData, StateData) ->
receive
?SYSTEM(x, SysData, StateData);
?EXIT(x, SysData, StateData);
a ->
?DEBUG(a, x, SysData, StateData),
x(SysData, StateData);
b ->
?DEBUG(b, x, SysData, StateData),
y(SysData, StateData)
end.
%% ___
%% (_y_)
%% |
%% +-----+------+-----+
%% __|_ __|_ __|_ __|_
%% >_a_| >_b_| /_c_/ /_d_/
%% _|_ _|_
%% (_x_) (_y_)
%%
y(SysData, StateData) ->
receive
?SYSTEM(y, SysData, StateData);
?EXIT(y, SysData, StateData);
a ->
?DEBUG(a, y, SysData, StateData),
x(SysData, StateData);
b ->
?DEBUG(b, y, SysData, StateData),
y(SysData, StateData);
Msg when Msg /= c, Msg /= d ->
?DEBUG(Msg, y, SysData, StateData),
y(SysData, StateData)
end.
%%
%% sys_fsm callbacks
%%
terminate(_Reason, _StateName, _SysData, _StateData) -> ok.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-------------- next part --------------
-record(sys, {parent, name, debug}).
-define(SYSTEM(StateName, SysData, StateData),
{system, From, Msg} ->
sys:handle_system_msg(Msg, From, SysData#sys.parent,
sys_fsm, SysData#sys.debug,
[?MODULE, StateName, SysData, StateData])).
-define(EXIT(StateName, SysData, StateData),
{'EXIT', Parent, Reason} = Msg when Parent == SysData#sys.parent ->
gen_fsm:terminate(Reason, SysData#sys.name, Msg, ?MODULE,
StateName, StateData, SysData#sys.debug)).
-define(DEBUG(Msg, StateName, SysData, StateData),
case SysData#sys.debug of
[] -> ok;
Debug ->
sys:handle_debug(Debug,
fun(Device, Event, Extra) ->
gen_fsm:print_event(Device, Event, Extra)
end, {SysData#sys.name, StateName}, {in, Msg})
end).
-------------- next part --------------
%%% sys_fsm.erl
%%%
%%% Behaviour module for an FSM process which receives messages
%%% directly. Much of this module is ruthlessly culled from gen_fsm.
%%%
-module(sys_fsm).
%% API
-export([start/3, start_link/3, start/4, start_link/4, format_status/2]).
-export([behaviour_info/1]).
%% internal
-export([init_it/6]).
%% sys callbacks
-export([system_continue/3, system_terminate/4, system_code_change/4]).
-include("sys_fsm.hrl").
behaviour_info(callbacks) ->
[{init,1}, {terminate,3},{code_change,4}];
behaviour_info(_Other) ->
undefined.
%% the start functions have the same syntax as gen_fsm
start(Mod, Args, Options) ->
gen:start(?MODULE, nolink, Mod, Args, Options).
start(Name, Mod, Args, Options) ->
gen:start(?MODULE, nolink, Name, Mod, Args, Options).
start_link(Mod, Args, Options) ->
gen:start(?MODULE, link, Mod, Args, Options).
start_link(Name, Mod, Args, Options) ->
gen:start(?MODULE, link, Name, Mod, Args, Options).
%% the user's init/1 callback has the same results as
%% gen_fsm's but without the timeout option.
init_it(Starter, self, Name, Mod, Args, Options) ->
init_it(Starter, self(), Name, Mod, Args, Options);
init_it(Starter, Parent, Name, Mod, Args, Options) ->
Debug = gen:debug_options(Options),
SysData = #sys{parent = Parent, name = Name, debug = Debug},
case catch Mod:init(Args) of
{ok, StateName, StateData} ->
proc_lib:init_ack(Starter, {ok, self()}),
Mod:StateName(SysData, StateData);
{stop, Reason} ->
proc_lib:init_ack(Starter, {error, Reason}),
exit(Reason);
ignore ->
proc_lib:init_ack(Starter, ignore),
exit(normal);
{'EXIT', Reason} ->
proc_lib:init_ack(Starter, {error, Reason}),
exit(Reason);
Else ->
Error = {bad_return_value, Else},
proc_lib:init_ack(Starter, {error, Error}),
exit(Error)
end.
%%
%% callbacks for system messages
%%
system_continue(_Parent, Debug, [Module, StateName, SysData, StateData]) ->
Module:StateName(SysData#sys{debug = Debug}, StateData).
system_terminate(Reason, _Parent, Debug,
[Module, StateName, SysData, StateData]) ->
gen_fsm:terminate(Reason, SysData#sys.name, [], Module,
StateName, StateData, Debug).
system_code_change([Module, StateName, SysData, StateData],
Module, OldVsn, Extra) ->
case catch Module:code_change(OldVsn, StateName, StateData, Extra) of
{ok, NewStateName, NewStateData} ->
{ok, [Module, NewStateName, SysData, NewStateData]};
Else -> Else
end.
format_status(Opt, StatusData) ->
[PDict, SysState, Parent, Debug, Misc] = StatusData,
[Module, StateName, SysData, StateData] = Misc,
GenFsmMisc = [SysData#sys.name, StateName, StateData, Module, 0],
gen_fsm:format_status(Opt, [PDict, SysState, Parent, Debug, GenFsmMisc]).
More information about the erlang-questions
mailing list