clarify need to carefully managing blocking, see #2616
This commit is contained in:
parent
ef80428b02
commit
c27389ca1b
7 changed files with 86 additions and 10 deletions
|
|
@ -89,10 +89,9 @@ Actor Best Practices
|
||||||
bothering everyone else needlessly and avoid hogging resources. Translated
|
bothering everyone else needlessly and avoid hogging resources. Translated
|
||||||
to programming this means to process events and generate responses (or more
|
to programming this means to process events and generate responses (or more
|
||||||
requests) in an event-driven manner. Actors should not block (i.e. passively
|
requests) in an event-driven manner. Actors should not block (i.e. passively
|
||||||
wait while occupying a Thread) on some external entity, which might be a
|
wait while occupying a Thread) on some external entity—which might be a
|
||||||
lock, a network socket, etc. The blocking operations should be done in some
|
lock, a network socket, etc.—unless it is unavoidable; in the latter case
|
||||||
special-cased thread which sends messages to the actors which shall act on
|
see below.
|
||||||
them.
|
|
||||||
|
|
||||||
#. Do not pass mutable objects between actors. In order to ensure that, prefer
|
#. Do not pass mutable objects between actors. In order to ensure that, prefer
|
||||||
immutable messages. If the encapsulation of actors is broken by exposing
|
immutable messages. If the encapsulation of actors is broken by exposing
|
||||||
|
|
@ -112,6 +111,53 @@ Actor Best Practices
|
||||||
performance) and it also reduces the number of blocking calls made, since
|
performance) and it also reduces the number of blocking calls made, since
|
||||||
the creation of top-level actors involves synchronous messaging.
|
the creation of top-level actors involves synchronous messaging.
|
||||||
|
|
||||||
|
Blocking Needs Careful Management
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
In some cases it is unavoidable to do blocking operations, i.e. to put a thread
|
||||||
|
to sleep for an indeterminate time, waiting for an external event to occur.
|
||||||
|
Examples are legacy RDBMS drivers or messaging APIs, and the underlying reason
|
||||||
|
in typically that (network) I/O occurs under the covers. When facing this, you
|
||||||
|
may be tempted to just wrap the blocking call inside a :class:`Future` and work
|
||||||
|
with that instead, but this strategy is too simple: you are quite likely to
|
||||||
|
find bottle-necks or run out of memory or threads when the application runs
|
||||||
|
under increased load.
|
||||||
|
|
||||||
|
The non-exhaustive list of adequate solutions to the “blocking problem”
|
||||||
|
includes the following suggestions:
|
||||||
|
|
||||||
|
- Do the blocking call within an actor (or a set of actors managed by a router
|
||||||
|
[:ref:`Java <routing-java>`, :ref:`Scala <routing-scala>`]), making sure to
|
||||||
|
configure a thread pool which is either dedicated for this purpose or
|
||||||
|
sufficiently sized.
|
||||||
|
|
||||||
|
- Do the blocking call within a :class:`Future`, ensuring an upper bound on
|
||||||
|
the number of such calls at any point in time (submitting an unbounded
|
||||||
|
number of tasks of this nature will exhaust your memory or thread limits).
|
||||||
|
|
||||||
|
- Do the blocking call within a :class:`Future`, providing a thread pool with
|
||||||
|
an upper limit on the number of threads which is appropriate for the
|
||||||
|
hardware on which the application runs.
|
||||||
|
|
||||||
|
- Dedicate a single thread to manage a set of blocking resources (e.g. a NIO
|
||||||
|
selector driving multiple channels) and dispatch events as they occur as
|
||||||
|
actor messages.
|
||||||
|
|
||||||
|
The first possibility is especially well-suited for resources which are
|
||||||
|
single-threaded in nature, like database handles which traditionally can only
|
||||||
|
execute one outstanding query at a time and use internal synchronization to
|
||||||
|
ensure this. A common pattern is to create a router for N actors, each of which
|
||||||
|
wraps a single DB connection and handles queries as sent to the router. The
|
||||||
|
number N must then be tuned for maximum throughput, which will vary depending
|
||||||
|
on which DBMS is deployed on what hardware.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Configuring thread pools is a task best delegated to Akka, simply configure
|
||||||
|
in the ``application.conf`` and instantiate through an :class:`ActorSystem`
|
||||||
|
[:ref:`Java <dispatcher-lookup-java>`, :ref:`Scala
|
||||||
|
<dispatcher-lookup-scala>`]
|
||||||
|
|
||||||
What you should not concern yourself with
|
What you should not concern yourself with
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,6 @@ package docs.dispatcher;
|
||||||
|
|
||||||
//#imports
|
//#imports
|
||||||
import akka.actor.*;
|
import akka.actor.*;
|
||||||
import akka.actor.ActorRef;
|
|
||||||
import akka.actor.Props;
|
|
||||||
import akka.actor.UntypedActor;
|
|
||||||
import akka.actor.UntypedActorFactory;
|
|
||||||
//#imports
|
//#imports
|
||||||
|
|
||||||
//#imports-prio
|
//#imports-prio
|
||||||
|
|
@ -37,6 +33,7 @@ import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import scala.Option;
|
import scala.Option;
|
||||||
|
import scala.concurrent.ExecutionContext;
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory;
|
import com.typesafe.config.ConfigFactory;
|
||||||
|
|
||||||
|
|
@ -75,6 +72,14 @@ public class DispatcherDocTestBase {
|
||||||
.withDispatcher("my-pinned-dispatcher"));
|
.withDispatcher("my-pinned-dispatcher"));
|
||||||
//#defining-pinned-dispatcher
|
//#defining-pinned-dispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void compileLookup() {
|
||||||
|
//#lookup
|
||||||
|
// this is scala.concurrent.ExecutionContext
|
||||||
|
// for use with Futures, Scheduler, etc.
|
||||||
|
final ExecutionContext ex = system.dispatchers().lookup("my-dispatcher");
|
||||||
|
//#lookup
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void priorityDispatcher() throws Exception {
|
public void priorityDispatcher() throws Exception {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,15 @@ Default dispatcher
|
||||||
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
|
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
|
||||||
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
|
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
|
||||||
|
|
||||||
|
.. _dispatcher-lookup-java:
|
||||||
|
|
||||||
|
Looking up a Dispatcher
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Dispatchers implement the :class:`ExecutionContext` interface and can thus be used to run :class:`Future` invocations etc.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/dispatcher/DispatcherDocTestBase.java#lookup
|
||||||
|
|
||||||
Setting the dispatcher for an Actor
|
Setting the dispatcher for an Actor
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,13 @@ class DispatcherDocSpec extends AkkaSpec(DispatcherDocSpec.config) {
|
||||||
//#defining-pinned-dispatcher
|
//#defining-pinned-dispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"looking up a dispatcher" in {
|
||||||
|
//#lookup
|
||||||
|
// for use with Futures, Scheduler, etc.
|
||||||
|
implicit val executionContext = system.dispatchers.lookup("my-dispatcher")
|
||||||
|
//#lookup
|
||||||
|
}
|
||||||
|
|
||||||
"defining priority dispatcher" in {
|
"defining priority dispatcher" in {
|
||||||
//#prio-dispatcher
|
//#prio-dispatcher
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,15 @@ Default dispatcher
|
||||||
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
|
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
|
||||||
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
|
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
|
||||||
|
|
||||||
|
.. _dispatcher-lookup-scala:
|
||||||
|
|
||||||
|
Looking up a Dispatcher
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Dispatchers implement the :class:`ExecutionContext` interface and can thus be used to run :class:`Future` invocations etc.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/dispatcher/DispatcherDocSpec.scala#lookup
|
||||||
|
|
||||||
Setting the dispatcher for an Actor
|
Setting the dispatcher for an Actor
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class CreationActor extends Actor {
|
||||||
case result: MathResult ⇒ result match {
|
case result: MathResult ⇒ result match {
|
||||||
case MultiplicationResult(n1, n2, r) ⇒
|
case MultiplicationResult(n1, n2, r) ⇒
|
||||||
println("Mul result: %d * %d = %d".format(n1, n2, r))
|
println("Mul result: %d * %d = %d".format(n1, n2, r))
|
||||||
case DivisionResult(n1, n2, r) ⇒
|
case DivisionResult(n1, n2, r) ⇒
|
||||||
println("Div result: %.0f / %d = %.2f".format(n1, n2, r))
|
println("Div result: %.0f / %d = %.2f".format(n1, n2, r))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class LookupActor extends Actor {
|
||||||
def receive = {
|
def receive = {
|
||||||
case (actor: ActorRef, op: MathOp) ⇒ actor ! op
|
case (actor: ActorRef, op: MathOp) ⇒ actor ! op
|
||||||
case result: MathResult ⇒ result match {
|
case result: MathResult ⇒ result match {
|
||||||
case AddResult(n1, n2, r) ⇒
|
case AddResult(n1, n2, r) ⇒
|
||||||
println("Add result: %d + %d = %d".format(n1, n2, r))
|
println("Add result: %d + %d = %d".format(n1, n2, r))
|
||||||
case SubtractResult(n1, n2, r) ⇒
|
case SubtractResult(n1, n2, r) ⇒
|
||||||
println("Sub result: %d - %d = %d".format(n1, n2, r))
|
println("Sub result: %d - %d = %d".format(n1, n2, r))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue