=doc #18128 explain PoisonPill interaction with PA

This commit is contained in:
Konrad Malawski 2015-08-19 18:20:13 +02:00
parent 7679b7e63f
commit 8c4cc9a93f
14 changed files with 341 additions and 22 deletions

View file

@ -291,13 +291,13 @@ delivery is an explicit ACKRETRY protocol. In its simplest form this requires
The third becomes necessary by virtue of the acknowledgements not being guaranteed The third becomes necessary by virtue of the acknowledgements not being guaranteed
to arrive either. An ACK-RETRY protocol with business-level acknowledgements is to arrive either. An ACK-RETRY protocol with business-level acknowledgements is
supported by :ref:`at-least-once-delivery` of the Akka Persistence module. Duplicates can be supported by :ref:`at-least-once-delivery-scala` of the Akka Persistence module. Duplicates can be
detected by tracking the identifiers of messages sent via :ref:`at-least-once-delivery`. detected by tracking the identifiers of messages sent via :ref:`at-least-once-delivery-scala`.
Another way of implementing the third part would be to make processing the messages Another way of implementing the third part would be to make processing the messages
idempotent on the level of the business logic. idempotent on the level of the business logic.
Another example of implementing all three requirements is shown at Another example of implementing all three requirements is shown at
:ref:`reliable-proxy` (which is now superseded by :ref:`at-least-once-delivery`). :ref:`reliable-proxy` (which is now superseded by :ref:`at-least-once-delivery-scala`).
Event Sourcing Event Sourcing
-------------- --------------
@ -313,7 +313,7 @@ components may consume the event stream as a means to replicate the component
state on a different continent or to react to changes). If the components state on a different continent or to react to changes). If the components
state is lost—due to a machine failure or by being pushed out of a cache—it can state is lost—due to a machine failure or by being pushed out of a cache—it can
easily be reconstructed by replaying the event stream (usually employing easily be reconstructed by replaying the event stream (usually employing
snapshots to speed up the process). :ref:`event-sourcing` is supported by snapshots to speed up the process). :ref:`event-sourcing-scala` is supported by
Akka Persistence. Akka Persistence.
Mailbox with Explicit Acknowledgement Mailbox with Explicit Acknowledgement

View file

@ -542,4 +542,74 @@ public class LambdaPersistenceDocTest {
//#view-update //#view-update
} }
}; };
static Object o14 = new Object() {
//#safe-shutdown
final class Shutdown {
}
class MyPersistentActor extends AbstractPersistentActor {
@Override
public String persistenceId() {
return "some-persistence-id";
}
@Override
public PartialFunction<Object, BoxedUnit> receiveCommand() {
return ReceiveBuilder
.match(Shutdown.class, shutdown -> {
context().stop(self());
})
.match(String.class, msg -> {
System.out.println(msg);
persist("handle-" + msg, e -> System.out.println(e));
})
.build();
}
@Override
public PartialFunction<Object, BoxedUnit> receiveRecover() {
return ReceiveBuilder.matchAny(any -> {}).build();
}
}
//#safe-shutdown
public void usage() {
final ActorSystem system = ActorSystem.create("example");
final ActorRef persistentActor = system.actorOf(Props.create(MyPersistentActor.class));
//#safe-shutdown-example-bad
// UN-SAFE, due to PersistentActor's command stashing:
persistentActor.tell("a", ActorRef.noSender());
persistentActor.tell("b", ActorRef.noSender());
persistentActor.tell(PoisonPill.getInstance(), ActorRef.noSender());
// order of received messages:
// a
// # b arrives at mailbox, stashing; internal-stash = [b]
// # PoisonPill arrives at mailbox, stashing; internal-stash = [b, Shutdown]
// PoisonPill is an AutoReceivedMessage, is handled automatically
// !! stop !!
// Actor is stopped without handling `b` nor the `a` handler!
//#safe-shutdown-example-bad
//#safe-shutdown-example-good
// SAFE:
persistentActor.tell("a", ActorRef.noSender());
persistentActor.tell("b", ActorRef.noSender());
persistentActor.tell(new Shutdown(), ActorRef.noSender());
// order of received messages:
// a
// # b arrives at mailbox, stashing; internal-stash = [b]
// # Shutdown arrives at mailbox, stashing; internal-stash = [b, Shutdown]
// handle-a
// # unstashing; internal-stash = [Shutdown]
// b
// handle-b
// # unstashing; internal-stash = []
// Shutdown
// -- stop --
//#safe-shutdown-example-good
}
};
} }

