Starting with Scala 2.10.0, the Scala Actors library is deprecated. In Scala 2.10.0 the default actor library is Akka.
To ease the migration from Scala Actors to Akka we are providing the
Actor Migration Kit (AMK). The AMK consists of an extension to Scala
Actors which is enabled by including the scala-actors-migration.jar
on a project's classpath. In addition, Akka 2.1 includes features,
such as the ActorDSL
singleton, which enable a simpler conversion of
code using Scala Actors to Akka. The purpose of this document is to
guide users through the migration process and explain how to use the
AMK.
This guide has the following structure. In Section "Limitations of the Migration Kit" we outline the main limitations of the migration kit. In Section "Migration Overview" we describe the migration process and talk about changes in the Scala distribution that make the migration possible. Finally, in Section "Step by Step Guide for Migrating to Akka" we show individual steps, with working examples, that are recommended when migrating from Scala Actors to Akka's actors.
A disclaimer: concurrent code is notorious for bugs that are hard to debug and fix. Due to differences between the two actor implementations it is possible that errors appear. It is recommended to thoroughly test the code after each step of the migration process.
-
Relying on termination reason and bidirectional behavior with
link
method - Scala and Akka actors have different fault-handling and actor monitoring models. In Scala linked actors terminate if one of the linked parties terminates abnormally. If termination is tracked explicitly (byself.trapExit
) the actor receives the termination reason from the failed actor. This functionality can not be migrated to Akka with the AMK. The AMK allows migration only for the Akka monitoring mechanism. Monitoring is different than linking because it is unidirectional and the termination reason is now known. If monitoring support is not enough to migrate the user code there are two possible workarounds:- Postpone the migration of linking to the last possible moment (Step 4). Then when moving to Akka create an supervision hierarchy that will handle faults.
- Make all actor failures explicit and send user defined messages for each type of failure in the actor. For example, in the master-slave configuration, slave catches errors explicitly and notifies the master about the failure by sending a message containing the type of failure.
-
Usage of the
restart
method - Akka does not provide explicit restart of actors so we can not provide the smooth migration for this use-case. The user must change the system so there are no usages of therestart
method. -
Usage of method
getState
- Akka actors do not have explicit state so this functionality can not be migrated. The user code must not havegetState
invocations. -
Not starting actors right after instantiation - Akka actors are automatically started when instantiated. Users will have to reshape their system so it starts all the actors right after their instantiation.
-
Method
mailboxSize
does not exist in Akka and therefore can not be migrated. This method is seldom used and can easily be removed.
In Scala 2.10.0 tactors reside inside the Scala distribution as a separate jar ( scala-actors.jar ), and the their interface is deprecated. The distribution also includes Akka actors in the akka-actor.jar. The AMK resides both in the Scala actors and in the akka-actor.jar. Future major releases of Scala will not contain Scala actors and the AMK.
To start the migration user needs to add the scala-actors.jar and the scala-actors-migration.jar to the build of their projects. Addition of scala-actors.jar and scala-actors-migration.jar enables the usage of the AMK described below. These artifacts reside in the Scala Tools repository and in the Scala distribution.
Actor Migration Kit should be used in 5 steps. Each step is designed to introduce minimal changes
to the code base and allows users to run all system tests after it. In the first four steps of the migration
the code will use the Scala actors implementation. However, the methods and class signatures will be transformed to closely resemble Akka.
The migration kit on the Scala side introduces a new actor type (StashingActor
) and enforces access to actors through the ActorRef
interface.
It also enforces creation of actors through special methods on the ActorDSL
object. In these steps it will be possible to migrate one
actor at a time. This reduces the possibility of complex errors that are caused by several bugs introduced at the same time.
After the migration on the Scala side is complete the user should change import statements and change
the library used to Akka. On the Akka side, the ActorDSL
and the ActWithStash
allow
modeling the react
construct of Scala Actors and their life cycle. This step migrates all actors to the Akka back-end and could introduce bugs in the system. Once code is migrated to Akka, users will be able to use all the features of Akka.
In this chapter we will go through 5 steps of the actor migration. After each step the code can be tested for possible errors. In the first 4 steps one can migrate one actor at a time and test the functionality. However, the last step migrates all actors to Akka and it can be tested only as a whole. After this step the system should have the same functionality as before, however it will use the Akka actor library.
The Scala actors library provides public access to multiple types of actors. They are organized in the class hierarchy and each subclass
provides slightly richer functionality. To make further steps of the migration easier we will first change each actor in the system to be of type Actor
.
This migration step is straightforward since the Actor
class is located at the bottom of the hierarchy and provides the broadest functionality.
The Actors from the Scala library should be migrated according to the following rules:
-
class MyServ extends Reactor[T]
->class MyServ extends Actor
Note that
Reactor
provides an additional type parameter which represents the type of the messages received. If user code uses that information then one needs to: i) apply pattern matching with explicit type, or ii) do the downcast of a message fromAny
to the typeT
. -
class MyServ extends ReplyReactor
->class MyServ extends Actor
-
class MyServ extends DaemonActor
->class MyServ extends Actor
To pair the functionality of the
DaemonActor
add the following line to the class definition.override def scheduler: IScheduler = DaemonScheduler
In Akka, actors can be accessed only through the narrow interface called ActorRef
. Instances of ActorRef
can be acquired either
by invoking an actor
method on the ActorDSL
object or through the actorOf
method on an instance of an ActorRefFactory
.
In the Scala side of AMK we provide a subset of the Akka ActorRef
and the ActorDSL
which is the actual singleton object in the Akka library.
This step of the migration makes all accesses to actors through ActorRef
s. First, we show how to migrate common patterns for instantiating
Scala Actor
s. Then we show how to overcome issues with the different interfaces of ActorRef
and Actor
, respectively.
The translation rules for actor instantiation (the following rules require importing scala.actors.migration._
):
-
Constructor Call Instantiation
val myActor = new MyActor(arg1, arg2) myActor.start()
should be replaced with
ActorDSL.actor(new MyActor(arg1, arg2))
-
DSL for Creating Actors
val myActor = actor { // actor definition }
should be replaced with
val myActor = ActorDSL.actor(new Actor { def act() { // actor definition } })
-
Object Extended from the
Actor
Traitobject MyActor extends Actor { // MyActor definition } MyActor.start()
should be replaced with
class MyActor extends Actor { // MyActor definition } object MyActor { val ref = ActorDSL.actor(new MyActor) }
All accesses to the object
MyActor
should be replaced with accesses toMyActor.ref
.
Note that Akka actors are always started on instantiation. In case actors in the migrated system are created and started at different locations, and changing this can affect the behavior of the system, users need to change the code so actors are started right after instantiation.
At this point we have changed all the actor instantiations to return ActorRef
s, however, we are not done yet.
There are differences in the interface of ActorRef
s and Actor
s so we need to change the methods invoked on each migrated instance.
Unfortunately, some of the methods that Scala Actor
s provide can not be migrated. For the following methods users need to find a workaround:
-
getState()
- actors in Akka are managed by their supervising actors and are restarted by default. In that scenario state of an actor is not relevant. -
restart()
- explicitly restarts a Scala actor. There is no corresponding functionality in Akka.
All other Actor
methods need to be translated to two methods that exist on the ActorRef. The translation is achieved by the rules described below.
Note that all the rules require the following imports:
import scala.concurrent.duration._
import scala.actors.migration.pattern.ask
import scala.actors.migration._
import scala.concurrent._
Additionally rules 1-3 require an implicit Timeout
with infinite duration defined in the scope. However, since Akka does not allow for infinite timeouts, we will use
100 years. For example:
implicit val timeout = Timeout(36500 days)
Rules:
-
!!(msg: Any): Future[Any]
gets replaced with?
. This rule will change a return type to thescala.concurrent.Future
which might not type check. Sincescala.concurrent.Future
has broader functionality than the previously returned one, this type error can be easily fixed with local changes:actor !! message -> respActor ? message
-
!![A] (msg: Any, handler: PartialFunction[Any, A]): Future[A]
gets replaced with?
. The handler can be extracted as a separate function and then applied to the generated future result. The result of a handle should yield another future like in the following example:val handler: PartialFunction[Any, T] = ... // handler actor !! (message, handler) -> (respActor ? message) map handler
-
!? (msg: Any): Any
gets replaced with?
and explicit blocking on the returned future:actor !? message -> Await.result(respActor ? message, Duration.Inf)
-
!? (msec: Long, msg: Any): Option[Any]
gets replaced with?
and explicit blocking on the future:actor !? (dur, message) -> val res = respActor.?(message)(Timeout(dur milliseconds)) val optFut = res map (Some(_)) recover { case _ => None } Await.result(optFut, Duration.Inf)
Public methods that are not mentioned here are declared public for purposes of the actors DSL. They can be used only inside the actor definition so their migration is not relevant in this step.
At this point all actors inherit the Actor
trait, we instantiate actors through special factory methods,
and all actors are accessed through the ActorRef
interface.
Now we need to change all actors to the StashingActor
class from the AMK. This class behaves exactly the same like Scala Actor
but, additionally, provides methods that correspond to methods in Akka's Actor
trait. This allows easy, step by step, migration to the Akka behavior.
To change user base to the new type of actor all actors should extend the StashingActor
instead of the Actor
. Apply the
following rule:
class MyActor extends Actor -> class MyActor extends StashingActor
After this change code will not compile. The StashingActor
trait does not support receive
/receiveWithin
methods. These methods need to be replaced with usage of react
/reactWithin
.
We present the transformation for two simplest scenarios: series of receives, and receive within a loop. For other scenarions users should
devise a translation based on these two.
-
Series of
receive
methods with code before and afterdef act() = { // do before receive { // handler 1 } // in between receive { // handler 2 } // after }
should be replaced with the following code
def act() = { // do before react (({ // handler 1 }: PartialFunction[Any, Unit]).andThen { x => // in between react (({ case msg => // handler 2 }: PartialFunction[Any, Unit]).andThen { x => // after }) }) }
The
andThen
combinator is used to avoid duplication of\\ after
code in each case of handlers. -
Receive inside a loop that terminates based on a condition.
def act() = { var c = true while (c) { // before body receive { case msg => // process case "exit" => c = false } // after receive } // after loop }
should be replaced with
def act() = { var c = true loopWhile(c) { // before body react (({ case msg => // process case "exit" => c = false }: PartialFunction[Any, Unit]).andThen { x => // after receive if (c == false) { // after loop } }) } }
Additionally, to make the code compile, users must add the override
keyword before the act
method, and to create
the empty receive
method in the code. Method act
needs to be overriden since its implementation in StashingActor
mimics the message processing loop of Akka. The changes are shown in the following example:
class MyActor extends StashingActor {
// dummy receive method (not used for now)
def receive = {case x => x}
override def act() {
// old code with methods receive changed to react.
}
}
After this point user can run the test suite and the whole system should behave as before. The StashingActor
and Actor
use the same infrastructure so the system
should behave exactly the same.
In this section we describe how to remove the act
method from StashingActor
s and how to
change the methods used in the StashingActor
to resemble Akka. Since this step can be complex, it is recommended
to do changes one actor at a time. In Scala, an actor's behavior is defined by implementing the act
method. Logically, an actor is a concurrent process
which executes the body of its act
method, and then terminates. In Akka, the behavior is defined by using a global message
handler which processes the messages in the actor's mailbox one by one. The message handler is a partial function, returned by the receive
method,
which gets applied to each message.
Since the behavior of Akka methods in the StashingActor
depends on the removal of the act
method we have to do that first. Then we will give the translation
rules for translating individual methods of the scala.actors.Actor
trait.
In the following list we present the translation rules for common message processing patterns. This list is not
exhaustive and it covers only some common patterns. However, users can migrate more complex act
methods to Akka by looking
at existing translation rules and extending them for more complex situations.
A note about nested react
/reactWithin
calls: the message handling
partial function needs to be expanded with additional constructs that
bring it closer to the Akka model. Although these changes can be
complicated, migration is possible for an arbitrary level of
nesting. See below for examples.
A note about using receive
/receiveWithin
with complex control
flow: migration can be complicated since it requires refactoring the
act
method. A receive
call can be modeled using react
and
andThen
on the message processing partial function. Again, simple
examples are shown below.
-
If there is any code in the
act
method that is being executed before the firstloop
withreact
that code should be moved to thepreStart
method.def act() { // some code loop { react { ... } } }
should be replaced with
override def preStart() { // some code } def act() { loop { react{ ... } } }
This rule should be used in other patterns as well if there is code before the first react.
-
When
act
is in the form of a simpleloop
with a nestedreact
use the following pattern.def act() = { loop { react { // body } } }
should be replaced with
def receive = { // body }
-
When
act
contains aloopWhile
construct use the following translation.def act() = { loopWhile(c) { react { case x: Int => // do task if (x == 42) { c = false } } } }
should be replaced with
def receive = { case x: Int => // do task if (x == 42) { context.stop(self) } }
-
When
act
contains nestedreact
s use the following rule:def act() = { var c = true loopWhile(c) { react { case x: Int => // do task if (x == 42) { c = false } else { react { case y: String => // do nested task } } } } }
should be replaced with
def receive = { case x: Int => // do task if (x == 42) { context.stop(self) } else { context.become(({ case y: String => // do nested task }: Receive).andThen(x => { unstashAll() context.unbecome() }).orElse { case x => stash(x) }) } }
-
For
reactWithin
method use the following translation rule:loop { reactWithin(t) { case TIMEOUT => // timeout processing code case msg => // message processing code } }
should be replaced with
import scala.concurrent.duration._ context.setReceiveTimeout(t millisecond) def receive = { case ReceiveTimeout => // timeout processing code case msg => // message processing code }
-
Exception handling is done in a different way in Akka. To mimic Scala actors behavior apply the following rule
def act() = { loop { react { case msg => // work that can fail } } } override def exceptionHandler = { case x: Exception => println("got exception") }
should be replaced with
def receive = PFCatch({ case msg => // work that can fail }, { case x: Exception => println("got exception") })
where
PFCatch
is defined asclass PFCatch(f: PartialFunction[Any, Unit], handler: PartialFunction[Exception, Unit]) extends PartialFunction[Any, Unit] { def apply(x: Any) = { try { f(x) } catch { case e: Exception if handler.isDefinedAt(e) => handler(e) } } def isDefinedAt(x: Any) = f.isDefinedAt(x) } object PFCatch { def apply(f: PartialFunction[Any, Unit], handler: PartialFunction[Exception, Unit]) = new PFCatch(f, handler) }
PFCatch
is not included in the AMK as it can stay as the permanent feature in the migrated code and the AMK will be removed with the next major release. Once the whole migration is complete fault-handling can also be converted to the Akka supervision.
After we have removed the act
method we should rename the methods that do not exist in Akka but have similar functionality. In the following list we present
the list of differences and their translation:
-
exit()
/exit(reason)
- should be replaced withcontext.stop(self)
-
receiver
- should be replaced withself
-
reply(msg)
- should be replaced withsender ! msg
-
link(actor)
- In Akka, linking of actors is done partially by supervision and partially by actor monitoring. In the AMK we support only the monitoring method so the complete Scala functionality can not be migrated.The difference between linking and watching is that watching actors always receive the termination notification. However, instead of matching on the Scala
Exit
message that contains the reason of termination the Akka watching returns theTerminated(a: ActorRef)
message that contains only theActorRef
. The functionality of getting the reason for termination is not supported by the migration. It can be done in Akka, after the Step 4, by organizing the actors in a supervision hierarchy.If the actor that is watching does not match the
Terminated
message, and this message arrives, it will be terminated with theDeathPactException
. Note that this will happen even when the watched actor terminated normally. In Scala linked actors terminate, with the same termination reason, only if one of the actors terminates abnormally.If the system can not be migrated solely with watching the user has the two alternatives described in "Limitations of the Migration Kit".
NOTE: There is another subtle difference between Scala and Akka actors. In Scala,
link
/watch
to the already dead actor will not have affect. In Akka, watching the already dead actor will result in sending theTerminated
message. This can give unexpected behavior in the Step 5 of the migration guide.
At this point user code is ready to operate on Akka actors. Now we can switch the actors library from Scala to
Akka actors. In order to do this configure the build to exclude the scala-actors.jar
and the scala-actors-migration.jar
and add the akka-actor.jar. The AMK is built to work only with Akka actors version 2.1 which are included in the Scala distribution
and can be configured by these instructions. During
the RC phase the Akka RC number should match the Scala one (e.g. Scala 2.10.0-RC2 runs with Akka 2.1-RC2).
After this change the compilation will fail due to different package names and slight differences in the API. We will have to change each imported actor from scala to Akka. Following is the non-exhaustive list of package names that need to be changed:
scala.actors._ -> akka.actor._
scala.actors.migration.pattern.ask -> akka.pattern.ask
scala.actors.migration.Timeout -> akka.util.Timeout
Occurrences of StashingActor
must be replaced with ActWithStash
(in the akka.actor.ActorDSL
object). This can be done conveniently
using a renaming import (using an import selector clause):
import akka.actor.ActorDSL.{ ActWithStash => StashingActor }
This imports Akka's ActWithStash
and renames it to
StashingActor
. This way, it is not necessary to textually replace
all occurrences of StashingActor
.
Also, method declarations def receive =
in ActWithStash
should be prepended with override
.
In Scala actors the stash
method needs a message as a parameter. For example:
def receive = {
...
case x => stash(x)
}
In Akka only the currently processed message can be stashed. Therefore replace the above example with:
def receive = {
...
case x => stash()
}
The Akka actors are organized in Actor systems. Each actor that is instantiated
must belong to one ActorSystem
. To achieve this add an ActorSystem
instance to each actor instatiation call as a first argument. The following example
shows the transformation.
To achieve this transformation you need to have an actor system instantiated. For example:
val system = ActorSystem("migration-system")
Then apply the following transformation:
ActorDSL.actor(...) -> ActorDSL.actor(system)(...)
Finally, Scala programs are terminating when all the non-daemon threads and actors finish. With Akka the program ends when all the non-daemon threads finish and all actor systems are shut down.
Actor systems need to be explicitly terminated before the program can exit. This is achieved by invoking the shutdown
method on an Actor system.
TODO Philipp: Paragraph about remoting. alive(port: Int): Unit - starts the remote service -> this done by configuration in Akka register(name, actor) - passing the name to the actorOf
All of the code snippets presented in this document can be found in the Scala test suite as test files with the prefix actmig
.
This document and the Actor Migration Kit were designed and implemented by: Vojin Jovanovic and Philipp Haller
If you find any issues or rough edges please report them at the Scala Bugtracker. During the RC release cycles bugs will be fixed within several working days thus that would be the best time to try the AMK on an application.