Charlie Harvey

Erlang — Day Three

Its the last day of 2011, the yuletide festivities are over and I am on a mission to get Erlang wrapped. Today's installment focussed on concurrency, message processing, some of the Erlang's process monitoring, and IPC. It was all a bit boggling for me. But sort of cool. The excercises introduced me to the OTP library which seemed actually a rather nicer way to do this sort of work than rolling one's own, you'll see that reflected in my answers, no doubt. For the first time I didn't get the bonus questions done in the alloted time. But I had fun nonetheless. I’ve grown rather fond of Erlang, but like 2011 it'll soon be time to move on (to Clojure, rather than to 2012 of course).

translator.erl

Monitor the translate_servide and restart it should it die.

I should start by saying that the first bit of homework was to read up on OTP. The supervisor behaviour seemed to be pretty much what I wanted. So I rewrote the original code to implement the gen_server behaviour and then wrote a supervisor to monitor it. That seemed like the right thing to do. I should note that I basically nicked the outline of the code from Jesse Farmer's Erlang: A Generic Server Tutorial. Here is what I came up with anyhow. -module(translator). -behaviour(gen_server). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([start/0,translate/1]). % These are both wrappers for calls to the server start() -> io:format("Starting new process: ~p~n", [self()]), gen_server:start_link({local, ?MODULE}, ?MODULE, [], []) . translate(Word) -> gen_server:call(?MODULE, {translate, Word}). % This is called when a connection is made to the server init([]) -> process_flag(trap_exit, true), {ok,0} . % handle_call is invoked in response to gen_server:call handle_call({translate, Word}, _From, T) -> Response = case Word of "casa" -> "house"; "blanca" -> "white"; _ -> "Don't understand" end, {reply, Response, T} . % These callbacks need to be defined. handle_cast(_Message, T) -> {noreply, T}. handle_info(_Message, T) -> {noreply, T}. terminate(_Reason, _T) ->io:format("Stopping because: ~p~n", [_Reason]), ok. code_change(_OldVersion, T, _Extra) -> {ok,T}. What I have done is simply to override the callbacks in the gen_server behaviour. Its pretty much like implementing an interface in Java. You are required to give bodies to the functions even if they don't do much. It'd be nice not to have to do that.

translator_boss.erl

I wonder if you spotted the Lonely Island reference in the filename? Umm OK. Well this is my supervisor that will restart the translator if it gets killed somehow. Like me calling terminate on it for example. -module(translator_boss). -behavior(supervisor). % Code boilerplate adapted from % http://www.erlang.org/doc/design_principles/sup_princ.html#id70605 -export([start/0, init/1]). start() -> {ok, Pid} = supervisor:start_link({local, ?MODULE}, ?MODULE, _Arg = []), unlink(Pid). init([]) -> {ok, {{one_for_one, 1, 60}, [ { tr1, {translator, start, []}, permanent, 9999, worker, [translator] } ] }}. And with that we're done. Let’s see how that looks in the erl shell: 10> translator_boss:start(). Starting new process: <0.60.0> true 11> translator:translate("casa"). "house" 12> translator:translate("rioja"). "Don't understand" 13> translator:terminate("too much rioja",didntunderstand). Stopping because: "too much rioja" ok 14> translator:translate("blanca"). "white"

roulette.erl, doctor.erl, uberdoctor.erl, metauberdoctor.erl

This is the answer to two questions.

  • Make the Doctor process restart itself if it should die.
  • Make a monitor for the doctor monitor. If either monitor dies, restart it.

My approach was pretty much identical to the previous question — rewrite the code as a server, and use a supervisor to monitor it restarting as required (up to 59 times a minute). I hit a couple of hurdles in my syntax (bad_return is a less than helpful error message!) but got there in the end. If I’d allocated more time I’d get round to cleaning up the error message that you see when the uberdoctor is killed.

The first piece of code is the unmodified roulette.erl froim the book. -module(roulette). -export([loop/0]). loop() -> receive 3 -> io:format("BANG!~n"), exit({roulette,die,at,erlang:time()}); _ -> io:format("click~n"), loop() end.

Next comes the complicated bit, our doctor.erl module. This implements the doctor behaviour as a server. I oringinally started by modifying the code from the book, but pretty much gave up on that approach. This seemed funner. Note that I have simply implemented the gen_server behaviour once more.-module(doctor). -export([ start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3 ]). -behaviour(gen_server). start_link() -> io:format("Starting new doctor process: ~p~n", [self()]), gen_server:start_link({local, doctor}, ?MODULE, [], []) . init([]) -> io:format("Init doctor~n"), process_flag(trap_exit, true), io:format("Spawning revolver~n"), register(revolver, spawn_link(fun roulette:loop/0)), {ok,0} . handle_info({'EXIT', From, Reason}, _State) -> io:format("Shooter ~p Died with reason ~p.~n", [From, Reason]), io:format("Restarting.~n"), register(revolver, spawn_link(fun roulette:loop/0)), {noreply, 0} . handle_call(_Message, X, T) -> {noreply, X, T}. handle_cast(_Message, T) -> {noreply, T}. terminate(_Reason, _T) -> io:format("Stopping because: ~p~n", [_Reason]), ok. code_change(_OldVersion, T, _Extra) -> {ok,T}.

