Buffered channels in Erlang

Golang has the handy concept of a buffered channel (a similar concept to .NET’s blocking collections). It allows you to throttle your code, by blocking when adding an item to a collection that is already full.

Erlang doesn’t offer anything similar, out of the box (mailboxes will happily increase in size until the VM topples over), but it’s pretty easy to implement using a gen_server. You only need to handle 2 messages, adding an item:

init([Limit]) ->
    State = #{
        limit => Limit,
        count => 0,
        add_q => queue:new(),
        get_q => queue:new()
    },
    {ok, State}.

handle_call(add, From, State = #{count:=Count, limit:=Limit, add_q:=AddQ, get_q:=GetQ}) ->
    UpdatedCount = Count + 1,
    case UpdatedCount > Limit of
        false ->
            case queue:is_empty(GetQ) of
                true ->
                    {reply, ok, State#{count => UpdatedCount}};
                _ ->
                    {{value, Client}, UpdatedQ} = queue:out(GetQ),
                    gen_server:reply(Client, ok),
                    {reply, ok, State#{get_q => UpdatedQ}}
            end;
        _ ->
            {noreply, State#{add_q => queue:in(From, AddQ)}}
    end;

The process is initialised with a limit, the number of items that can be added to the queue before it will block. This collection is FIFO, but another data structure, e.g. a stack, could be used.

When an add message is received, the current size of the collection is checked. If adding another item would breach the limit, then the pid of the caller is added to the wait queue. Otherwise, the number of items is incremented (this version only keeps a count of items, rather than an actual list, but adding that would be trivial).

If any process is blocked waiting to get an item from the collection, then it is unblocked and an item removed immediately. We also need to handle a get message:

handle_call(get, From, State = #{count:=Count, name:=_Name, add_q:=AddQ, get_q:=GetQ}) ->
    case Count > 0 of
        true ->
            case queue:is_empty(AddQ) of
                true ->
                    UpdatedCount = Count - 1,
                    {reply, ok, State#{count => UpdatedCount}};
                _ ->
                    {Client, UpdatedQ} = queue:out(AddQ),
                    gen_server:reply(Client, ok),
                    {reply, ok, State#{add_q => UpdatedQ}}
            end;
        _ ->
            {noreply, State#{get_q => queue:in(From, GetQ)}}
    end;

which is pretty much the opposite, check if the collection is empty. Block if it is, or return an element to the caller.

Once your collection is up & running (and supervised!), using it is as simple as calling any other gen_server:

ok = gen_server:call(queue_1, get, infinity),
do_something(),
ok = gen_server:call(queue_2, add, infinity),

I’ve set the timeout to infinity, but that might not be a good idea for production code.

It would also be pretty simple to back the collection with disk storage, e.g. using a DETS table.

Automatic bidding

Moving on from the excitement of tic-tac-toe, I wanted to try my hand at something a little more complex. I decided to have a look at implementing ebay style “automatic bidding“, which turned out to be a lot more interesting than I expected.

It’s a good exercise because it’s realistic, the behaviour is well documented, and there are plenty of examples. I chose to implement it as a gen_server, but it would work in any language.

I started out by just implementing basic bidding with a starting price, if the bid is higher than the current bid then it is accepted:

-record(state, {starting_price, bids=[]}).
-record(bid, {bidder, amount, time}).

-type money() :: {integer(), integer()}.
-spec start_link([money()]) -> {ok, pid()}.
start_link(StartingPrice) ->
    gen_server:start_link(?MODULE, [StartingPrice], []).

init([StartingPrice]) ->
    {ok, #state{starting_price = StartingPrice}}.

handle_call({bid, _UserId, Bid}, _From, State) when Bid < State#state.starting_price ->
    {reply, bid_too_low, State};

handle_call({bid, UserId, BidAmount}, _From, State) when State#state.bids =:= [] ->
    Bid = #bid{bidder = UserId, amount = BidAmount, time = calendar:universal_time()},
    NewState = State#state{bids = [Bid | State#state.bids]},
    {reply, bid_accepted, NewState};

handle_call({bid, UserId, BidAmount}, _From, State) ->
    CurrentHighBid = hd(State#state.bids),
    case BidAmount > CurrentHighBid#bid.amount of
        true ->
            Bid = #bid{bidder = UserId, amount = BidAmount, time = calendar:universal_time()},
            NewState = State#state{bids = [Bid | State#state.bids]},
            {reply, bid_accepted, NewState};
        false ->
            {reply, bid_too_low, State}
    end;

handle_call(list_bids, _From, State) ->
    {reply, State#state.bids, State};

I decided to handle currency as a tuple of pounds & pennies, due to the way Erlang term comparisons work they are ordered correctly.

With this in place, I started moving towards automatic bidding (the commit history is available, but I’ll skip ahead). The algorithm is fairly simple, but as always the devil is in the details!

-type user_id() :: integer().
-record(bid, {bidder :: user_id(), amount :: eb_money:money(), time :: calendar:datetime(), automatic = false :: boolean()}).
-record(state, {starting_price :: eb_money:money(), bids = [] :: [#bid{}], high_bid :: #bid{}}).

-spec start_link(eb_money:money(), calendar:datetime()) -> {ok, pid()}.
start_link(StartingPrice, EndTime) ->
    gen_server:start_link(?MODULE, [StartingPrice, EndTime], []).

init([StartingPrice, EndTime]) ->
    lager:info("Bidding begins. Starting price: ~p", [StartingPrice]),
    TimeRemaining = calculate_time_remaining(EndTime),
    lager:info("Time remaining: ~p", [TimeRemaining]),
    _TimerRef = erlang:send_after(TimeRemaining, self(), bidding_ends),
    {ok, #state{starting_price = StartingPrice}}.

handle_call({bid, _UserId, MaxBid}, _From, State) when MaxBid < State#state.starting_price ->
    {reply, bid_too_low, State};

handle_call({bid, UserId, MaxBid}, _From, State) when State#state.bids =:= [] ->
    BidTime = calendar:universal_time(),
    Bid = #bid{bidder = UserId, amount = State#state.starting_price, time = BidTime},
    NewState = State#state{bids = [Bid | State#state.bids], high_bid = #bid{bidder = UserId, amount = MaxBid, time = BidTime}},
    {reply, bid_accepted, NewState};

handle_call({bid, UserId, MaxBid}, _From, State) ->
    HighBid = State#state.high_bid,
    case HighBid#bid.bidder =:= UserId of
        true ->
            update_bid_for_high_bidder(State, UserId, MaxBid);
        false ->
            handle_new_bid(State, MaxBid, UserId)
    end;

handle_call(list_bids, _From, State) ->
    {reply, lists:reverse(State#state.bids), State};

handle_info(bidding_ends, State) when State#state.bids =:= [] ->
    lager:info("Bidding ends", []),
    lager:info("No winner", []),
    {stop, normal, State};

handle_info(bidding_ends, State) ->
    lager:info("Bidding ends", []),
    WinningBid = hd(State#state.bids),
    lager:info("Winner: ~p, Bid: ~p", [WinningBid#bid.bidder, WinningBid#bid.amount]),
    {stop, normal, State};

update_bid_for_high_bidder(State, UserId, MaxBid) ->
    HighBid = State#state.high_bid,
    case MaxBid > HighBid#bid.amount of
        true ->
            NewState = State#state{high_bid = #bid{bidder = UserId, amount = MaxBid, time = calendar:universal_time()}},
            {reply, bid_accepted, NewState};
        false ->
            {reply, bid_too_low, State}
    end.

handle_new_bid(State, MaxBid, UserId) ->
    CurrentBid = hd(State#state.bids),
    case MaxBid =< CurrentBid#bid.amount of
        true ->
            {reply, bid_too_low, State};
        false ->
            handle_valid_new_bid(State, MaxBid, UserId)
    end.

handle_valid_new_bid(State, MaxBid, UserId) ->
    HighBid = State#state.high_bid,
    BidTime = calendar:universal_time(),
    case MaxBid =< HighBid#bid.amount of
        true ->
            bid_lower_than_or_equal_to_high_bid(State, MaxBid, UserId, BidTime, HighBid);
        false ->
            bid_higher_than_current_high_bid(State, MaxBid, UserId, BidTime, HighBid)
    end.

bid_lower_than_or_equal_to_high_bid(State, MaxBid, UserId, BidTime, HighBid) ->
    LosingBid = #bid{bidder=UserId, amount=MaxBid, time=BidTime},
    update_bid_list(State, LosingBid, HighBid).

bid_higher_than_current_high_bid(State, MaxBid, UserId, BidTime, HighBid) ->
    WinningBid = #bid{bidder=UserId, amount=MaxBid, time=BidTime},
    update_bid_list(State, HighBid, WinningBid).

update_bid_list(State, LosingBid, WinningBid) ->
    NewAmount = eb_money:add(LosingBid#bid.amount, eb_bid_increments:get(LosingBid#bid.amount)),
    NewHighBid = case NewAmount < WinningBid#bid.amount of
        true ->
            #bid{bidder = WinningBid#bid.bidder, amount = NewAmount, time = WinningBid#bid.time, automatic = true};
        false ->
            #bid{bidder = WinningBid#bid.bidder, amount = WinningBid#bid.amount, time = WinningBid#bid.time, automatic = false}
    end,
    NewState = State#state{bids = [NewHighBid | [LosingBid | State#state.bids]]},
    {reply, bid_accepted, NewState}.

calculate_time_remaining(EndTime) ->
    Now = calendar:universal_time(),
    (calendar:datetime_to_gregorian_seconds(EndTime) - calendar:datetime_to_gregorian_seconds(Now)) * 1000. %% milliseconds!

Once you get used to the convenience of features like tuples and pattern matching, it can be quite painful to go back to a language without them. The next step was reserve price auctions, and some day I might get round to implementing the lowering of them.

If you want to play along, the tests can be found here.

Handling a POST with Cowboy

It’s not immediately obvious how to handle different HTTP methods using Cowboy.
This SO answer, and one of the examples show the way:

handle(Req, State) ->
	{Method, Req2} = cowboy_req:method(Req),
	{ok, Req3} = process_req(Method, Req2),
	{ok, Req3, State}.

process_req(<<"POST">>, Req) ->
	{ok, Body, Req2} = cowboy_req:body_qs(Req),
	_Foo = proplists:get_value(<<"foo">>, Body),
	cowboy_req:reply(200, [
		{<<"content-type">>, <<"text/plain; charset=utf-8">>}
	], <<"ohai">>, Req);

process_req(_, _, Req) ->
	%% Method not allowed.
	cowboy_req:reply(405, Req).

Basic auth with Hackney

Hackney is an Erlang http client library. The homepage states that it supports basic authentication, but documentation of this feature is a little light.

The only evidence of it’s existence seems to be this test, but usage is pretty simple:

Url = <<"http://localhost:8000/basic-auth/username/password">>,
Options = [{basic_auth, {<<"username">>, <<"password">>}}],
{ok, StatusCode, _, _} = hackney:request(get, URL, [], <<>>, Options)

Using erlydtl with Cowboy

Erlydtl seems to be the most popular Erlang templating library, and using it with Cowboy is fairly simple; but doesn’t seem to be terribly well documented.

As always, I’m assuming you’re using erlang.mk and know how to set up a Cowboy project. First, you need to add erlydtl as a dependency in your Makefile:

PROJECT = cowboy_stormpath
DEPS = cowboy erlydtl
include erlang.mk

and as a dependency in your .app.src:

{application, cowboy_erlydtl, [
    {description, ""},
    {vsn, "0.1.0"},
    {id, "git"},
    {modules, []},
    {registered, []},
    {applications, [
        kernel,
        stdlib,
        cowboy,
        erlydtl
    ]},
    {mod, {cowboy_erlydtl_app, []}},
    {env, []}
]}.

Next, create a folder called “templates” in your project root, any .dtl files in here will be compiled when you run “make app”:

<html><body>Your favourite Smurf is {{ smurf_name }}.</body></html>

If you look in ebin/ you should see a file named smurfin_dtl.beam (or whatever), this is the compiled version of your template. Finally, you need a handler to render the template:

-module(smurf_handler).
-behaviour(cowboy_http_handler).

-export([init/3]).
-export([handle/2]).
-export([terminate/3]).

-record(state, {}).

init(_, Req, _Opts) ->
    {ok, Req, #state{}}.

handle(Req, State=#state{}) ->
    {ok, Body} = smurfin_dtl:render([{smurf_name, "Smurfette"}]),
    {ok, Req2} = cowboy_req:reply(200, [{<<"content-type">>, <<"text/html">>}], Body, Req),
    {ok, Req2, State}.

terminate(_Reason, _Req, _State) ->
    ok.

and add a route:

    Dispatch = cowboy_router:compile([
        {'_', [
            {"/smurfin", smurf_handler, []}
        ]}
    ]),

et voila, you’ve rendered your first template!

Testing a gen_server with eunit

Of all the strange corners of Erlang, eunit can be one of the hardest to get your head around. Particularly if you’re used to one of the XUnit frameworks.

It is very powerful, especially once you get into generating tests; but it can also be very confusing, and sometimes the error messages are not that helpful.

I wanted to write some tests for a gen_server, using a new instance for each test, and it took me a couple of attempts to get what I wanted working. It’s very important to make sure your test can actually fail, either in advance or by mutating it, or to add some output (?debugMsg) that proves it ran.

-module(foo_server_tests).

-include_lib("eunit/include/eunit.hrl").

foo_server_test_() ->
    {foreach, fun setup/0, fun cleanup/1, [
        fun(Pid) -> fun() -> server_is_alive(Pid) end end,
        fun(Pid) -> fun() -> something_else(Pid) end end
    ]}.

setup() ->
    ?debugMsg("setup"),
    process_flag(trap_exit, true),
    {ok, Pid} = foo_server:start_link(),
    Pid.

server_is_alive(Pid) ->
    ?assertEqual(true, is_process_alive(Pid)).

something_else(Pid) ->
    ?assertEqual(bar, gen_server:call(Pid, foo)).

cleanup(Pid) ->
    ?debugMsg("cleanup"),
    exit(Pid, kill), %% brutal kill!
    ?assertEqual(false, is_process_alive(Pid)).

We need to trap exits in the setup fun, as the process is linked to the gen_server when it starts. The tricky bit is the nested funs in the generator, if you try to call the “test” directly:

foo_server_test_() ->
    {foreach, fun setup/0, fun cleanup/1, [
        fun(Pid) -> server_is_alive(Pid) end
    ]}.

you’ll get an error like this:

*** result from instantiator foo_server_tests:'-foo_server_test_/0-fun-2-'/1 is not a test ***

because eunit is trying to use the output of the called fun as a test. And if you return multiple tests from one block:

foo_server_test_() ->
    {foreach, fun setup/0, fun cleanup/1, [
        fun(Pid) -> [
             fun() -> server_is_alive(Pid) end,
             fun() -> something_else(Pid) end
        ] end
    ]}.

then the setup & teardown will only be called once for those tests (which may, or may not, be what you want).

UPDATE: actually this is a better approach:

-module(foo_server_tests).

-include_lib("eunit/include/eunit.hrl").

foo_server_test_() ->
    {foreach, fun setup/0, fun cleanup/1, [
        fun server_is_alive/1,
        fun something_else/1
    ]}.

server_is_alive(Pid) ->
    fun() ->
        ?assertEqual(true, is_process_alive(Pid))
    end.

something_else(Pid) ->
    fun() ->
        ?assertEqual(bar, gen_server:call(Pid, foo))
    end.

as the test names are included in the output:

$ SKIP_DEPS=true make eunit
 APP    foo.app.src
 GEN    test-dir
 GEN    eunit
======================== EUnit ========================
directory "ebin"
  module 'foo_server'
    module 'foo_server_tests'
      foo_server_tests: server_is_alive...ok
      foo_server_tests: something_else...[0.001 s] ok
      [done in 0.012 s]
    [done in 0.012 s]
  [done in 0.028 s]
=======================================================
  All 2 tests passed.

UPDATE 2: in case it wasn’t clear, this is effectively an “integration” test or black box test. The server is started, and messages are passed to it. It is also possible to “unit” test individual methods, if you don’t mind being more tightly coupled to the internal state of the server (white box). Both kinds of testing can provide value, pick whichever works for you for that scenario.

Building a Tic Tac Toe game with Cowboy & websockets (Part 4)

Saving game state

As discussed in Part 3, if the FSM crashes it will be re-started, but the game state will be lost. Which isn’t ideal.

The easiest way to solve this is to store the state record in an Ets or Dets table (the main difference being whether it is stored on disk or just in memory). I decided to use a Dets table, although if the VM were to crash (or be stopped deliberately) the game supervisor would not restart the child FSMs when it came back up (yet, there are some other problems to solve (e.g. restoring sessions) before that would be possible).

The first step was to add the game id, and current FSM state to the state record:

-record(state, {game_id, current_state=p1_turn, p1, p2, board=#{
    <<"1,1">> => '_', <<"1,2">> => '_', <<"1,3">> => '_',
    <<"2,1">> => '_', <<"2,2">> => '_', <<"2,3">> => '_',
    <<"3,1">> => '_', <<"3,2">> => '_', <<"3,3">> => '_'
}}).

And update the init function to open the table, and check if any state already exists for that game id:

init(Args) ->
    [{P1, P2, GameId}] = Args,
    process_flag(trap_exit, true),
    true = gproc:reg({n, l, GameId}),
    {ok, t3_game_state} = dets:open_file(t3_game_state, []),
    State = get_game_state(GameId, P1, P2),
    notify_players(State),
    {ok, State#state.current_state, State}.

get_game_state(GameId, P1, P2) ->
    case dets:lookup(t3_game_state, GameId) of
        [] ->
            save_game_state(#state{game_id = GameId, p1 = P1, p2 = P2});
        [{GameId, State}] ->
            State
    end.

We also trap exits; so the terminate function of the FSM is called, and we can close the table:

terminate(_Reason, _StateName, _State) ->
    dets:close(t3_game_state).

Finally, we save the game state after any successful FSM transition:

p1_turn({play, P1, Cell}, State = #state{p1 = P1}) ->
    NewState = play(Cell, State, 'O', p2_turn),
    Res = case t3_game:has_won(NewState#state.board, 'O') of
        true ->
            game_won(NewState#state.p1, NewState#state.p2, NewState#state.board),
            {stop, normal, NewState};
        false ->
            case t3_game:is_draw(NewState#state.board) of
                true ->
                    game_drawn(NewState#state.p1, NewState#state.p2, NewState#state.board),
                    {stop, normal, NewState};
                false ->
                    notify_players(NewState),
                    {next_state, p2_turn, NewState}
            end
    end,
    save_game_state(NewState),
    Res.

save_game_state(State = #state{game_id = GameId}) ->
    ok = dets:insert(t3_game_state, {GameId, State}),
    State.

Now, if the process crashes, it will be resurrected with the state rolled back to before the offending message. Of course in this system, it’s likely that any crash will be repeated if the same message arrives at the same state; but if your application is likely to suffer from transient errors (e.g. network errors calling another system) then you can just “let it crash”.

There are, of course, some trade-offs to this solution. A Dets table is only available to one Erlang node, if you wanted to scale out it would make more sense to use replicated Mnesia or an external data store such as Postgres, or Riak. And the shared Dets table could become a bottleneck when there were many games running.

A more pressing problem would be handling versioning of the game state, this naive code would crash if the record was changed and an old version was retrieved from the data store.

Building a Tic Tac Toe game with Cowboy & websockets (Part 3)

Game FSM

Following on from Part 1, and Part 2, we’re now going to take a look at the game itself.

My first attempt at modeling the game state was using an array of arrays:

-record(state, {p1, p2, board=[
    ['_', '_', '_'],
    ['_', '_', '_'],
    ['_', '_', '_']
]).

but the lack of mutation makes that awkward to work with. My second draft used a map, keyed by {row, column} tuples:

-record(state, {p1, p2, board=#{
    {1,1} => '_', {1,2} => '_', {1,3} => '_',
    {2,1} => '_', {2,2} => '_', {2,3} => '_',
    {3,1} => '_', {3,2} => '_', {3,3} => '_'
}}).

but that blew up when being serialized to send to the client, due to the tuple keys. I could have re-formatted it before sending, but it seemed like less hassle to use the one model. So the final version just used bitstring keys:

-record(state, {p1, p2, board=#{
    <<"1,1">> => '_', <<"1,2">> => '_', <<"1,3">> => '_',
    <<"2,1">> => '_', <<"2,2">> => '_', <<"2,3">> => '_',
    <<"3,1">> => '_', <<"3,2">> => '_', <<"3,3">> => '_'
}}).

When the game is started:

init(Args) ->
    io:format("New game started: ~p~n", [Args]),
    [{P1, P2, GameId}] = Args,
    true = gproc:reg({n, l, GameId}),
    State = #state{p1 = P1, p2 = P2},
    P1 ! {your_turn, State#state.board},
    P2 ! {wait, State#state.board},
    {ok, p1_turn, State}.

It registers itself with gproc using the game id, and notifies the players of whose turn it is. There are only two states p1_turn and p2_turn:

p1_turn({play, P1, Cell}, State = #state{p1 = P1}) ->
    NewState = play(Cell, State, 'O'),
    case t3_game:has_won(NewState#state.board, 'O') of
        true ->
            game_won(NewState#state.p1, NewState#state.p2, NewState#state.board),
            {stop, normal, NewState};
        false ->
            case t3_game:is_draw(NewState#state.board) of
                true ->
                    game_drawn(NewState#state.p1, NewState#state.p2, NewState#state.board),
                    {stop, normal, NewState};
                false ->
                    notify_players(NewState#state.p2, NewState#state.p1, NewState#state.board),
                    {next_state, p2_turn, NewState}
            end
    end.

play(Cell, State, Symbol) ->
    '_' = maps:get(Cell, State#state.board),
    State#state{board = maps:update(Cell, Symbol, State#state.board)}.

notify_players(Play, Wait, Board) ->
    Play ! {your_turn, Board},
    Wait ! {wait, Board}.

game_won(Win, Lose, Board) ->
    Win ! {you_win, Board},
    Lose ! {you_lose, Board}.

game_drawn(P1, P2, Board) ->
    P1 ! {draw, Board},
    P2 ! {draw, Board}.

First we check if it was a winning move, or a draw; in either case, the game is over and the process stops normally. As it was registered as a “transient” process, it will not be restarted. Otherwise it becomes the other player’s turn. In all cases, the updated board is sent to the client.

While this is a working implementation of multi-player game of tic-tac-toe, there are still a lot of rough edges. For example, if the FSM crashes it will be restarted but the game state will be lost. An improvement would be to store it (in an ETS table, or Mnesia, or even something like Riak) after successfully processing a message. It also relies on pids in a few places, that could change if a process is restarted; it would be better to look them up in gproc using a more stable identifier. It’s also possible that the websocket connection would be interrupted, and the client would need to resync with the server.

Using Lager with Cowboy

Lager is a popular logging framework for Erlang applications. To get it working with Cowboy (assuming you’re using erlang.mk), you need to add it as a dependency to your Makefile:

DEPS = cowboy lager

and then add the parse transform to the options for erlc (as shown here):

include erlang.mk
ERLC_OPTS += +'{parse_transform, lager_transform}'

(alternatively, you could add a compile header to each file that needs it). You also need to add lager to your app.src file:

    {applications, [
        kernel,
        stdlib,
        cowboy,
        lager
    ]},
]}.

so that it is started as part of your release. You can find an example repo here.

Building a Tic Tac Toe game with Cowboy & websockets (Part 2)

In Part 1 we dealt with session management, now we’ll take a look at starting a new game.

First, we send a message from the client:

newGameBtn.onclick = function() {
    var msg = JSON.stringify({type: 'new_game', sessionId: sessionId});
    send(msg);
    newGameBtn.disabled = true;
    clearBoard();
    updateStatus('Waiting to join game...');
};

and handle that on the other side of the websocket:

websocket_handle({text, Json}, Req, State) ->
    Msg = jiffy:decode(Json, [return_maps]),
    Resp = validate_session(Msg, fun() ->
        Type = maps:get(<<"type">>, Msg),
        handle_message(Type, Msg)
    end),
    {reply, make_frame(Resp), Req, State};

validate_session(Msg, Fun) ->
    SessionId = maps:get(<<"sessionId">>, Msg),
    case gen_server:call(t3_session_manager, {validate_session, SessionId}) of
        ok -> Fun();
        invalid_session -> #{type => <<"error">>, msg=> <<"invalid_session">>}
    end.

handle_message(<<"new_game">>, Msg) ->
    SessionId = maps:get(<<"sessionId">>, Msg),
    start_new_game(SessionId);

start_new_game(_SessionId) ->
    Res = try
         gen_server:call(t3_match_maker, {find_game}, 30000)
    catch
        exit:{timeout,_} -> timeout
    end,
    case Res of
        {ok, GameId} -> #{type => <<"new_game">>, id => GameId};
        timeout -> #{type => <<"no_game_available">>}
    end.

We decode the message, and validate the session; then we need to find a game, a task we delegate to the “match maker”, another gen_server. It’s possible a game won’t be found in time, so we need to handle timeouts too.

The match maker is pretty simple, like the session manager, but might be more interesting in a real app:

handle_call({find_game}, From, State) ->
    case find_game(From, State) of
        {ok, GameId, NewState} -> {reply, {ok, GameId}, NewState};
        {wait, NewState} -> {noreply, NewState}
    end;

find_game(From, #state{waiting=[]}) ->
    {wait, #state{waiting=[From]}};

find_game({P2,_}, #state{waiting=[From|Rest]}) ->
    GameId = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
    {P1,_} = From,
    {ok, _Pid} = supervisor:start_child(t3_game_sup, [{P1, P2, GameId}]),
    gen_server:reply(From, {ok, GameId}),
    {ok, GameId, #state{waiting=Rest}}.

If someone is waiting, then we start a new game; otherwise, the pid of the websocket gets pushed onto the queue (this should probably be a more stable reference, like a user id, and we would look up the pid. We also don’t handle the case that a queued socket is closed, or times out waiting).

We need to add two more items to our supervision tree:

init([]) ->
    Procs = [
        {t3_session_manager, {t3_session_manager, start_link, []}, permanent, 5000, worker, [t3_session_manager]},
        {t3_match_maker, {t3_match_maker, start_link, []}, permanent, 5000, worker, [t3_match_maker]},
        {t3_game_sup, {t3_game_sup, start_link, []}, permanent, 5000, supervisor, [t3_game_sup]}
    ],
    {ok, {{one_for_one, 1, 5}, Procs}}.

The game supervisor is also very simple:

init([]) ->
    Procs = [{t3_game_fsm, {t3_game_fsm, start_link, []}, transient, 5000, worker, [t3_game_fsm]}],
    {ok, {{simple_one_for_one, 5, 10}, Procs}}.

The important thing to note is the “simple one-for-one” restart strategy, where children are added dynamically, on demand.

We’ll look at the game FSM in more detail in Part 3, but when started it notifies both players:

init(Args) ->
    io:format("New game started: ~p~n", [Args]),
    [{P1, P2, GameId}] = Args,
    true = gproc:reg({n, l, GameId}),
    State = #state{p1 = P1, p2 = P2},
    P1 ! {your_turn, State#state.board},
    P2 ! {wait, State#state.board},
    {ok, p1_turn, State}.

Finally, we need to handle some new messages on the client:

    } else if (msg.type === 'new_game') {
        gameId = msg.id;
        updateStatus('New game!');
    } else if (msg.type === 'your_turn') {
        updateStatus('Your turn!');
        updateBoard(msg.data);
        enableBoard();
    } else if (msg.type === 'wait') {
        updateBoard(msg.data);
        updateStatus('Waiting for other player...');
    }