浏览 4374 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-12-09
但是由于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进程更稳定了 时间仓促,没有多考虑,应该会有更好的办法 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-12-09
我只看了一半,你是想在某个状态下收到意料之外的消息不崩溃而已,何必搞得那么麻烦
wait_start(start, State) -> play_game(State); wait_start(OtherMsg, State) -> {reply, you_are_not_welcome, wait_start, State}. |
|
返回顶楼 | |
发表时间:2009-12-09
最后修改:2009-12-09
darkdestiny 写道 我只看了一半,你是想在某个状态下收到意料之外的消息不崩溃而已,何必搞得那么麻烦
wait_start(start, State) -> play_game(State); wait_start(OtherMsg, State) -> {reply, you_are_not_welcome, wait_start, State}. 对呀,这样处理肯定是可以的,应该算标准处理方法 只是我的fsm里的方法太多了,同步,异步,all_state_event等等一大堆,每个都写一个安全的cause也有点麻烦而已 改下gen_fsm 2行代码没觉得很麻烦呀 |
|
返回顶楼 | |
发表时间:2009-12-09
改源码总是最后的选择才对,以后erlang升级你还得改一次,不推荐这样的方式,哪怕麻烦一点。看了下,gen_fsm确实没有提供error handle的hook方式。
|
|
返回顶楼 | |
发表时间:2009-12-09
dennis_zane 写道 改源码总是最后的选择才对,以后erlang升级你还得改一次,不推荐这样的方式,哪怕麻烦一点。看了下,gen_fsm确实没有提供error handle的hook方式。
恩,后来想了一下,是有点脑袋短路,还是不改源码的好,上面提供的方式是最标准的解决办法 |
|
返回顶楼 | |