I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 638 posts at DZone. You can read more from them at their website. View Full User Profile

Erlang: monitoring

11.21.2012
| 2232 views |
  • submit to reddit

Linking processes bidirectionally is a robust way of dealing with failures: a parent can be sure its children are stopped when it crashes, while it can trap the exit signals from them to handle their demise.

However, you have also the option of trapping exists unidirectionally: in this scenario a process A will monitor the state of process B, but B won't be aware of the status of A or if it still exists at all.
This time, I've prepared a strictly technical example where we build a parent process that continuously respawns children when they terminate with an error.

The specification

This is our unit test for the functionality of the parent. We first create it, and two of its children; then we tell the second one to crash. This processes are built to outline Erlang's functionality, but this crash message could correspond to any invalid input that would trigger an exception inside the child.

The assertion phase of the test is interaction-based: we receive in the current processes messages from the parent that tell us all its state changes.

So first the number of children will become 1 and 2; then 1 after a crash, and then 2 again after a substitute child is spawned.
constant_number_of_children_test() ->
  Parent = new_parent(self()),
  _Child1 = new_child(Parent, "1"),
  Child2 = new_child(Parent, "2"),
  Child2 ! {crash},
  receive_event(1),
  receive_event(2),
  receive_event(1),
  receive_event(2).
I think a training in Erlang can really help a programmer to get interaction-based testing and how mock works. It was really natural to write this routine in Erlang, while it would be more troublesome in imperative languages as we would have to create classes or generate them with a mocking framework.
receive_event(ExpectedNumber) ->
  receive
  {children, ActualNumber} -> ActualNumber
  end,
  ?assertEqual(ExpectedNumber, ActualNumber).
If any expected message is not received (what happens when there is no implementation code written yet) the test will automatically time out, and marked as cancelled. Moreover, you can easily perform the verification of calls at the end of the test method, something that not all mocking frameworks let you do; the process mailbox just retains the incoming messages until are delivered to a receive construct.

The child

The children behavior is very simple: they should just do nothing but stay alive, until they receive a crash message. In that case, they should, well, crash; a division by 0 is my preferred method for doing so, but there may be a more semantic way to simulate errors.
child_loop() ->
  receive
  {crash} -> 1 / 0
  end.

The parent

The parent understands two messages: one to create a new child and one to recreate one when it stops.
parent_loop(Interested) ->
  parent_loop(Interested, 0).

parent_loop(Interested, ChildrenNumber) ->
  receive
  {new_child, _Name, Sender} -> Pid = spawn(fun() -> child_loop() end),
  erlang:monitor(process, Pid),
  Sender ! {created, Pid},
  Interested ! {children, ChildrenNumber + 1},

  parent_loop(Interested, ChildrenNumber + 1);
  {'DOWN', _Reference, process, _Pid, _Reason} ->
  Interested ! {children, ChildrenNumber - 1},
  Pid = spawn(fun() -> child_loop() end),
  erlang:monitor(process, Pid),
  Interested ! {children, ChildrenNumber},
  parent_loop(Interested, ChildrenNumber + 1)
  end.
The erlang:monitor/2 primitive declares the unidirectional link between the monitoring and monitored processes. It is the only bit of new infrastructure here: the rest is just sendind/receiving messages, spawning processes and tail-recurring as always.

The API

As usual, it's easy to expose a functional API over the process creation and message sending; this code will be executed in the topmost process (the shell or the tests in our case).
new_parent(Interested) ->
  spawn(fun() -> parent_loop(Interested) end).

new_child(Parent, Name) ->
  Parent ! {new_child, Name, self()},
  receive {created, Child} -> Child end.

Conclusions

We have seen two different ways of recovering from failures: linking and monitoring, different in their bi- or uni-directionality. Erlang teaches us something on object-oriented programming too, and how easy can be to model behavior as object interacting with messages.
As always, all the code is available  on Github.

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)