Sunday, February 1, 2009

Erlang.org course exercises for masters, slaves, and error handling.

The Erlang.org website contains one programming exercise dealing masters, slaves, and error handling.

The requirements are as follows (copied from the Erlang.org website):

Write a module ms with the following interface:

start(N) - Start the master and tell it to start N slave processes. Register the master as the registered process master.

to_slave(Message, N) - Send a message to the master and tell it to relay the message to slave N. The slave should exit (and be restarted by the master) if the message is die.

The master should detect the fact that a slave processes dies and restart it and print a message that it has done so.

The slave should print all messages it recieves except the message die

The source code for my solution is:






%% Erlang exercises: Masters and Slaves, error handling
%%
%% See http://erlang.org/course/exercises.html for details.

-module(masterslave).
-author('Cayle Spandon').

-export([start/1, stop/0, to_slave/2]).

start(NrSlaves) ->
MasterPid = spawn(fun() -> master_start(NrSlaves) end),
register(master, MasterPid),
ok.

stop() ->
master ! die,
ok.

to_slave(Message, SlaveNr) ->
master ! {to_slave, Message, SlaveNr},
ok.

slave_pid_to_nr(SlavePid, SlavePids) ->
slave_pid_to_nr(SlavePid, SlavePids, 1).

slave_pid_to_nr(SlavePid, [SlavePid | _Tail], SlaveNr) ->
SlaveNr;

slave_pid_to_nr(SlavePid, [_Head | Tail], SlaveNr) ->
slave_pid_to_nr(SlavePid, Tail, SlaveNr + 1).

slave_change_pid(OldSlavePid, NewSlavePid, SlavePids) ->
lists:map(
fun(Pid) ->
if
Pid == OldSlavePid ->
NewSlavePid;
true ->
Pid
end
end,
SlavePids
).

master_start(NrSlaves) ->
process_flag(trap_exit, true),
io:format("master: started~n", []),
SlavePids = [spawn_link(fun() -> slave_start(SlaveNr) end) || SlaveNr <- lists:seq(1, NrSlaves)],
master_loop(SlavePids).

master_loop(SlavePids) ->
receive
die ->
io:format("master: received die~n"),
lists:foreach(fun(SlavePid) -> SlavePid ! die end, SlavePids);
{to_slave, Message, SlaveNr} ->
io:format("master: forward ~p to slave ~p~n", [Message, SlaveNr]),
SlavePid = lists:nth(SlaveNr, SlavePids),
SlavePid ! Message,
master_loop(SlavePids);
{'EXIT', SlavePid, _Reason} ->
SlaveNr = slave_pid_to_nr(SlavePid, SlavePids),
io:format("master: slave ~p died~n", [SlaveNr]),
NewSlavePid = spawn_link(fun() -> slave_start(SlaveNr) end),
NewSlavePids = slave_change_pid(SlavePid, NewSlavePid, SlavePids),
master_loop(NewSlavePids)
end.

slave_start(SlaveNr) ->
io:format("slave ~p: started~n", [SlaveNr]),
slave_loop(SlaveNr).

slave_loop(SlaveNr) ->
receive
die ->
io:format("slave ~p: received die~n", [SlaveNr]);
Message ->
io:format("slave ~p: received ~p~n", [SlaveNr, Message]),
slave_loop(SlaveNr)
end.