RemoteActor in Scala
Concurrency in a whole bunch of languages is an error-prone affair, with shared memory and locks being complex mechanisms to get right. Too much locking, and you get deadlocked code; too little locking, and you trash another object’s data.
Erlang implements the concept of actors, which pass messages asynchronously between each other. An actor can be thought of as a thread in Java parlance. Any actor can send another actor a message, and each actor has a “mailbox” for incoming messages. Messages are discrete entities, not shared between the actors themselves, and the actors are totally decoupled from each other. Thus, rather than shared memory and locks, we have a “shared nothing” approach to concurrency.
At the moment, I’m building a simulator to mimic a network of nodes passing routing protocol packets between each other; using a programming model such as this just seems to Make Sense. This simulation may have to scale up quite a lot, so actually I want these actors to run on different machines. That’s slightly trickier, not because of the language model, but because of a slight lack of documentation for the relevant portion of the API in my chosen language, Scala.
Scala uses the same concurrency model as Erlang, but with the advantage that it’s newer, and is built upon Java (and thus has access to Java’s huge class library, so despite being a newer language it already has the power of a large, rigorously tested library behind it). It also offers the RemoteActor class, which helps mask the complexity of setting up TCP connections and constructing messages from the TCP bytestream; it allows a level of transparency between physical nodes when communicating with an actor which may (but does not have to be) located on a different physical machine.
So here’s my working RemoteActor code for a basic source/sink. This seems to work for me, and I spent quite a while trial-and-erroring to make it so. So, here’s the code.
My basicsource.scala:
package com.sds.testing
import scala.actors.Actor
import scala.actors.remote.RemoteActor
import scala.actors.remote.RemoteActor._
import scala.actors.remote.Node
object BasicSourceApp {
def main(args: Array[String]) : Unit = {
if (args.length == 2) {
val remoteport = args(1).toInt
val peer = Node(args(0), remoteport)
val source = new RemoteSource(peer)
source.start()
}
else {
println("usage: scala BasicSourceApp [RemoteHostName] [RemotePort]")
}
}
}
class RemoteSource(peer: Node) extends Actor {
def act() {
RemoteActor.classLoader = getClass().getClassLoader()
val sink = select(peer, 'SinkApp)
link(sink)
while (true) {
sink ! "Hello, world!"
Thread sleep 5000
}
}
}
In this, the link() call isn’t actually used, but it can be used to inform this actor that the remote actor has terminated. The application is called, which creates a new RemoteSource with the specified hostname and port number. select() looks up “SinkApp” on the remote peer and creates whatever local proxies it needs. You can then send messages to “sink” using the “!” operator as you would a local actor.
And, here’s my basicsink.scala
package com.sds.testing
import scala.actors.Actor
import scala.actors.Actor._
import scala.actors.remote.RemoteActor
import scala.actors.remote.RemoteActor._
object BasicSinkApp {
def main(args: Array[String]) : Unit = {
if (args.length == 1) {
val port = args(0).toInt
val sink = new RemoteSink(port)
sink.start()
}
else {
println("usage: scala BasicSinkApp [LocalPort]")
}
}
}
class RemoteSink(port: Int) extends Actor {
RemoteActor.classLoader = getClass().getClassLoader()
def act() {
alive(port)
register('SinkApp, self)
while (true) {
receive {
case msg =>
println(msg)
}
}
}
}
Here, alive() creates a socket and binds to it using the specified port, and register maps “SinkApp” to this actor, such that the select() call from the source can succeed. Afterward, all the sink does is repeatedly check its own mailbox, and prints out whatever messages it’s received. These few calls make for very little boilerplate around Scala’s standard message passing code.
The code for this post was heavily borrowed fromthe simple code posted at dirkmeister.blogspot.com, but it took me a while to make even that work, so I figured some even-more-simplistic tutorial code might be useful.
11 comments
[...] ditch a bunch of the Java code I spend my day staring at and just start again! There’s a couple of great articles out there already that cover remote actors – so I’m not going to reproduce that here [...]
Great article! It saved me many hours of websurfing to try and understand the remote actor system.. :)
The link to dirkmeister.de is a bit old, so if anyone is interested, you can find the page here (moved to blogspot): http://dirkmeister.blogspot.com/2008/12/remote-actors-in-scala.html
Thanks! I’ve updated the link in the body of this post. I had been wondering where the original had gone, so thanks for finding it for me!
First, thanks for the post. I am new to Scala (and remote actors are one of the reasons I am learning this languages) so I want to get this example running. However, I am unable to figure out how to run this exactly with 2.8.
I peformed scalac file1 file2
however, I am not sure where to go next. How do I actually wunthe BasicSinkApp and BasicSourceApp? (I am finding the differences between scala and scalac a little confusing)
Thanks in advance and I apologize if the answer is obvious.
I should add that if I enter the interpreter, import com.sds.testing
I can create the necessary objects. I am just having a heck of a time getting the class path to work to call the object BasicSourceApp for example directly from the command line.
Hi Marc,
It sounds like you’re blindingly close to getting this to work. I run the following commands:
sds@ikeq:/tmp$ scalac source.scala
sds@ikeq:/tmp$ scalac sink.scala
sds@ikeq:/tmp$ scala com.sds.testing.BasicSinkApp 55000
Then, in another terminal I run:
sds@ikeq:/tmp$ scala com.sds.testing.BasicSourceApp localhost 55000
One omission on my part was that the BasicSinkApp didn’t print a helpful message describing its inputs if run without any arguments. I’ve updated this post to fix that.
Hope that helps!
I ran across this blog looking for a way to find out if the RemoteActor actually exists…good info on the Actor#link method. Unfortunately there doesn’t appear to be a timeout value for the Actor#link method…so if the RemoteActor doesn’t resolve the thread trying to link will just hang forever. How do people solve this issue?
Hi Matt, apologies for taking some time to respond to your message! For reference, I’m using Scala 2.8.0 in this response; perhaps some of the libraries have changed for the upcoming 2.9.0 release.
As far as I see, link() does not block at any point. I dug around in the remoteactor libraries to see what the real behaviour is.
The select() call creates a local Proxy for the remote entity. This Proxy is created regardless of whether the remote entity exists or not; ultimately, the Proxy creates a TcpService object which attempts to form a connection to the remote actor. Note that I say “attempts”; if the connection is not successful, then the TcpService will try to connect() again next time the Source sends data.
However, messages are buffered if there is no connection. This includes the link() command. So if the Sink becomes live after the Source, the link() command and any messages subsequently sent will propagate the next time the Source attempts to send another message.
Interestingly, this exposes what I would consider to be a subtle bug: buffered messages are sent in reverse order.
In effect, the remoteactor libraries assume that the remote entity exists, regardless of whether it actually does.
[...] post is most closely related to my other posts on Remote Actors: [1] [...]
Your post says “and each message has a “mailbox” for incoming messages”. I guess it should be “and each actor …”. Thanks for the article.
Ah, of course; I’ve fixed the body text. Well spotted!