[erlang-questions] Order of message processing by a gen_server

Andreas Hillqvist andreas.hillqvist@REDACTED
Wed Dec 5 09:20:22 CET 2007


It looks like you want to modle a finitive state machine(fsm). You
could use gen_fsm for this:
-module(my_fsm).

-define(NAME, ?MODULE).

-behaviour(gen_fsm).

%% External exports
-export([start_link/0,
         cancel/0,
         phase1/0,
         phase2/0,
         phase3/0,
         get_state/0]).

%% gen_fsm exports
-export([init/1,
         idle/2,
         idle/3,
         phase1/2,
         phase1/3,
         phase2/2,
         phase2/3,
         phase3/2,
         phase3/3,
         handle_event/3,
         handle_sync_event/4,
         handle_info/3,
         terminate/3,
         code_change/4]).

-record(state, {}).

%% External functions
start_link() ->
    Args = [],
    Options = [],
    {ok, Pid} = gen_fsm:start_link({local, ?NAME}, ?MODULE, Args, Options).

cancel() ->
    gen_fsm:send_all_state_event(?NAME, cancel).

phase1() ->
    gen_fsm:send_event(?NAME, phase1).

phase2() ->
    gen_fsm:send_event(?NAME, phase2).

phase3() ->
    gen_fsm:send_event(?NAME, phase3).

get_state() ->
    gen_fsm:sync_send_all_state_event(?NAME, get_state).

%% gen_fsm callbacks
init([]) ->
    {ok, idle, #state{}}.


idle(phase1, StateData) ->
    io:format("We have recived phase1 in idle!\n"
              "We now transits to phase1\n"),
    {next_state, phase1, StateData};
idle(_, StateData) ->
    {next_state, idle, StateData}.

idle(_, From, StateData) ->
    Reply = ok,
    {reply, Reply, idle, StateData}.



phase1(phase2, StateData) ->
    io:format("We have recived phase2 in phase1!\n"
              "We now transits to phase2\n"),
    {next_state, phase2, StateData};
phase1(_, StateData) ->
    {next_state, phase1, StateData}.

phase1(_, From, StateData) ->
    Reply = ok,
    {reply, Reply, phase1, StateData}.


phase2(phase3, StateData) ->
    io:format("We have recived phase3 in phase2!\n"
              "We now transits to phase3\n"),
    {next_state, phase3, StateData};
phase2(_, StateData) ->
    {next_state, phase2, StateData}.

phase2(_, From, StateData) ->
    Reply = ok,
    {reply, Reply, phase2, StateData}.


phase3(_, StateData) ->
    {next_state, phase3, StateData}.

phase3(_, From, StateData) ->
    Reply = ok,
    {reply, Reply, phase3, StateData}.


handle_event(cancel, StateName, StateData) ->
    io:format("We have recived cancel in ~p!\n"
              "We now transits to idle\n", [StateName]),
    {next_state, idle, StateData}.



handle_sync_event(get_state, _From, StateName, StateData) ->
    Reply = StateName,
    {reply, Reply, StateName, StateData};
handle_sync_event(Event, From, StateName, StateData) ->
    Reply = ok,
    {reply, Reply, StateName, StateData}.


handle_info(Info, StateName, StateData) ->
    {next_state, StateName, StateData}.


terminate(Reason, StateName, StatData) ->
    ok.


code_change(OldVsn, StateName, StateData, Extra) ->
    {ok, StateName, StateData}.



Here is a test suit that illustrates when it transits:
-module(test_my_fsm).

-export([start/0]).

start() ->
    {ok, Pid} = my_fsm:start_link(),
    idle = my_fsm:get_state(),

    my_fsm:cancel(),
    idle = my_fsm:get_state(),

    my_fsm:phase1(),
    phase1 = my_fsm:get_state(),

    my_fsm:cancel(),
    idle = my_fsm:get_state(),


    my_fsm:phase2(),
    idle = my_fsm:get_state(),

    my_fsm:phase1(),
    my_fsm:phase2(),
    phase2 = my_fsm:get_state(),

    my_fsm:cancel(),
    idle = my_fsm:get_state(),


    my_fsm:phase3(),
    idle = my_fsm:get_state(),

    my_fsm:phase1(),
    my_fsm:phase3(),
    phase1 = my_fsm:get_state(),

	my_fsm:phase2(),
    my_fsm:phase3(),
    phase3 = my_fsm:get_state(),

    my_fsm:cancel(),
    idle = my_fsm:get_state(),

    success.


There may problem, if the processing of state take some time.

Example:
 1. phase1 is sent to my_fms
      2. my_fms receives phase1
      3. my_fms starts processing phase1
 4. phase2 is sent to my_fms
 5. phase3 is sent to my_fms
 6. cancel is sent to my_fms
      7. my_fms finish processing phase1
      8. my_fms receives phase2
      9. my_fms starts processing phase2
     10. my_fms finish processing phase2
     11. my_fms receives phase3
     12. my_fms starts processing phase3
     13. my_fms finish processing phase3
     14. my_fms receives cancel

As I intrepid it, you want step 8 to receive cancel event and ignore
the transitions to phase2 and phase3?

If this is the case your loop would have to look like:

    loop(State) ->
        receive
            cancel -> ...
        after 0
            receive
                phase1 -> ...
                phase2 -> ...
                phase3 -> ...
                cancel -> ...
            end
        end.

If you receive an cancel you should also empty the message box.
It may also be useful to use a reference to unice identify if the
transitions are valid or old.


Kind regards
Andreas Hillqvist

2007/12/5, Jack Orenstein <jao@REDACTED>:
> On Dec 5, 2007, at 12:40 AM, Lev Walkin wrote:
>
> > Jack Orenstein wrote:
> >> Suppose I have a gen_server process whose queue contains {a} and
> >> {b}.  Does the order of the handle_call function clauses determine
> >> which  message is processed first? I.e., if the function clauses are
> >>      handle_call({a}, From, State) -> ...
> >>      handle_call({b}, From, State) -> ...
> >> is it guaranteed that {a} will be processed first?
> >
> > Yes, but this is implementation detail and is not guaranteed
> > by the standard.
>
> What I am trying to do is to write a server that will process a
> series of requests, always the same requests (phase1, phase2, phase3)
> in the same order. But I want to be able to cancel the sequence of
> requests at any point, resetting the process state to be ready for
> phase1 again. If I write my own function with a receive, I can do this:
>
>           loop(State) ->
>               receive
>                   cancel -> ...
>                   phase1 -> ...
>                   phase2 -> ...
>                   phase3 -> ...
>               end.
>
> Normally this server will receive messages phase1, phase2, phase3 in
> order. But if a cancel message shows up, e.g. after phase2, it takes
> priority (if I understand receive correctly).
>
> I'd like to use a gen_server, but it sounds like I can't do anything
> that is guaranteed by the language (not the implementation) to handle
> the cancel message with higher priority than any of the other
> messages. I.e.,
>
>          handle_call({cancel}, From, State) -> ...
>          handle_call({phase1}, From, State) -> ...
>          handle_call({phase2}, From, State) -> ...
>          handle_call({phase3}, From, State) -> ...
>
> isn't guaranteed to work similarly to the loop function above.
>
> Any advice on how to proceed?
>
> Jack Orenstein
>
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://www.erlang.org/mailman/listinfo/erlang-questions
>



More information about the erlang-questions mailing list