A well known feature of Scala is it support for message passing concurrency via Actors similar to Erlang. In Chapter 30 of "Programming in Scala" Scala's Actors is described in detail.
However, it is not so well-known that Scala also supports actors distributed across different nodes (Remote Actors). Here is the Ping-Pong example with the Ping actor and the Pong actor running in different processes on (possibly) different (cluster) nodes. The example also shows the actor-linking support of Scala. Linking an actor a to an actor b, means that a is notified if b terminated.
Messages:
package de.dirkmeister.pingpong
case object Ping
case object Pong
case object Quit
Ping:
package de.dirkmeister.pingpong
import scala.actors.Actor
import scala.actors.Actor._
import scala.actors.Exit
import scala.actors.remote.RemoteActor._
import scala.actors.remote.Node
object RemotePingApp {
def main(args: Array[String]) : Unit = {
val port = args(0).toInt
val peer = Node(args(1), args(2).toInt)
val ping = new RemotePing(port, peer, 16)
ping.start()
}
}
class RemotePing(port: Int, peer: Node, count: Int) extends Actor {
trapExit = true // (1)
def act() {
alive(port) // (2)
register('Ping, self) // (3)
val pong = select(peer, 'Pong) // (4)
link(pong) // (5)
var pingsLeft = count - 1
pong ! Ping // (6)
while (true) {
receive { // (7)
case Pong =>
Console.println("Ping: pong")
if (pingsLeft > 0) {
pong ! Ping
pingsLeft -= 1
} else {
Console.println("Ping: start termination")
pong ! Quit // (8)
// Terminate ping after Pong exited (by linking)
}
case Exit(pong, 'normal) => // (9)
Console.println("Ping: stop")
exit()
}
}
}
}
Pong:
package de.dirkmeister.pingpong
import scala.actors.Actor
import scala.actors.Actor._
import scala.actors.remote.RemoteActor._
object RemotePongApp {
def main(args: Array[String]) : Unit = {
val port = args(0).toInt
val pong = new RemotePong(port)
pong.start()
}
}
class RemotePong(port: Int) extends Actor {
def act() {
alive(port)
register('Pong, self)
while (true) {
receive {
case Ping =>
Console.println("Pong: ping")
sender ! Pong
case Quit =>
Console.println("Pong: stop")
exit() // (10)
}
}
}
}
Notes:
(1) By setting trapExit, the linked actor is notified by sending an Exit(sender, reason) message. Otherwise the termination is either ignored (if reason is 'normal) or the linked actor is terminated, too (reason != 'normal).
(2) alive(port) (member of the RemoteActor object) starts the remote service listening on the given port
(3) register(symbol, actor) (member of the RemoteActor object) registers the given actor using the symbol. The other actors can then lookup the actor by the hostname and port and this symbol.
(4) This lookup is done by the select(node, symbol) method that returns an proxy actor, which managed the complete transmission.
(5) The link method links the Ping actor with the Pong actor, so that the current actor is notified if the Pong actor is terminated. This example shows that this also works remotely.
(6) and (7) That proxy actor is used to send messages to the remote node. Sending and receiving remote messages is similar to local messages. Well, everything must be serializable, but the use of case classes is recommended anyway.
(8) The Quit message stops they Pong actor. See (10)
(9) When the Pong actor terminates, an Exit(sender, reason) message is sent to the Ping actor. This is linking system is used for error handling, here it is used to terminate the Ping actor, too.
(10) The Pong actor calls the exit() method, which terminates the actor with the reason 'normal.
I think Scala's Remote Actors are really nice. Well, it lacks Erlang's capability to spawn actors on a different node and maybe other things, but it has the property that it isn't written in a 20 years old language: It is written in a modern, OO/functional-hybrid language.
According to a comment on the Lambda blog, there is (or was?) and effort to use the Java P2P protocol JXTA for remote actors. That would be cool, but I found nothing newer about that effort. Here is a description about clustering Scala actors via Terracotta. Here one using Oracle Coherence for that.