View file

@ -13,7 +13,6 @@ import akka.japi.Function;
import akka.japi.Procedure; import akka.japi.Procedure;
import akka.persistence.*; import akka.persistence.*;
import scala.Option;
import java.io.Serializable; import java.io.Serializable;
public class PersistenceDocTest { public class PersistenceDocTest {
@ -504,7 +503,7 @@ public class PersistenceDocTest {
} }
}; };
static Object o13 = new Object() { static Object o14 = new Object() {
//#view //#view
class MyView extends UntypedPersistentView { class MyView extends UntypedPersistentView {
@Override @Override
@ -534,4 +533,74 @@ public class PersistenceDocTest {
//#view-update //#view-update
} }
}; };
static Object o13 = new Object() {
//#safe-shutdown
final class Shutdown {}
class MyPersistentActor extends UntypedPersistentActor {
@Override
public String persistenceId() {
return "some-persistence-id";
}
@Override
public void onReceiveCommand(Object msg) throws Exception {
if (msg instanceof Shutdown) {
context().stop(self());
} else if (msg instanceof String) {
System.out.println(msg);
persist("handle-" + msg, new Procedure<String>() {
@Override
public void apply(String param) throws Exception {
System.out.println(param);
}
});
} else unhandled(msg);
}
@Override
public void onReceiveRecover(Object msg) throws Exception {
// handle recovery...
}
}
//#safe-shutdown
public void usage() {
final ActorSystem system = ActorSystem.create("example");
final ActorRef persistentActor = system.actorOf(Props.create(MyPersistentActor.class));
//#safe-shutdown-example-bad
// UN-SAFE, due to PersistentActor's command stashing:
persistentActor.tell("a", ActorRef.noSender());
persistentActor.tell("b", ActorRef.noSender());
persistentActor.tell(PoisonPill.getInstance(), ActorRef.noSender());
// order of received messages:
// a
// # b arrives at mailbox, stashing; internal-stash = [b]
// # PoisonPill arrives at mailbox, stashing; internal-stash = [b, Shutdown]
// PoisonPill is an AutoReceivedMessage, is handled automatically
// !! stop !!
// Actor is stopped without handling `b` nor the `a` handler!
//#safe-shutdown-example-bad
//#safe-shutdown-example-good
// SAFE:
persistentActor.tell("a", ActorRef.noSender());
persistentActor.tell("b", ActorRef.noSender());
persistentActor.tell(new Shutdown(), ActorRef.noSender());
// order of received messages:
// a
// # b arrives at mailbox, stashing; internal-stash = [b]
// # Shutdown arrives at mailbox, stashing; internal-stash = [b, Shutdown]
// handle-a
// # unstashing; internal-stash = [Shutdown]
// b
// handle-b
// # unstashing; internal-stash = []
// Shutdown
// -- stop --
//#safe-shutdown-example-good
}
};
} }

View file

@ -537,6 +537,8 @@ different one. Outside of an actor and if no reply is needed the second
argument can be ``null``; if a reply is needed outside of an actor you can use argument can be ``null``; if a reply is needed outside of an actor you can use
the ask-pattern described next.. the ask-pattern described next..
.. _actors-ask-lambda:
Ask: Send-And-Receive-Future Ask: Send-And-Receive-Future
---------------------------- ----------------------------

View file

