Gen_FSM broken down

Hi All, This is a continuation from my previous article on Generic Server Behavior in Erlang. I expect you are very thorough with what happens in gen_server module before trying out gen_fsm. Gen_fsm is another widely used module in the Erlang world along with gen_server. Let’s get together and see how gen_fsm really works 😅.

To make things very clear, Gen_fsm module lets you build a FSM( Finite State Machine). A finite-state machine (FSM) is not really a machine, but it has a finite number of states. The following diagram illustrates a FSM about your pet, dog 😃.

States of a dog

Our dog has 3 states: sitting, barking and wagging its tail. Different events or inputs may change its state. The dog first sits, but if it sees a squirrel, it will start barking and won’t stop until you pet it again. However, if the dog is sitting and you pet it, we have no idea what might happen (there is no defined state in our machine).

Let us start with the diagram given above. At any given point in time, our dog will be in one of these states: sitting / barking / wagging tail. For each of these states, gen_fsm callbacks should be exported. The callback functions will handle events that occur when our dog is in that particular state.

For example, callback function sitting/2 handles all events when the dog is “sitting”. Compared to a gen_server implementation, sitting/2 is same as the gen_server’s Module:handle_cast/2.

In any of the FSM states, an event can be triggered in 2 ways: synchronously (using gen_fsm:sync_send_event/[2,3]), or asynchronously (via gen_fsm:send_event/2).

our dog’s state changes due to 3 actions: gets petted / sees squirrel / wait (timeout). The first two are result of external actions, so we export API functions for it, namely pet/0 and squirrel/0. Timeout occurs as time passes over the defined timeout value.

Let’s start coding dog_fsm.erl 😄.

%% API
-export([start_link/0, stop/0]).
-export([squirrel/0, pet/0]).
%% gen_fsm callbacks
-define(SERVER, ?MODULE).

We declare our module dog_fsm as an implementation of gen_fsm behaviour. Then we export API calls start/0 and stop/0 to start and stop the FSM. In order to change our dog’s state, we either pet it or show him a squirrel. This functionality will be made available via pet/0 and squirrel/0.

Now let’s come to gen_fsm callbacks. The below functions are required for gen_fsm behaviour to work , so we export them:

init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4.

It is now time to export our states barking/2, wagging_tail/2 and sitting/2. These functions will handle their state events that are called asynchronously as I will be using gen_fsm:send_event/2 to trigger those events.

Let’s move on and define our API functions now.

%%% API
start() ->
   gen_fsm:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() ->
   gen_fsm:send_all_state_event(?SERVER, stop).
squirrel() ->
   gen_fsm:send_event(?SERVER, squirrel).
pet() ->
   gen_fsm:send_event(?SERVER, pet).

start/0 calls gen_fsm:start_link/[3,4] which will perform a synchronous call to init/1.

squirrel/0 and pet/0 triggers an event asynchronously (since it calls gen_fsm:send_event/2), and this event is processed based on the current state of the FSM process. For example, if the current dog state is “barking” and I call pet/0, then the event pet will be sent to barking/2.

stop/0 sends the event stop asynchronously using gen_fsm:send_all_state_event/2.

init([]) ->
  {ok, barking, [], 0}.

init/1 function returns a tuple with the following values to gen_fsm:start_link/[2,3]:

  1. ok — Notifies that the process initialization is complete
  2. barking — The initial state of the FSM process. This means that our dog switches to ‘barking’ state as soon as the process starts, and hence any further events will be handled by barking/2.
  3. [] — State variable. We do not need one in our case. Normally there would be a record defined (like: -record(state, {name, age}). ) that will be initialized here as State variable.
  4. 0 — Timeout. We set Timeout to 0 deliberately so that a timeout event is passed to barking/2 as soon as the process switches to the ‘barking’ state. Inside barking/2, we handle the timeout event by printing the dog bark text in a loop (see below).
barking(Event, State) ->
   case Event of
      pet ->
         {next_state, wagging_tail, State, 30000};
%%timeout given in milliseconds
      timeout ->
         {next_state, barking, State, 2000};
     _ ->
         io:format("Dog is confused~n"),
         {next_state, barking, State, 0}

We know that the process switched to barking state in init/1 . So all further events will be handled by barking/2 as long as the state remains barking.

From above code snippet, timeout event will call bark/0 (which will print the text “Dog says: BARK! BARK!”) and then switch to barking state , but also sets timeout value to 2 seconds. This means that ‘timeout’ event will trigger again within 2 seconds, which in turn will print the text on screen in 2 second time intervals, and this goes on and on until you send in another event using the API.

If you trigger the pet event, using pet/0, wag/0 function (found in the above code)is called which prints a message to screen and then switches the FSM state to wagging_tail for the next 30 seconds. The state changes from barking to wagging_tail instantly, and the timeout event in wagging_tail state will be triggered after 30 seconds.

wagging_tail(Event, State) ->
   case Event of
      pet ->
         {next_state, sitting, State};
     timeout ->
        {next_state, barking, State, 0};
     _ ->
        io:format("Dog is confused~n"),
        {next_state, wagging_tail, State, 30000}

After 30 seconds, the ‘timeout’ event in ‘wagging_tail’ state gets triggered, which in turn switches the FSM back to barking state.

Now let’s say that you triggered the pet event in wagging_tail state before it timed out (i.e., call pet/0 within 30 seconds). As you can see from above snippet, sit/0 is then called which just prints out another line of text on screen and then switches the state to sitting. Notice that there is no timeout value mentioned here , which means that the process will wait endlessly for the next event to come its way, and will handle it in sitting/2.

sitting(Event, State) ->
   case Event of
      squirrel ->
         {next_state, barking, State, 0};
      _ ->
         io:format("Dog is confused~n"),
         {next_state, sitting, State}

When FSM enters sitting state, the only valid event it will accepts is squirrel. This event can be triggered via squirrel/0 after which the process state switches to barking.

Now let us look at some other interesting callbacks.

There are cases when you will want to trigger an event irrespective of the state that our FSM process is currently in. Stoping an FSM process is one classic example.

Gen_fsm behaviour provides handle_event/3 callback to handle such events in an asynchronous way. Events that we handle via handle_event/3 are triggered via gen_fsm:send_all_state_event/2.

handle_event(stop, _StateName, State) ->
   {stop, normal, State};
handle_event(_Event, StateName, State) ->
   {next_state, StateName, State}.

However, it is necessary to write code for handle_sync_event/4 callback. All other messages received by the FSM can be handled using handle_info/3 also. terminate/3 and code_change/4 complete our gen_fsm callback code.

handle_sync_event(_Event, _From, StateName, State) ->
   Reply = ok,
   {reply, Reply, StateName, State}.
handle_info(_Info, StateName, State) ->
   {next_state, StateName, State}.
terminate(_Reason, _StateName, _State) ->

code_change(_OldVsn, StateName, State, _Extra) ->
   {ok, StateName, State}.

There are a few internal functions which simply make our dog active 😈

%%% Internal functions
bark() ->
   io:format("Dog says: BARK! BARK!~n").
wag() ->
   io:format("Dog wags its tail~n").
sit() ->
   io:format("Dog is sitting!~n").

Congratulations ! You have coded a dog 😛. Let’s compile it and take it for a spin.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.