gen_fsm — stdlib v6.0

Search documentation of stdlib

# View Source gen_fsm behaviour (stdlib v6.0)

Deprecated and replaced by `gen_statem`.

## Migration to gen_statem

Here follows a simple example of turning a gen_fsm into a `gen_statem`. The example comes from the previous Users Guide for `gen_fsm`

    -module(code_lock).
    -define(NAME, code_lock).
    %-define(BEFORE_REWRITE, true).

    -ifdef(BEFORE_REWRITE).
    -behaviour(gen_fsm).
    -else.
    -behaviour(gen_statem).
    -endif.

    -export([start_link/1, button/1, stop/0]).

    -ifdef(BEFORE_REWRITE).
    -export([init/1, locked/2, open/2, handle_sync_event/4, handle_event/3,
    	 handle_info/3, terminate/3, code_change/4]).
    -else.
    -export([init/1, callback_mode/0, locked/3, open/3, terminate/3, code_change/4]).
    %% Add callback__mode/0
    %% Change arity of the state functions
    %% Remove handle_info/3
    -endif.

    -ifdef(BEFORE_REWRITE).
    start_link(Code) ->
        gen_fsm:start_link({local, ?NAME}, ?MODULE, Code, []).
    -else.
    start_link(Code) ->
        gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
    -endif.

    -ifdef(BEFORE_REWRITE).
    button(Digit) ->
        gen_fsm:send_event(?NAME, {button, Digit}).
    -else.
    button(Digit) ->
        gen_statem:cast(?NAME, {button,Digit}).
        %% send_event is asynchronous and becomes a cast
    -endif.

    -ifdef(BEFORE_REWRITE).
    stop() ->
        gen_fsm:sync_send_all_state_event(?NAME, stop).
    -else.
    stop() ->
        gen_statem:call(?NAME, stop).
        %% sync_send is synchronous and becomes call
        %% all_state is handled by callback code in gen_statem
    -endif.

    init(Code) ->
        do_lock(),
        Data = #{code => Code, remaining => Code},
        {ok, locked, Data}.

    -ifdef(BEFORE_REWRITE).
    -else.
    callback_mode() ->
        state_functions.
    %% state_functions mode is the mode most similar to
    %% gen_fsm. There is also handle_event mode which is
    %% a fairly different concept.
    -endif.

    -ifdef(BEFORE_REWRITE).
    locked({button, Digit}, Data0) ->
        case analyze_lock(Digit, Data0) of
    	{open = StateName, Data} ->
    	    {next_state, StateName, Data, 10000};
    	{StateName, Data} ->
    	    {next_state, StateName, Data}
        end.
    -else.
    locked(cast, {button,Digit}, Data0) ->
        case analyze_lock(Digit, Data0) of
    	{open = StateName, Data} ->
    	    {next_state, StateName, Data, 10000};
    	{StateName, Data} ->
    	    {next_state, StateName, Data}
        end;
    locked({call, From}, Msg, Data) ->
        handle_call(From, Msg, Data);
    locked({info, Msg}, StateName, Data) ->
        handle_info(Msg, StateName, Data).
    %% Arity differs
    %% All state events are dispatched to handle_call and handle_info help
    %% functions. If you want to handle a call or cast event specifically
    %% for this state you would add a special clause for it above.
    -endif.

    -ifdef(BEFORE_REWRITE).
    open(timeout, State) ->
         do_lock(),
        {next_state, locked, State};
    open({button,_}, Data) ->
        {next_state, locked, Data}.
    -else.
    open(timeout, _, Data) ->
        do_lock(),
        {next_state, locked, Data};
    open(cast, {button,_}, Data) ->
        {next_state, locked, Data};
    open({call, From}, Msg, Data) ->
        handle_call(From, Msg, Data);
    open(info, Msg, Data) ->
        handle_info(Msg, open, Data).
    %% Arity differs
    %% All state events are dispatched to handle_call and handle_info help
    %% functions. If you want to handle a call or cast event specifically
    %% for this state you would add a special clause for it above.
    -endif.

    -ifdef(BEFORE_REWRITE).
    handle_sync_event(stop, _From, _StateName, Data) ->
        {stop, normal, ok, Data}.

    handle_event(Event, StateName, Data) ->
        {stop, {shutdown, {unexpected, Event, StateName}}, Data}.

    handle_info(Info, StateName, Data) ->
        {stop, {shutdown, {unexpected, Info, StateName}}, StateName, Data}.
    -else.
    -endif.

    terminate(_Reason, State, _Data) ->
        State =/= locked andalso do_lock(),
        ok.
    code_change(_Vsn, State, Data, _Extra) ->
        {ok, State, Data}.

    %% Internal functions
    -ifdef(BEFORE_REWRITE).
    -else.
    handle_call(From, stop, Data) ->
         {stop_and_reply, normal,  {reply, From, ok}, Data}.

    handle_info(Info, StateName, Data) ->
        {stop, {shutdown, {unexpected, Info, StateName}}, StateName, Data}.
    %% These are internal functions for handling all state events
    %% and not behaviour callbacks as in gen_fsm
    -endif.

    analyze_lock(Digit, #{code := Code, remaining := Remaining} = Data) ->
         case Remaining of
             [Digit] ->
    	     do_unlock(),
    	     {open,  Data#{remaining := Code}};
             [Digit|Rest] -> % Incomplete
                 {locked, Data#{remaining := Rest}};
             _Wrong ->
                 {locked, Data#{remaining := Code}}
         end.

    do_lock() ->
        io:format("Lock~n", []).
    do_unlock() ->
        io:format("Unlock~n", []).

# Summary

## Callbacks

code_change(OldVsn, StateName, StateData, Extra)
format_status(Opt, StatusData)
handle_event(Event, StateName, StateData)
handle_info(Info, StateName, StateData)
handle_sync_event(Event, From, StateName, StateData)
init(Args)
terminate(Reason, StateName, StateData)

# Callbacks

Link to this callback

# code_change(OldVsn, StateName, StateData, Extra)

View Source (optional)

    -callback code_change(OldVsn :: term() | {down, term()},
                          StateName :: atom(),
                          StateData :: term(),
                          Extra :: term()) ->
                             {ok, NextStateName :: atom(), NewStateData :: term()}.

Link to this callback

# format_status(Opt, StatusData)

View Source (optional)

    -callback format_status(Opt, StatusData) -> Status
                               when
                                   Opt :: normal | terminate,
                                   StatusData :: [PDict | State],
                                   PDict :: [{Key :: term(), Value :: term()}],
                                   State :: term(),
                                   Status :: term().

Link to this callback

# handle_event(Event, StateName, StateData)

View Source

    -callback handle_event(Event :: term(), StateName :: atom(), StateData :: term()) ->
                              {next_state, NextStateName :: atom(), NewStateData :: term()} |
                              {next_state,
                               NextStateName :: atom(),
                               NewStateData :: term(),
                               timeout() | hibernate} |
                              {stop, Reason :: term(), NewStateData :: term()}.

Link to this callback

# handle_info(Info, StateName, StateData)

View Source (optional)

    -callback handle_info(Info :: term(), StateName :: atom(), StateData :: term()) ->
                             {next_state, NextStateName :: atom(), NewStateData :: term()} |
                             {next_state,
                              NextStateName :: atom(),
                              NewStateData :: term(),
                              timeout() | hibernate} |
                             {stop, Reason :: normal | term(), NewStateData :: term()}.

Link to this callback

# handle_sync_event(Event, From, StateName, StateData)

View Source

    -callback handle_sync_event(Event :: term(),
                                From :: {pid(), Tag :: term()},
                                StateName :: atom(),
                                StateData :: term()) ->
                                   {reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term()} |
                                   {reply,
                                    Reply :: term(),
                                    NextStateName :: atom(),
                                    NewStateData :: term(),
                                    timeout() | hibernate} |
                                   {next_state, NextStateName :: atom(), NewStateData :: term()} |
                                   {next_state,
                                    NextStateName :: atom(),
                                    NewStateData :: term(),
                                    timeout() | hibernate} |
                                   {stop, Reason :: term(), Reply :: term(), NewStateData :: term()} |
                                   {stop, Reason :: term(), NewStateData :: term()}.

Link to this callback

# init(Args)

View Source

    -callback init(Args :: term()) ->
                      {ok, StateName :: atom(), StateData :: term()} |
                      {ok, StateName :: atom(), StateData :: term(), timeout() | hibernate} |
                      {stop, Reason :: term()} |
                      ignore.

Link to this callback

# terminate(Reason, StateName, StateData)

View Source (optional)

    -callback terminate(Reason :: normal | shutdown | {shutdown, term()} | term(),
                        StateName :: atom(),
                        StateData :: term()) ->
                           term().