Well, there's a bit more complexity in doctor.erl but now I can make an uberdoctor.erl and a metauberdoctor.erl trivially as supervisor of doctor and supervisor of uberdoctor. Uberdoctor looks like this. -module(uberdoctor). -behavior(supervisor). -export([start/0, init/1]). start() -> io:format("The UberDoctor will see you now.~n"), process_flag(trap_exit, true), supervisor:start_link({local,?MODULE}, ?MODULE, _Arg = []) . init(_Args) -> {ok, {{one_for_one, 59, 60}, [ { doc1, {doctor, start_link, []}, permanent, 9999, worker, [doctor] } ] }} . And metauberdoctor like this. -module(metauberdoctor). -behavior(supervisor). -export([start/0, init/1]). start() -> io:format("The MetaUberDoctor will see you now.~n"), supervisor:start_link({local,?MODULE}, ?MODULE, _Arg = []) . init(_Args) -> process_flag(trap_exit, true), {ok, {{one_for_one, 59, 60}, [ { doc2, {uberdoctor, start, []}, permanent, 9999, supervisor, [uberdoctor] } ] }} .

One of the discoveries that I made when trying to test this little lot was pman:start(). This is erlang's rather nifty process management gui. I was able to kill uberdoctor and metauberdoctor processes just by selecting them and going trace | kill. Nice bit of kit. In the screenshot I’ve checked "Hide System Processes" to make the whole thing a little clearer.

Screenshot of erlang process manager pman

Well, the proof is in the pudding as they say. So this is what I did to test my code in the shell. 1> pman:start(). <0.34.0> 2> metauberdoctor:start(). The MetaUberDoctor will see you now. The UberDoctor will see you now. Starting new doctor process: <0.42.0> Init doctor Spawning revolver {ok,<0.41.0>} 3> revolver ! 2 3> . click 2 4> revolver ! 3. BANG! 3 Shooter <0.44.0> Died with reason {roulette,die,at,{15,35,6}}. Restarting. 5> doctor:terminate("Too much stress",bye). Stopping because: "Too much stress" ok 6> revolver ! 2. click 2 7> % kill Uberdoctor by hand. Stopping because: killed The UberDoctor will see you now. Starting new doctor process: <0.51.0> 7> =ERROR REPORT==== 31-Dec-2011::15:36:26 === ** Generic server doctor terminating ** Last message in was {'EXIT',<0.42.0>,killed} ** When Server state == 0 ** Reason for termination == ** killed Init doctor Spawning revolver 7> revolver ! 2. click 2 8> revolver ! 3. BANG! 3 Shooter <0.53.0> Died with reason {roulette,die,at,{15,36,53}}. Restarting. 9> % killing the metauberdoctor kills everything Stopping because: shutdown ** exception error: killed 9> =ERROR REPORT==== 31-Dec-2011::15:37:34 === ** Generic server uberdoctor terminating ** Last message in was {'EXIT',<0.41.0>,killed} ** When Server state == {state, {local,uberdoctor}, one_for_one, [{child,<0.52.0>,doc1, {doctor,start_link,[]}, permanent,9999,worker, [doctor]}], {dict,0,16,16,8,80,48, {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[], []}, {{[],[],[],[],[],[],[],[],[],[],[],[],[],[], [],[]}}}, 59,60,[],uberdoctor,[]} ** Reason for termination == ** killed 9> revolver ! 1. ** exception error: bad argument in operator !/2 called as revolver ! 1

Final thoughts on Erlang

Tate is right to point out that there is some hairy syntax in Erlang. What language doesn't have some? OK, Ruby perhaps! But I’ve actually rather enjoyed my time with Erlang, and I think it has helped me get a much better grip on what was going on with Prolog. I don't imagine having to use it a huge amount, but I’d certainly be looking towards it if I needed to build some big distributed system which had to do lots of concurrency. Well on that note I bid you adieu and, incidentally a happy new year to each of you.


Comments

  • Be respectful. You may want to read the comment guidelines before posting.
  • You can use Markdown syntax to format your comments. You can only use level 5 and 6 headings.
  • You can add class="your language" to code blocks to help highlight.js highlight them correctly.

Privacy note: This form will forward your IP address, user agent and referrer to the Akismet, StopForumSpam and Botscout spam filtering services. I don’t log these details. Those services will. I do log everything you type into the form. Full privacy statement.