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
- You can use the
$
as syntactic sugar for.get
. For instance, you can writeeric$name
in place oferic.getname
above.