Thursday, February 04, 2010

Breaking my head with message passing and Scala Actors

I recently started working on a personal project on which a friend of mine is helping. After some discussion, we thought Scala might be a good bet to use as the development platform of our choice (and we integrate Spring into Scala). Anyway for that, I got into Scala actors. Actors appear easy code – all you have to do is create an actor (if you use Actor.actor construct, it starts automatically, otherwise you have to invoke start) and from somewhere keep sending messages to the Actor.

So let us first define what message that I want to send using Case Classes. Refer the documentation on case classes.


case class Message(someData : String)


Now let us create our component which is an Actor and for each string passed in the constructor, we append the message (Some stupid behavior, but serves the example here)




class MyActor(toInform: Array[String]) extends scala.actors.Actor{
private val noticeTo = toInform
def act(){
loop{
react{
case Message(r) =>{
for(item<-noticeTo)
println(item+"_"+r)
}
}
}
}
}


Now with the following code to test the above actor




val actor = new MyActor(Array("Krishna"))
actor ! "Welcome"


With all the excitement in the world, you run the test and it just hangs in there, nothing happens :). So you suddenly realize "YOU FORGOT TO START THE ACTOR". Damn! it was the actor.start that is causing the issue. So you cleverly add the actor.start statement. ("you" meaning "me")




val actor = new MyActor(Array("Krishna"))
actor.start //do not forget this :)
actor ! "Welcome"


You run the test again, to have no success. Then you realize that your actor expects Message(r) where as you are sending a String. So you change that




val actor = new MyActor(Array("Krishna"))
actor.start //do not forget this :)
actor ! Message("Welcome")
//actor.exit


Also notice the commented out explicit kill of the exit call outside the actor. It is not advised to be doing that. Remeber that "!" is a send-and-continue kinda call (asynchronous call). So before the actor is scheduled to work on the message, it might be killed. Instead it is advised that you specify an "Exit" like message within the case on the Actor. Now you run the test and it works :) Hurray!!!!



Later, smart-ass like you (this time, it is you :)), decide to write a very bad code (ok, you is not really you, for now lets assume the actor code i wrote is simply perfect) like shown below.




object Launcher extends Application{
val actor = new MyActor(null)
actor start() //do not forget this
actor ! Message("Welcome")
actor ! "E" //this is the exit message.
}


You repeat the test again :) and this time it does nothing, it appears to be blocked :). Again....damn... so how would we know what the issue is? You spend a your day-off trying to figure out what is wrong with this simple code...then after spending 10 hours trying all the magic tricks (aparently, i know too many magic tricks that never work, hence the time), you realize the actor must be dead (netbeans threads view would show what threads are running and you never see any FJ Threads). So the actor died! So what can kill an actor? - call to exit(), or an exception!!!! There it is .. so let us change the actor code so that it can catch the exception.




class MyActor(toInform: Array[String]) extends scala.actors.Actor{
private val noticeTo = toInform
def act(){
loop{
react{
case Message(r) =>{
try{
for(item<-noticeTo)
println(item+"_"+r)
}catch{
case e => e.printStackTrace
}
}
case "E" => exit
}
}
}
}


Run the test again and notice the stacktrace!




java.lang.NullPointerException
at scala.collection.mutable.ArrayOps$ofRef.length(ArrayOps.scala:68)
at scala.collection.IndexedSeqLike$class.foreach(IndexedSeqLike.scala:86)
at scala.collection.mutable.ArrayOps.foreach(ArrayOps.scala:20)
at tryscala.MyActor$$anonfun$act$1$$anonfun$apply$1.apply(Launcher.scala:13)
at tryscala.MyActor$$anonfun$act$1$$anonfun$apply$1.apply(Launcher.scala:10)
at scala.actors.Reaction$$anonfun$$init$$1.apply(Reaction.scala:33)
at scala.actors.Reaction$$anonfun$$init$$1.apply(Reaction.scala:29)
at scala.actors.ReactorTask.run(ReactorTask.scala:33)
at scala.actors.scheduler.ForkJoinScheduler$$anon$1.compute(ForkJoinScheduler.scala:111)
at scala.concurrent.forkjoin.RecursiveAction.exec(RecursiveAction.java:147)
at scala.concurrent.forkjoin.ForkJoinTask.quietlyExec(ForkJoinTask.java:422)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.mainLoop(ForkJoinWorkerThread.java:340)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:325)
BUILD SUCCESSFUL (total time: 7 seconds)


So the fix is to :) pass in an empty array at least, instead of sending in a null ( use Array.empty).



Clearly, this is not the exact sample that I was running, it was a little bit complicated and myself being totally new to Scala had a hard time trying to figure out that an exception can kill scala Actors!!! I know I am dumb, but with this post, I want to save you from considering yourself dumb after trying very hard for a few hours.

1 comment:

Joe said...

Hey thanks for that blog on scala - good tips for the beginners (like me). I see that the post is just over a year ago - did you stick with it? Do any more scala since? I am doing a final year project and am trying to pass a list to an actor. I am trying to figure out whether or not a list gets passed or just the reference?!?! anyway, happy coding.