修改gen_fsm源码构建更强壮的有限状态机

关于erlang提供的gen_fsm有限状态机模型,可以说是很好很强大,正是有了它,才使erlang编写复杂业务逻辑成为一件轻松愉快的事情

但是由于erlang的速错设计原则,我们的状态机进程可能在某些异常下直接退出,这点可能跟我们的需要有点背离

最常见的情况是,状态转移中的错误处理问题,如下代码:


%% ====================================================================
%% Interface Function
%% ====================================================================
join(UserId)->
io:format("join UserId=~p~n",[UserId]),
gen_fsm:sync_send_event(?MODULE, {join,UserId}).

start()->
gen_fsm:send_event(?MODULE,start).

%% ====================================================================
%% Callback Function
%% ====================================================================
wait_player({join,UserId},_From, State) ->
Count=State#state.count+1,
if
Count =:= 3 ->
io:format("jump to next phrase -> start~n"),
{reply,next_phrase,wait_start,State};
true->
NewState=State#state{count=Count},
{reply,stand_still,wait_player,NewState}
end.

wait_start(start,State)->
io:format("recv start event ~n"),
{next_state,wait_start,State}.

start_link()->
gen_fsm:start_link({local,?MODULE}, ?MODULE, [] ,[]).

init([])->
{ok, wait_player, #state{count=0}}.



我要实现的是一个简单的状态转移,

初始状态是 wait_player,收到 {join,UserId} 的消息后,进行简单业务逻辑处理判断,当有3个人加入进来,则转移到下一状态 wait_start,这时的StateName是 wait_start,

但是,假如这时客户端继续调用了join接口,也就是发送了{join,UserId}的消息给gen_fsm进程,那么gen_fsm实际回调的是 Mod:StateName,
也就是我们的wait_start({join,UserId},State)方法,结果就是undef 错误,然后整个gen_fsm进程退出,这就不是我想要的了,我希望我的gen_fsm进程能够不理会这种错误的消息或行为(只记录下错误调用日志即可),而不是直接退出

再来简单看一下gen_fsm处理流程(其实基本和gen_server差不多的),

gen_fsm进程启动后会进入一个main loop,等待处理mailbox里的消息,根据消息类型(接口处打上不同的label)分类处理,回调等

main_loop 代码

loop(Parent, Name, StateName, StateData, Mod, Time, Debug) ->
Msg = receive
Input ->
Input
after Time ->
{'$gen_event', timeout}
end,
decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, Debug, false).


跟踪到这里

handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time) -> %No debug here
From = from(Msg),
case catch dispatch(Msg, Mod, StateName, StateData) of
{next_state, NStateName, NStateData} ->
loop(Parent, Name, NStateName, NStateData, Mod, infinity, []);
{next_state, NStateName, NStateData, Time1} ->
loop(Parent, Name, NStateName, NStateData, Mod, Time1, []);
{reply, Reply, NStateName, NStateData} when From =/= undefined ->
reply(From, Reply),
loop(Parent, Name, NStateName, NStateData, Mod, infinity, []);
{reply, Reply, NStateName, NStateData, Time1} when From =/= undefined ->
reply(From, Reply),
loop(Parent, Name, NStateName, NStateData, Mod, Time1, []);
{stop, Reason, NStateData} ->
terminate(Reason, Name, Msg, Mod, StateName, NStateData, []);
{stop, Reason, Reply, NStateData} when From =/= undefined ->
{'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
StateName, NStateData, [])),
reply(From, Reply),
exit(R);
{'EXIT', What} ->
%%{Reason,StackInfo}=What,
%%loop(Parent, Name, StateName, StateData, Mod, infinity, []);
terminate(What, Name, Msg, Mod, StateName, StateData, []);
Reply ->
terminate({bad_return_value, Reply},
Name, Msg, Mod, StateName, StateData, [])
end.


dispatch函数实际就是根据消息label,调用不同回调接口的地方,然后可以看到,根据回调接口的返回,来进行不同处理,
异步的消息直接返回{next_state,...},
同步则是{reply,...}

其实最重要的是告知gen_fsm的下一状态名,也就是NStateName

实际,消息回调发生问题,或者错误的状态消息,则会返回{'EXIT',What}这个消息,gen_fsm源码是一律 terminate

我添加了2行注释,可以不直接退出

%%{Reason,StackInfo}=What,
%%loop(Parent, Name, StateName, StateData, Mod, infinity, []);


实际我们要做的就是在这个基础上完善一下就可以了,比如可以根据Reason判断那种情况该terminate,那种情况可以忽略,继续loop进入主循环处理下一消息

另外一个注意的问题是,尽管加了这2行代码后,gen_fsm不会立即由于undef function而直接退出,但是由于我们的join接口是sync同步调用,实际在gen.erl里会等待gen_fsm进程处理完该消息的返回,

而实际由于是一个错误的调用,所以我们可以返回一个自定义的错误,或者干脆不理会这个返回,如果不理会,则gen.erl由于等待消息超时一样会crash,实际crash的是调用者进程,所以要做好catch

这样,通过添加2行代码到gen_fsm就可以使我们的gen_fsm进程更稳定了

时间仓促,没有多考虑,应该会有更好的办法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值