I think the problem is the following:
The Actor model is essentially an asynchronous model, meaning that actors process messages at times indifferent to the sending times.
You are sending to each of your actor a reference to a size 2 array, which keeps changing its content depending on the state of your iteration. However, actors will not process the initialization message right after the call nodes(i) ! spliceArr
. So what probably happens is that the iteration finishes and only after that the actors are scheduled to process the messages. The trouble is, all of them see the instance of spliceArr
as it is when the for loop has finished.
So the simple solution is to not send an array but a pair:
nodes(i) ! spliceArr
becomes
nodes(i) ! (nodes(i-1), nodes(i+1))
and you should also modify the corresponding lines before the loop. This change should be performed also in the code of the actors - use tuples instead of arrays for this kind of stuff.
If my guess was right, then the core problem is that you're using mutable data structures (the array in your case) which are shared amongst various entities (the actors, in your example). This always leads to problems so unless the application you are working on really has a particular need for stateful data structures you should always bet on immutability.
Now, in the particular case of actor systems the messages exchanged between actors have an even greater need to be immutable. Actors are supposed to be enclosed data structures and their state should not be accessible from the exterior. Also, in an actor system there should be no global state.
Unfortunately, unlike other languages that implement actor systems such as Erlang, Scala cannot enforce this behavior. Therefore it is the job of the developer to ensure this happens.
Mutable messages are bad since they can cause actors to share state - the state contained in the message which in the context of the actor's concurrent execution will probably lead to hard to spot issues.
Here's how the code would look like with the fixes described above:
def buildRing(nodes: Array[Actor]) {
nodes.zipWithIndex.foreach {
case (actor, index) => actor ! (previous(nodes, index), next(nodes, index))
}
}
//Gets the next actor from the ring for the specified index.
def next(nodes: Array[Actor], index: Int): Actor = {
val k = (index + 1) % nodes.length
nodes(k)
}
//Gets the previous actor
def previous(nodes: Array[Actor], index: Int): Actor = {
val k = if (index == 0) nodes.length - 1 else index - 1
nodes(k)
}
class Node(locLogger:logger, nid:Int, boss:Actor) extends Actor {
private var leftNeighbour: Option[Actor] = None //avoid using null in favor of Option
private var rightNeighbour: Option[Actor] = None
def act {
locLogger.start
loop {
receive {
case (left, right) => {
leftNeighbour = Some(left)
rightNeighbour = Some(right)
}
}
}
}
}
I have also made some changes for greater readability of the algorithm, I hope you don't mind.