Code Review 6: Bowling Kata, Erlang edition

I spent most of the past month interacting with the score server. The Armstrong book details two “stages” of distributed programming: running on two Erlang nodes on the same machine and running on two Erlang nodes on different machines.

First, I added some functionality to the server:

-module(score_server).
-export([
	start/1,
	score_of/2
]).
-include("constants.hrl").
-include("game.hrl").

score_of(Pid, Game) when is_record(Game, game) ->
	rpc(Pid, {score_of, Game});
score_of(Pid, Game) when is_list(Game) ->
	rpc(Pid, {score_of, Game}).
	
rpc(Pid, Message) ->
	Pid ! {self(), Message},
	receive
		{Pid, Response} ->
			Response
	end.

start(Filename) -> 
	{logger, Log} = logger:new(Filename),
	spawn(fun() -> main_loop(Log) end).

main_loop(Log) ->
	receive
		{From, {score_of, Game}} when is_record(Game, game) ->
			Log(info, io_lib:format("Server received from ~w score_of message: ~w", [From, Game]), with_code, ?INFO), 
			ScoreOf = get_scorer(),
			From ! {self(), ScoreOf(Game)},
			main_loop(Log);
		{From, {score_of, Rolls}} when is_list(Rolls) ->
			Log(info, io_lib:format("Server received from ~w score_of message: ~w", [From, Rolls]), with_code, ?INFO), 
			CreateGame = get_creator(),
			{game, Game} = CreateGame(rolls_in_game, Rolls),
			ScoreOf = get_scorer(),
			From ! {self(), ScoreOf(Game)},
			main_loop(Log)
	end.
	
get_scorer() ->
	{scorer, Scorer} = game:scorer(value_of_a_frame()),
	Scorer.
	
get_creator() ->
	{logger, Log} = logger:new("testlog.txt"),
	{creator, Creator} = game:creator(
		frame_roll_function, fun frame:roll/4, 
		frame_is_full_function, fun frame:is_full/1,
		frame_rescore_function, fun frame:rescore/4,
		frame_constructor_function, fun frame:new/0,
		logger_function, Log),
	Creator.
	
value_of_a_frame() -> fun frame:value_of/1.

Notice there is a score_of function. The book recommended this route, except it was oriented around a single process on a single node, and that process was registered. So score_of would’ve not taken in the pid and instead sent a message to the registered name. I ended up sort of self-discovering just how to enable a client to interact with multiple processes on the same node – namely via spawning processes on remote nodes.

Initially, I naively fired up two Erlang consoles on my machine, started one server in one of them and tried to interact with it from the other console, but of course it didn’t work because I didn’t know what I was doing, and because I didn’t start named Erlang nodes.

As I said, starting up two nodes on my local box and interacting between the nodes was no problem:

(server@MDthe5th)4> make:all([load]).
Recompile: bowling/score_server
up_to_date
(server@MDthe5th)5> {logger, Log} = logger:new("testlog.txt").
{logger,#Fun<logger.0.60591536>}
(server@MDthe5th)6> score_server:start(logger_function, Log).
true

c:\Program Files\erl5.6.5\bin>erl.exe -sname client
Eshell V5.6.5  (abort with ^G)
(client@MDthe5th)1> rpc:call(server@MDthe5th, score_server, score_of, [[3, 4, 5, 6]]).
{score,18}
(client@MDthe5th)2> rpc:call(server@MDthe5th, score_server, score_of, [[3, 5,5, 6]]).
{score,19}
(client@MDthe5th)3>

It’s a slightly outdated version of the code running here. It is the version that uses a registered process… oh crap. I thought I “self-discovered” how to do remote spawning and didn’t read the entire section in the book. I’m missing something critical. Might be why it isn’t working distributed. And for some reason, the version of the code I have above is NOT working on separate nodes on the same machine, despite the fact that I remember it working before. I’m losing it.

I changed the code thusly, but I’m having no luck:

start(Node, Filename) -> 
	spawn(Node, fun() -> intermediate_loop(Filename) end).

intermediate_loop(Filename) ->
	{logger, Log} = logger:new(Filename),
	main_loop(Log).

I get these errors:

(client@MDthe5th.ibuilthiscage.com)2> Server = score_server:start('server@MDthe4th.ibuilthiscage.com', "testlog.txt").
<0.42.0>

=ERROR REPORT==== 12-Oct-2010::20:51:17 ===
Error in process <0.38.0> on node 'client@MDthe5th.ibuilthiscage.com' with exit value: {badarg,[{erlang,list_to_existing_atom,["server
@MDTHE4TH.ibuilthiscage.com"]},{dist_util,recv_challenge,1},{dist_util,handshake_we_started,1}]}

(client@MDthe5th.ibuilthiscage.com)3>
=ERROR REPORT==== 12-Oct-2010::20:51:17 ===
** Can not start erlang:apply,[#Fun<score_server.0.120008502>,[]] on 'server@MDthe4th.ibuilthiscage.com' **

And no, my two machines are not Erlang-pingable from each other. Regular ping from the Windows prompt does work, for some reason.

Other oddness I have encountered is not being able to receive more than one message to the same process when running on the console:

1> {logger, Log} = logger:new("testlog.txt").
2> Server = score_server:start(logger_function, Log).
3> {game, Game1} = CreateGame(rolls_in_game, [3, 4, 5, 6, 7]).
4> Pid ! {self(), Game1}, receive {Pid, Response} -> Response end end.

This would work no problem, but if I tried to define another Game record and send a message to the same process, the receive statement would hang, but defining an RPC function from the book worked, and I have no idea why:

1> {game, MyGame} = CreateGame(rolls_in_game, [3, 4, 5, 6, 7]).
2> Server = score_server:start(logger_function, Log).
3> Rpc = fun(Pid, Request) -> Pid ! {self(), Request}, receive {Pid, Response} -> Response end end.
4> Rpc(Server, {score_of, MyGame}).

Anyway, I don’t have many next steps planned because I am pissed that I can’t get my physically-separated Erlang nodes to talk to each other. That’s kind of the entire point of this language and I think I’m going to keep at it until it works. In the mean time, I will go back to reading Clean Code by Uncle Bob.

Announcer: You’re reading the EIP web-ring.
Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.