@ -376,10 +376,36 @@ restarts of the persistent actor.
.. _Thundering herd problem: https://en.wikipedia.org/wiki/Thundering_herd_problem .. _Thundering herd problem: https://en.wikipedia.org/wiki/Thundering_herd_problem
.. _safe-shutdown-lambda:
Safely shutting down persistent actors
--------------------------------------
Special care should be given when when shutting down persistent actors from the outside.
With normal Actors it is often acceptable to use the special :ref:`PoisonPill <poison-pill-java>` message
to signal to an Actor that it should stop itself once it receives this message in fact this message is handled
automatically by Akka, leaving the target actor no way to refuse stopping itself when given a poison pill.
This can be dangerous when used with :class:`PersistentActor` due to the fact that incoming commands are *stashed* while
the persistent actor is awaiting confirmation from the Journal that events have been written when ``persist()`` was used.
Since the incoming commands will be drained from the Actor's mailbox and put into it's internal stash while awaiting the
confirmation (thus, before calling the persist handlers) the Actor **may receive and (auto)handle the PoisonPill
before it processes the other messages which have been put into its stash**, causing a pre-mature shutdown of the Actor.
.. warning::
Consider using explicit shut-down messages instead of :class:`PoisonPill` when working with persistent actors.
The example below highlights how messages arrive in the Actor's mailbox and how they interact with it's internal stashing
mechanism when ``persist()`` is used, notice the early stop behaviour that occurs when ``PoisonPill`` is used:
.. includecode:: code/docs/persistence/LambdaPersistenceDocTest.java#safe-shutdown
.. includecode:: code/docs/persistence/LambdaPersistenceDocTest.java#safe-shutdown-example-bad
.. includecode:: code/docs/persistence/LambdaPersistenceDocTest.java#safe-shutdown-example-good
.. _persistent-views-java-lambda: .. _persistent-views-java-lambda:
Views Persistent Views
===== ================
.. warning:: .. warning::

View file

@ -82,7 +82,7 @@ If your usage does not require a live stream, you can disable refreshing by usin
.. includecode:: code/docs/persistence/PersistenceQueryDocTest.java#all-persistence-ids-snap .. includecode:: code/docs/persistence/PersistenceQueryDocTest.java#all-persistence-ids-snap
``EventsByPersistenceId`` is a query equivalent to replaying a :ref:`PersistentActor <event-sourcing>`, ``EventsByPersistenceId`` is a query equivalent to replaying a :ref:`PersistentActor <event-sourcing-scala>`,
however, since it is a stream it is possible to keep it alive and watch for additional incoming events persisted by the however, since it is a stream it is possible to keep it alive and watch for additional incoming events persisted by the
persistent actor identified by the given ``persistenceId``. Most journals will have to revert to polling in order to achieve persistent actor identified by the given ``persistenceId``. Most journals will have to revert to polling in order to achieve
this, which can be configured using the ``RefreshInterval`` query hint: this, which can be configured using the ``RefreshInterval`` query hint:
@ -135,7 +135,7 @@ specialised query object, as demonstrated in the sample below:
Performance and denormalization Performance and denormalization
=============================== ===============================
When building systems using :ref:`event-sourcing` and CQRS (`Command & Query Responsibility Segragation`_) techniques When building systems using :ref:`event-sourcing-scala` and CQRS (`Command & Query Responsibility Segragation`_) techniques
it is tremendously important to realise that the write-side has completely different needs from the read-side, it is tremendously important to realise that the write-side has completely different needs from the read-side,
and separating those concerns into datastores that are optimised for either side makes it possible to offer the best and separating those concerns into datastores that are optimised for either side makes it possible to offer the best
expirience for the write and read sides independently. expirience for the write and read sides independently.

View file

@ -379,6 +379,32 @@ restarts of the persistent actor.
.. _Thundering herd problem: https://en.wikipedia.org/wiki/Thundering_herd_problem .. _Thundering herd problem: https://en.wikipedia.org/wiki/Thundering_herd_problem
.. _safe-shutdown-java:
Safely shutting down persistent actors
--------------------------------------
Special care should be given when when shutting down persistent actors from the outside.
With normal Actors it is often acceptable to use the special :ref:`PoisonPill <poison-pill-java>` message
to signal to an Actor that it should stop itself once it receives this message in fact this message is handled
automatically by Akka, leaving the target actor no way to refuse stopping itself when given a poison pill.
This can be dangerous when used with :class:`PersistentActor` due to the fact that incoming commands are *stashed* while
the persistent actor is awaiting confirmation from the Journal that events have been written when ``persist()`` was used.
Since the incoming commands will be drained from the Actor's mailbox and put into it's internal stash while awaiting the
confirmation (thus, before calling the persist handlers) the Actor **may receive and (auto)handle the PoisonPill
before it processes the other messages which have been put into its stash**, causing a pre-mature shutdown of the Actor.
.. warning::
Consider using explicit shut-down messages instead of :class:`PoisonPill` when working with persistent actors.
The example below highlights how messages arrive in the Actor's mailbox and how they interact with it's internal stashing
mechanism when ``persist()`` is used, notice the early stop behaviour that occurs when ``PoisonPill`` is used:
.. includecode:: code/docs/persistence/PersistenceDocTest.java#safe-shutdown
.. includecode:: code/docs/persistence/PersistenceDocTest.java#safe-shutdown-example-bad
.. includecode:: code/docs/persistence/PersistenceDocTest.java#safe-shutdown-example-good
.. _persistent-views-java: .. _persistent-views-java:
Persistent Views Persistent Views

