Added patterns for initialization for Scala #2297

(cherry-picked from e2aa3b5)
This commit is contained in:
Endre Sándor Varga 2013-02-22 14:13:32 +01:00
parent 469b168ada
commit 7bf9f1f82b
4 changed files with 288 additions and 0 deletions

View file

@ -0,0 +1,95 @@
package docs.actor;
import akka.actor.*;
import akka.japi.Procedure;
import akka.testkit.JavaTestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import scala.Option;
/**
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
*/
public class InitializationDocSpecJava {
static public class PreStartInitExample extends UntypedActor {
public void onReceive(Object message) throws Exception {}
//#preStartInit
@Override
public void preStart() {
// Initialize children here
}
// Overriding postRestart to disable the call to preStart()
// after restarts
@Override
public void postRestart(Throwable reason) {
}
// The default implementation of preRestart() stops all the children
// of the actor. To opt-out from stopping the children, we
// have to override preRestart()
@Override
public void preRestart(Throwable reason, Option<Object> message) {
// Keep the call to postStop(), but no stopping of children
postStop();
}
//#preStartInit
}
public static class MessageInitExample extends UntypedActor {
//#messageInit
private String initializeMe = null;
@Override
public void onReceive(Object message) throws Exception {
if (message.equals("init")) {
initializeMe = "Up and running";
getContext().become(new Procedure<Object>() {
@Override
public void apply(Object message) throws Exception {
if (message.equals("U OK?"))
getSender().tell(initializeMe, getSelf());
}
});
}
}
//#messageInit
}
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("TestSystem");
}
@AfterClass
public static void teardown() {
system.shutdown();
}
@Test
public void testIt() {
new JavaTestKit(system) {{
ActorRef testactor = system.actorOf(new Props(MessageInitExample.class), "testactor");
String probe = "U OK?";
testactor.tell(probe, getRef());
expectNoMsg();
testactor.tell("init", getRef());
testactor.tell(probe, getRef());
expectMsgEquals("Up and running");
}};
}
}

View file

@ -684,3 +684,62 @@ created. This new instance will now be used in the actor references to this acto
state of the failing actor instance is lost if you don't store and restore it in
``preRestart`` and ``postRestart`` callbacks.
Initialization patterns
=======================
The rich lifecycle hooks of Actors provide a useful toolkit to implement various initialization patterns. During the
lifetime of an ``ActorRef``, an actor can potentially go through several restarts, where the old instance is replaced by
a fresh one, invisibly to the outside observer who only sees the ``ActorRef``.
One may think about the new instances as "incarnations". Initialization might be necessary for every incarnation
of an actor, but sometimes one needs initialization to happen only at the birth of the first instance when the
``ActorRef`` is created. The following sections provide patterns for different initialization needs.
Initialization via constructor
------------------------------
Using the constructor for initialization has various benefits. First of all, it makes it possible to use ``val`` fields to store
any state that does not change during the life of the actor instance, making the implementation of the actor more robust.
The constructor is invoked for every incarnation of the actor, therefore the internals of the actor can always assume
that proper initialization happened. This is also the drawback of this approach, as there are cases when one would
like to avoid reinitializing internals on restart. For example, it is often useful to preserve child actors across
restarts. The following section provides a pattern for this case.
Initialization via preStart
---------------------------
The method ``preStart()`` of an actor is only called once directly during the initialization of the first instance, that
is, at creation of its ``ActorRef``. In the case of restarts, ``preStart()`` is called from ``postRestart()``, therefore
if not overridden, ``preStart()`` is called on every incarnation. However, overriding ``postRestart()`` one can disable
this behavior, and ensure that there is only one call to ``preStart()``.
One useful usage of this pattern is to disable creation of new ``ActorRefs`` for children during restarts. This can be
achieved by overriding ``preRestart()``:
.. includecode:: code/docs/actor/InitializationDocSpecJava.java#preStartInit
Please note, that the child actors are *still restarted*, but no new ``ActorRef`` is created. One can recursively apply
the same principles for the children, ensuring that their ``preStart()`` method is called only at the creation of their
refs.
For more information see :ref:`what-restarting-means-scala`.
Initialization via message passing
----------------------------------
There are cases when it is impossible to pass all the information needed for actor initialization in the constructor,
for example in the presence of circular dependencies. In this case the actor should listen for an initialization message,
and use ``become()`` or a finite state-machine state transition to encode the initialized and uninitialized states
of the actor.
.. includecode:: code/docs/actor/InitializationDocSpecJava.java#messageInit
If the actor may receive messages before it has been initialized, a useful tool can be the ``Stash`` to save messages
until the initialization finishes, and replaying them after the actor became initialized.
.. warning::
This pattern should be used with care, and applied only when none of the patterns above are applicable. One of
the potential issues is that messages might be lost when sent to remote actors. Also, publishing an ``ActorRef`` in
an uninitialized state might lead to the condition that it receives a user message before the initialization has been
done.