1   Introduction

Suppose that we'd like our FSM to carry some state with it. We'd like it to hold some information across multiple calls. Further more, suppose we'd like to create multiple instances of this server and submit some requests to one and other requests to a different instance.

A previous post described how to implement an FSM with the Erlang gen_statem behavior. (see: http://www.davekuhlman.org/gen_statem-fsm-rules-implementation.html) In this post I'll describe an augmentation of that implementation which enables us to create multiple instances and to use them separately.

2   Our approach and modifications

Our approach is to provide a unique name in each call to gen_statem:start_link/4, where the name is an atom.

So, now, our implementation of start_link becomes:

start_link(Server) ->
    io:fwrite("(start_link) starting~n", []),
    gen_statem:start_link({local, Server}, ?MODULE, [], []).

Because each server (FSM) is given a unique name, we can call this function multiple times.

In order to do so, we modify start:

start(Server) ->
    {ok, Pid} = start_link(Server),
    Pid.

And then call it with something like this:

start(server1)

In our case, we also need to modify feed so that it takes the server ID (a process ID) as an argument, and passes that along to the private functions so that they can use in in their calls to gen_statem:call/2.

Now we can test this new implementation:

$ erl
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V8.3  (abort with ^G)
(ins)1> c(characters02).
{ok,characters02}
(ins)2> Pid1 = characters02:start(s1).
(start_link) starting
<0.65.0>
(ins)3> Pid2 = characters02:start(s2).
(start_link) starting
<0.67.0>
(ins)4> characters02:feed(Pid1, "abc\"def\"ghi").
{1,{ok,"abcDEFghi"}}
(ins)5> characters02:feed(Pid1, "abc\"def\"ghi").
{2,{ok,"abcDEFghi"}}
(ins)6> characters02:feed(Pid1, "abc\"def\"ghi").
{3,{ok,"abcDEFghi"}}
(ins)7> characters02:feed(Pid1, "abc\"def\"ghi").
{4,{ok,"abcDEFghi"}}
(ins)8> characters02:feed(Pid2, "abc\"def\"ghi").
{1,{ok,"abcDEFghi"}}
(ins)9> characters02:stop(Pid1).
(terminate) stopping
ok
(ins)10> characters02:stop(Pid2).
(terminate) stopping
ok
(ins)11>

If you look at the first item of the tuple now returned by feed, you will notice a count of the number of times it has been called. That count is maintained as a unique value in each separate FSM server process.

3   The implementation and the code

Here is the actual code: characters02.erl

Notes:

  • I modified our init/1 function so that it initializes the count in our state record to zero.
  • I also modified the start/3 event handler function so that it increments the count, and modified the not_in_quotes/3 event handler function so that it returns the current count when it reaches the end of a string.

4   Running and testing the code

Here is a script that can be used to run and test the FSM.

characters02.escript:

#!/usr/bin/env escript

main(_) ->
    test(),
    ok.

test() ->
    Pid1 = characters02:start(s1),
    Pid2 = characters02:start(s2),
    convert(Pid1, "abc\"def\"ghi"),
    convert(Pid1, "abc\"def\"ghi"),
    convert(Pid1, "abc\"def\"ghi"),
    convert(Pid1, "abc\"def\"ghi"),
    convert(Pid2, "abc\"def\"ghi"),
    convert(Pid1, "abc\"def\"ghi"),
    convert(Pid2, "abc\"def\"ghi"),
    convert(Pid1, "abc\"def\"ghi"),
    convert(Pid2, "abc\"def\"ghi"),
    convert(Pid1, "abc\"def\"ghi"),
    convert(Pid2, "abc\"def\"ghi"),
    characters02:stop(Pid1),
    characters02:stop(Pid2),
    ok.

convert(Pid, InString) ->
    {ok, Server, Count, OutString} = characters02:feed(Pid, InString),
    io:fwrite("Server: ~p  Count: ~B  OutString: ~p~n",
              [Server, Count, OutString]),
    ok.

Running the above script on my machine shows the following:

$ ./characters02.escript
(start_link) starting
(start_link) starting
Server: s1  Count: 1  OutString: "abcDEFghi"
Server: s1  Count: 2  OutString: "abcDEFghi"
Server: s1  Count: 3  OutString: "abcDEFghi"
Server: s1  Count: 4  OutString: "abcDEFghi"
Server: s2  Count: 1  OutString: "abcDEFghi"
Server: s1  Count: 5  OutString: "abcDEFghi"
Server: s2  Count: 2  OutString: "abcDEFghi"
Server: s1  Count: 6  OutString: "abcDEFghi"
Server: s2  Count: 3  OutString: "abcDEFghi"
Server: s1  Count: 7  OutString: "abcDEFghi"
Server: s2  Count: 4  OutString: "abcDEFghi"
(terminate) stopping s1 with Reason: normal.  Converted 7 strings.
(terminate) stopping s2 with Reason: normal.  Converted 4 strings.

- Dave Kuhlman