View file

@ -72,7 +72,7 @@ Processors / PersistentActor
**Akka Persistence:** ``PersistentActor`` **Akka Persistence:** ``PersistentActor``
- Trait that adds journaling to actors (see :ref:`event-sourcing`) and used by applications for - Trait that adds journaling to actors (see :ref:`event-sourcing-scala`) and used by applications for
*event sourcing* or *command sourcing*. Corresponds to ``Eventsourced`` processors in Eventsourced but is not a stackable trait. *event sourcing* or *command sourcing*. Corresponds to ``Eventsourced`` processors in Eventsourced but is not a stackable trait.
- Automatically recovers on start and re-start, by default. :ref:`recovery` can be customized or turned off by - Automatically recovers on start and re-start, by default. :ref:`recovery` can be customized or turned off by
overriding actor life cycle hooks ``preStart`` and ``preRestart``. ``Processor`` takes care that new messages overriding actor life cycle hooks ``preStart`` and ``preRestart``. ``Processor`` takes care that new messages

View file

@ -34,7 +34,7 @@ To extend ``PersistentActor``::
/*...*/ /*...*/
} }
Read more about the persistent actor in the :ref:`documentation for Scala <event-sourcing>` and Read more about the persistent actor in the :ref:`documentation for Scala <event-sourcing-scala>` and
:ref:`documentation for Java <event-sourcing-java>`. :ref:`documentation for Java <event-sourcing-java>`.
Changed processorId to (abstract) persistenceId Changed processorId to (abstract) persistenceId
@ -183,7 +183,7 @@ acknowledgement from the channel is needed to guarantee safe hand-off. Therefore
delivery is provided in a new ``AtLeastOnceDelivery`` trait that is mixed-in to the delivery is provided in a new ``AtLeastOnceDelivery`` trait that is mixed-in to the
persistent actor on the sending side. persistent actor on the sending side.
Read more about at-least-once delivery in the :ref:`documentation for Scala <at-least-once-delivery>` and Read more about at-least-once delivery in the :ref:`documentation for Scala <at-least-once-delivery-scala>` and
:ref:`documentation for Java <at-least-once-delivery-java>`. :ref:`documentation for Java <at-least-once-delivery-java>`.
Default persistence plugins Default persistence plugins

View file

@ -426,7 +426,7 @@ result:
It is always preferable to communicate with other Actors using their ActorRef It is always preferable to communicate with other Actors using their ActorRef
instead of relying upon ActorSelection. Exceptions are instead of relying upon ActorSelection. Exceptions are
* sending messages using the :ref:`at-least-once-delivery` facility * sending messages using the :ref:`at-least-once-delivery-scala` facility
* initiating first contact with a remote system * initiating first contact with a remote system
In all other cases ActorRefs can be provided during Actor creation or In all other cases ActorRefs can be provided during Actor creation or

View file

