Notes on INF5510 Exercise Set 2
v18.1 (2018-02-22)
Oleks Shturmov

The following notes present my reflections on INF5510 Exercise Set 1, following the exercise session on February 8, 2018. These notes would not constitute a complete reference solution.

1. Hello, All

We can use the locate and self keywords to grab hold of a Node object, which corresponds to the node the code is currently executing on:

const home : Node <- locate self

A Node object has a method getActiveNodes, which yields a NodeList:

const network : NodeList <- home.getActiveNodes

We can then iterate over this list (left as an exercise for the reader), where each element has type NodeListElement, and grab hold of the respective Node, using the getTheNode method of this element type:

var elem : NodeListElement
var friend : Node
% ...
  friend <- elem.getTheNode
% ...

To print at that node, we can issue the following remote call:

  friend.getStdOut.putString["Hello, All\n"]

Note: An object can have both (and neither) an initially, and a process block. The initially block is executed before the process block. The object constructor relinquishes control to the surrounding expression as soon as the initially block is done, while the process block is executed in a new, separate thread.

2. Echo

We can place all the code for this exercise inside a dedicated Emerald object. The given auxiliary functions can be placed inside this object, and we can declare a non-terminating loop inside an initially or process block:

for m : String <- self.readline while true by m <- self.readline
  exit when m = "exit"
  % echo m to all the active nodes
end for

If a node becomes unavailable between calling getActiveNodes, and issuing a remote call on that node, Emerald will raise an unavailable exception. Unless handled, this will cause the executing thread to exit abruptly, printing a corresponding message to stdout:

Unhandled unavailable "..."

Notably, this does not cause the node itself to crash. If you have a working Echo-program, this means that you will still be prompted for input by emx, but it will not be forwarded to any executing process (as there would be none).

The exception can be handled using an unavailable construct, which must occur at the end of a block. For instance, if we declare an echoTo operation for our object, a handler could look like this:

operation echoTo [ n : Node, m : String ]
  n.getStdout.putString[m || "\n"]
  unavailable
    % Do nothing
  end unavailable
end echoTo

3. The Moving Man

Instead of “collecting” node identifications, the following just prints the identity at each of the given nodes.

Here is a function that will yield a string identifier for a given Node, as specified in the exercise text:

function identity [ n : Node ] -> [ o : String ]
  o <- n$name || n$incarnationTime.asString
end identity

The move-to construct in Emerald is perhaps a bit misleading. Execution proceeds immediately with the next instruction, without any guarantee that the given object will actually ever move. One sure way to move an object in Emerald is using the refix-at construct.

After a refix, we may locate self again, and try and write at the node we're currently at:

var n : Node
var loc : Node

% for each active node n

  refix self at n

  loc <- locate self
  % presumably, n = loc, but use locate just to be sure

  loc$stdout.putstring[self.identity[loc] || "\n"]

% end for

Unfortunately, the refix-at construct will not raise an unavailable exception, but simply not move the node. (So much for the guaranteed move!) Neither will replacing refix, by the trio unfix, move, fix, help spot if a node becomes unavailable.

Instead, we can try and leverage the fact locate self after the atomic refix-at will reveal if the move was successful, or not:

refix self at n
loc <- locate self
if n == loc then
  % move successful
else
  % move unsuccessful, node is probably unavailable
end if

4. Watchdog

Fetching the name of a node will issue a remote call, and cause an unavailable exception if the node becomes unavailable. A “watchdog” could therefore repeatedly probe an initial list of nodes, until an unavailable exception occurs. The “watchdog” could then report on this observation, and continue monitoring the network.

To spot new nodes, it would suffice to compare the current list of active nodes to the one seen previously.

Note: If you think sending node names is wasteful, nodes also have an LNN, or Logical Node Number. The Emerald report otherwise claims that this value is “not interesting”.

Hence, a method for checking if a node is alive or not, could look like this:

operation isAlive [ n : Node ] -> [ alive : Boolean ]
  var lnn : Integer <- n$lnn
  alive <- true
  unavailable
    alive <- false
  end unavailable
end isAlive

Once a node goes down, it will not be possible to recover its name. Hence, it is a good idea to retain the name of a node, as soon as the node becomes available, to enable subsequent reporting on its eventual unavailability.

For instance, you could maintain an array of the active node identifiers, alongside a similarly enumerated list of active nodes.

The main loop of a “watchdog” object may then look like this:

timeout <- Time.create[1, 0] % 1 second, 0 microsends
% Other initialization code
loop
  % for each node n, at index i, among the active nodes
    if !self.isAlive[n] then
      stdout.putstring[ids[i] || " is unavailable\n"]
    end if
  % end for
  (locate self).delay[timeout]
end loop

Note: This loop does not take into account new nodes being added to the network. This is left as an exercise for the reader.

5. Keywords

After completing these exercises, you should be familiar with the following keywords. If not, read up on them in the language report.

  • Node
  • NodeList
  • fix
  • unfix
  • refix
  • unavailable
  • delay
  • Time

6. Tips

  1. You can use the $ as syntactic sugar for .get. For instance, you can write eric$name in place of eric.getname above.