@ -4,7 +4,7 @@
package docs.persistence package docs.persistence
import akka.actor.{ Actor, ActorRef, ActorSystem, Props } import akka.actor._
import akka.pattern.BackoffSupervisor import akka.pattern.BackoffSupervisor
import akka.persistence._ import akka.persistence._
@ -102,7 +102,7 @@ object PersistenceDocSpec {
object AtLeastOnce { object AtLeastOnce {
//#at-least-once-example //#at-least-once-example
import akka.actor.{ Actor, ActorPath, ActorSelection } import akka.actor.{ Actor, ActorSelection }
import akka.persistence.AtLeastOnceDelivery import akka.persistence.AtLeastOnceDelivery
case class Msg(deliveryId: Long, s: String) case class Msg(deliveryId: Long, s: String)
@ -358,6 +358,107 @@ object PersistenceDocSpec {
//#nested-persistAsync-persistAsync-caller //#nested-persistAsync-persistAsync-caller
} }
object AvoidPoisonPill {
//#safe-shutdown
/** Explicit shutdown message */
case object Shutdown
class SafePersistentActor extends PersistentActor {
override def persistenceId = "safe-actor"
override def receiveCommand: Receive = {
case c: String =>
println(c)
persist(s"handle-$c") { println(_) }
case Shutdown =>
context.stop(self)
}
override def receiveRecover: Receive = {
case _ => // handle recovery here
}
}
//#safe-shutdown
//#safe-shutdown-example-bad
// UN-SAFE, due to PersistentActor's command stashing:
persistentActor ! "a"
persistentActor ! "b"
persistentActor ! PoisonPill
// order of received messages:
// a
// # b arrives at mailbox, stashing; internal-stash = [b]
// # PoisonPill arrives at mailbox, stashing; internal-stash = [b, Shutdown]
// PoisonPill is an AutoReceivedMessage, is handled automatically
// !! stop !!
// Actor is stopped without handling `b` nor the `a` handler!
//#safe-shutdown-example-bad
//#safe-shutdown-example-good
// SAFE:
persistentActor ! "a"
persistentActor ! "b"
persistentActor ! Shutdown
// order of received messages:
// a
// # b arrives at mailbox, stashing; internal-stash = [b]
// # Shutdown arrives at mailbox, stashing; internal-stash = [b, Shutdown]
// handle-a
// # unstashing; internal-stash = [Shutdown]
// b
// handle-b
// # unstashing; internal-stash = []
// Shutdown
// -- stop --
//#safe-shutdown-example-good
class MyPersistAsyncActor extends PersistentActor {
override def persistenceId = "my-stable-persistence-id"
override def receiveRecover: Receive = {
case _ => // handle recovery here
}
//#nested-persistAsync-persistAsync
override def receiveCommand: Receive = {
case c: String =>
sender() ! c
persistAsync(c + "-outer-1") { outer
sender() ! outer
persistAsync(c + "-inner-1") { inner sender() ! inner }
}
persistAsync(c + "-outer-2") { outer
sender() ! outer
persistAsync(c + "-inner-2") { inner sender() ! inner }
}
}
//#nested-persistAsync-persistAsync
}
//#nested-persistAsync-persistAsync-caller
persistentActor ! "a"
persistentActor ! "b"
// order of received messages:
// a
// b
// a-outer-1
// a-outer-2
// b-outer-1
// b-outer-2
// a-inner-1
// a-inner-2
// b-inner-1
// b-inner-2
// which can be seen as the following causal relationship:
// a -> a-outer-1 -> a-outer-2 -> a-inner-1 -> a-inner-2
// b -> b-outer-1 -> b-outer-2 -> b-inner-1 -> b-inner-2
//#nested-persistAsync-persistAsync-caller
}
object View { object View {
import akka.actor.Props import akka.actor.Props

View file

@ -82,7 +82,7 @@ If your usage does not require a live stream, you can disable refreshing by usin
.. includecode:: code/docs/persistence/query/PersistenceQueryDocSpec.scala#all-persistence-ids-snap .. includecode:: code/docs/persistence/query/PersistenceQueryDocSpec.scala#all-persistence-ids-snap
``EventsByPersistenceId`` is a query equivalent to replaying a :ref:`PersistentActor <event-sourcing>`, ``EventsByPersistenceId`` is a query equivalent to replaying a :ref:`PersistentActor <event-sourcing-scala>`,
however, since it is a stream it is possible to keep it alive and watch for additional incoming events persisted by the however, since it is a stream it is possible to keep it alive and watch for additional incoming events persisted by the
persistent actor identified by the given ``persistenceId``. Most journals will have to revert to polling in order to achieve persistent actor identified by the given ``persistenceId``. Most journals will have to revert to polling in order to achieve
this, which can be configured using the ``RefreshInterval`` query hint: this, which can be configured using the ``RefreshInterval`` query hint:
@ -134,7 +134,7 @@ specialised query object, as demonstrated in the sample below:
Performance and denormalization Performance and denormalization
=============================== ===============================
When building systems using :ref:`event-sourcing` and CQRS (`Command & Query Responsibility Segragation`_) techniques When building systems using :ref:`event-sourcing-scala` and CQRS (`Command & Query Responsibility Segragation`_) techniques
it is tremendously important to realise that the write-side has completely different needs from the read-side, it is tremendously important to realise that the write-side has completely different needs from the read-side,
and separating those concerns into datastores that are optimised for either side makes it possible to offer the best and separating those concerns into datastores that are optimised for either side makes it possible to offer the best
expirience for the write and read sides independently. expirience for the write and read sides independently.

View file

@ -61,7 +61,7 @@ Architecture
.. _Community plugins: http://akka.io/community/ .. _Community plugins: http://akka.io/community/
.. _event-sourcing: .. _event-sourcing-scala:
Event sourcing Event sourcing
============== ==============
@ -366,6 +366,31 @@ restarts of the persistent actor.
.. _Thundering herd problem: https://en.wikipedia.org/wiki/Thundering_herd_problem .. _Thundering herd problem: https://en.wikipedia.org/wiki/Thundering_herd_problem
.. _safe-shutdown-scala:
Safely shutting down persistent actors
--------------------------------------
Special care should be given when when shutting down persistent actors from the outside.
With normal Actors it is often acceptable to use the special :ref:`PoisonPill <poison-pill-scala>` message
to signal to an Actor that it should stop itself once it receives this message in fact this message is handled
automatically by Akka, leaving the target actor no way to refuse stopping itself when given a poison pill.
This can be dangerous when used with :class:`PersistentActor` due to the fact that incoming commands are *stashed* while
the persistent actor is awaiting confirmation from the Journal that events have been written when ``persist()`` was used.
Since the incoming commands will be drained from the Actor's mailbox and put into it's internal stash while awaiting the
confirmation (thus, before calling the persist handlers) the Actor **may receive and (auto)handle the PoisonPill
before it processes the other messages which have been put into its stash**, causing a pre-mature shutdown of the Actor.
.. warning::
Consider using explicit shut-down messages instead of :class:`PoisonPill` when working with persistent actors.
The example below highlights how messages arrive in the Actor's mailbox and how they interact with it's internal stashing
mechanism when ``persist()`` is used, notice the early stop behaviour that occurs when ``PoisonPill`` is used:
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#safe-shutdown
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#safe-shutdown-example-bad
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#safe-shutdown-example-good
.. _persistent-views: .. _persistent-views:
@ -519,7 +544,7 @@ If failure messages are left unhandled by the actor, a default warning log messa
No default action is performed on the success messages, however you're free to handle them e.g. in order to delete No default action is performed on the success messages, however you're free to handle them e.g. in order to delete
an in memory representation of the snapshot, or in the case of failure to attempt save the snapshot again. an in memory representation of the snapshot, or in the case of failure to attempt save the snapshot again.
.. _at-least-once-delivery: .. _at-least-once-delivery-scala:
At-Least-Once Delivery At-Least-Once Delivery
====================== ======================

View file

@ -62,8 +62,8 @@ section below.
.. warning:: .. warning::
Due to the synchronous nature of ``TestActorRef`` it will **not** work with some support Due to the synchronous nature of ``TestActorRef`` it will **not** work with some support
traits that Akka provides as they require asynchronous behaviours to function properly. traits that Akka provides as they require asynchronous behaviours to function properly.
Examples of traits that do not mix well with test actor refs are :ref:`PersistentActor <event-sourcing>` Examples of traits that do not mix well with test actor refs are :ref:`PersistentActor <event-sourcing-scala>`
and :ref:`AtLeastOnceDelivery <at-least-once-delivery>` provided by :ref:`Akka Persistence <persistence-scala>`. and :ref:`AtLeastOnceDelivery <at-least-once-delivery-scala>` provided by :ref:`Akka Persistence <persistence-scala>`.
Obtaining a Reference to an :class:`Actor` Obtaining a Reference to an :class:`Actor`
------------------------------------------ ------------------------------------------