Skip to content

Commit 4b2d98c

Browse files
committed
+act #2075 Added possibility to pass an ExecutionContext to an ActorSystem
1 parent 32b76ad commit 4b2d98c

8 files changed

Lines changed: 173 additions & 23 deletions

File tree

akka-actor-tests/src/test/scala/akka/actor/ActorSystemSpec.scala

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ import language.postfixOps
77
import akka.testkit._
88
import org.scalatest.junit.JUnitSuiteLike
99
import com.typesafe.config.ConfigFactory
10-
import scala.concurrent.Await
10+
import scala.concurrent.{ ExecutionContext, Await, Future }
1111
import scala.concurrent.duration._
1212
import java.util.concurrent.{ RejectedExecutionException, ConcurrentLinkedQueue }
1313
import akka.util.Timeout
1414
import akka.japi.Util.immutableSeq
15-
import scala.concurrent.Future
1615
import akka.pattern.ask
1716
import akka.dispatch._
1817
import com.typesafe.config.Config
@@ -110,6 +109,19 @@ object ActorSystemSpec {
110109
override def dispatcher(): MessageDispatcher = instance
111110
}
112111

112+
class TestExecutionContext(testActor: ActorRef, underlying: ExecutionContext) extends ExecutionContext {
113+
114+
def execute(runnable: Runnable): Unit = {
115+
testActor ! "called"
116+
underlying.execute(runnable)
117+
}
118+
119+
def reportFailure(t: Throwable): Unit = {
120+
testActor ! "failed"
121+
underlying.reportFailure(t)
122+
}
123+
}
124+
113125
val config = s"""
114126
akka.extensions = ["akka.actor.TestExtension"]
115127
slow {
@@ -305,6 +317,54 @@ class ActorSystemSpec extends AkkaSpec(ActorSystemSpec.config) with ImplicitSend
305317
}
306318
}
307319

320+
"work with a passed in ExecutionContext" in {
321+
val ecProbe = TestProbe()
322+
val ec = new ActorSystemSpec.TestExecutionContext(ecProbe.ref, ExecutionContexts.global())
323+
324+
val system2 = ActorSystem(name = "default", defaultExecutionContext = Some(ec))
325+
326+
try {
327+
val ref = system2.actorOf(Props(new Actor {
328+
def receive = {
329+
case "ping" sender ! "pong"
330+
}
331+
}))
332+
333+
val probe = TestProbe()
334+
335+
ref.tell("ping", probe.ref)
336+
337+
ecProbe.expectMsg(1.second, "called")
338+
probe.expectMsg(1.second, "pong")
339+
} finally {
340+
shutdown(system2)
341+
}
342+
}
343+
344+
"not use passed in ExecutionContext if executor is configured" in {
345+
val ecProbe = TestProbe()
346+
val ec = new ActorSystemSpec.TestExecutionContext(ecProbe.ref, ExecutionContexts.global())
347+
348+
val config = ConfigFactory.parseString("akka.actor.default-dispatcher.executor = \"fork-join-executor\"")
349+
val system2 = ActorSystem(name = "default", config = Some(config), defaultExecutionContext = Some(ec))
350+
351+
try {
352+
val ref = system2.actorOf(Props(new Actor {
353+
def receive = {
354+
case "ping" sender ! "pong"
355+
}
356+
}))
357+
358+
val probe = TestProbe()
359+
360+
ref.tell("ping", probe.ref)
361+
362+
ecProbe.expectNoMsg()
363+
probe.expectMsg(1.second, "pong")
364+
} finally {
365+
shutdown(system2)
366+
}
367+
}
308368
}
309369

310370
}

akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,19 @@ class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference(ActorSystem.fin
7272

7373
{
7474
c.getString("type") should equal("Dispatcher")
75-
c.getString("executor") should equal("fork-join-executor")
75+
c.getString("executor") should equal("default-executor")
7676
c.getDuration("shutdown-timeout", TimeUnit.MILLISECONDS) should equal(1 * 1000)
7777
c.getInt("throughput") should equal(5)
7878
c.getDuration("throughput-deadline-time", TimeUnit.MILLISECONDS) should equal(0)
7979
c.getBoolean("attempt-teamwork") should equal(true)
8080
}
8181

82+
//Default executor config
83+
{
84+
val pool = c.getConfig("default-executor")
85+
pool.getString("fallback") should equal("fork-join-executor")
86+
}
87+
8288
//Fork join executor config
8389

8490
{

akka-actor/src/main/resources/reference.conf

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,23 @@ akka {
238238

239239
# Which kind of ExecutorService to use for this dispatcher
240240
# Valid options:
241+
# - "default-executor" requires a "default-executor" section
241242
# - "fork-join-executor" requires a "fork-join-executor" section
242243
# - "thread-pool-executor" requires a "thread-pool-executor" section
243244
# - A FQCN of a class extending ExecutorServiceConfigurator
244-
executor = "fork-join-executor"
245+
executor = "default-executor"
246+
247+
# This will be used if you have set "executor = "default-executor"".
248+
# If an ActorSystem is created with a given ExecutionContext, this
249+
# ExecutionContext will be used as the default executor for all
250+
# dispatchers in the ActorSystem configured with
251+
# executor = "default-executor". Note that "default-executor"
252+
# is the default value for executor, and therefore used if not
253+
# specified otherwise. If no ExecutionContext is given,
254+
# the executor configured in "fallback" will be used.
255+
default-executor {
256+
fallback = "fork-join-executor"
257+
}
245258

246259
# This will be used if you have set "executor = "fork-join-executor""
247260
fork-join-executor {

akka-actor/src/main/scala/akka/actor/ActorSystem.scala

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ object ActorSystem {
5656
def create(name: String): ActorSystem = apply(name)
5757

5858
/**
59-
* Creates a new ActorSystem with the name "default", and the specified Config, then
59+
* Creates a new ActorSystem with the specified name, and the specified Config, then
6060
* obtains the current ClassLoader by first inspecting the current threads' getContextClassLoader,
6161
* then tries to walk the stack to find the callers class loader, then falls back to the ClassLoader
6262
* associated with the ActorSystem class.
@@ -66,12 +66,29 @@ object ActorSystem {
6666
def create(name: String, config: Config): ActorSystem = apply(name, config)
6767

6868
/**
69-
* Creates a new ActorSystem with the name "default", the specified Config, and specified ClassLoader
69+
* Creates a new ActorSystem with the specified name, the specified Config, and specified ClassLoader
7070
*
7171
* @see <a href="http://typesafehub.github.io/config/v1.2.0/" target="_blank">The Typesafe Config Library API Documentation</a>
7272
*/
7373
def create(name: String, config: Config, classLoader: ClassLoader): ActorSystem = apply(name, config, classLoader)
7474

75+
/**
76+
* Creates a new ActorSystem with the specified name, the specified Config, the specified ClassLoader,
77+
* and the specified ExecutionContext. The ExecutionContext will be used as the default executor inside this ActorSystem.
78+
* If [[null]] is passed in for the Config, ClassLoader and/or ExecutionContext parameters, the respective default value
79+
* will be used. If no Config is given, the default reference config will be obtained from the ClassLoader.
80+
* If no ClassLoader is given, it obtains the current ClassLoader by first inspecting the current
81+
* threads' getContextClassLoader, then tries to walk the stack to find the callers class loader, then
82+
* falls back to the ClassLoader associated with the ActorSystem class. If no ExecutionContext is given, the
83+
* system will fallback to the executor configured under "akka.actor.default-dispatcher.default-executor.fallback".
84+
* Note that the given ExecutionContext will be used by all dispatchers that have been configured with
85+
* executor = "default-executor", including those that have not defined the executor setting and thereby fallback
86+
* to the default of "default-dispatcher.executor".
87+
*
88+
* @see <a href="http://typesafehub.github.io/config/v1.2.0/" target="_blank">The Typesafe Config Library API Documentation</a>
89+
*/
90+
def create(name: String, config: Config, classLoader: ClassLoader, defaultExecutionContext: ExecutionContext): ActorSystem = apply(name, Option(config), Option(classLoader), Option(defaultExecutionContext))
91+
7592
/**
7693
* Creates a new ActorSystem with the name "default",
7794
* obtains the current ClassLoader by first inspecting the current threads' getContextClassLoader,
@@ -88,27 +105,41 @@ object ActorSystem {
88105
* associated with the ActorSystem class.
89106
* Then it loads the default reference configuration using the ClassLoader.
90107
*/
91-
def apply(name: String): ActorSystem = {
92-
val classLoader = findClassLoader()
93-
apply(name, ConfigFactory.load(classLoader), classLoader)
94-
}
108+
def apply(name: String): ActorSystem = apply(name, None, None, None)
95109

96110
/**
97-
* Creates a new ActorSystem with the name "default", and the specified Config, then
111+
* Creates a new ActorSystem with the specified name, and the specified Config, then
98112
* obtains the current ClassLoader by first inspecting the current threads' getContextClassLoader,
99113
* then tries to walk the stack to find the callers class loader, then falls back to the ClassLoader
100114
* associated with the ActorSystem class.
101115
*
102116
* @see <a href="http://typesafehub.github.io/config/v1.2.0/" target="_blank">The Typesafe Config Library API Documentation</a>
103117
*/
104-
def apply(name: String, config: Config): ActorSystem = apply(name, config, findClassLoader())
118+
def apply(name: String, config: Config): ActorSystem = apply(name, Option(config), None, None)
105119

106120
/**
107-
* Creates a new ActorSystem with the name "default", the specified Config, and specified ClassLoader
121+
* Creates a new ActorSystem with the specified name, the specified Config, and specified ClassLoader
108122
*
109123
* @see <a href="http://typesafehub.github.io/config/v1.2.0/" target="_blank">The Typesafe Config Library API Documentation</a>
110124
*/
111-
def apply(name: String, config: Config, classLoader: ClassLoader): ActorSystem = new ActorSystemImpl(name, config, classLoader).start()
125+
def apply(name: String, config: Config, classLoader: ClassLoader): ActorSystem = apply(name, Option(config), Option(classLoader), None)
126+
127+
/**
128+
* Creates a new ActorSystem with the specified name,
129+
* the specified ClassLoader if given, otherwise obtains the current ClassLoader by first inspecting the current
130+
* threads' getContextClassLoader, then tries to walk the stack to find the callers class loader, then
131+
* falls back to the ClassLoader associated with the ActorSystem class.
132+
* If an ExecutionContext is given, it will be used as the default executor inside this ActorSystem.
133+
* If no ExecutionContext is given, the system will fallback to the executor configured under "akka.actor.default-dispatcher.default-executor.fallback".
134+
* The system will use the passed in config, or falls back to the deafult reference configuration using the ClassLoader.
135+
*
136+
* @see <a href="http://typesafehub.github.io/config/v1.2.0/" target="_blank">The Typesafe Config Library API Documentation</a>
137+
*/
138+
def apply(name: String, config: Option[Config] = None, classLoader: Option[ClassLoader] = None, defaultExecutionContext: Option[ExecutionContext] = None): ActorSystem = {
139+
val cl = classLoader.getOrElse(findClassLoader())
140+
val appConfig = config.getOrElse(ConfigFactory.load(cl))
141+
new ActorSystemImpl(name, appConfig, cl, defaultExecutionContext).start()
142+
}
112143

113144
/**
114145
* Settings are the overall ActorSystem Settings which also provides a convenient access to the Config object.
@@ -454,7 +485,7 @@ abstract class ExtendedActorSystem extends ActorSystem {
454485
private[akka] def printTree: String
455486
}
456487

457-
private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config, classLoader: ClassLoader) extends ExtendedActorSystem {
488+
private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config, classLoader: ClassLoader, defaultExecutionContext: Option[ExecutionContext]) extends ExtendedActorSystem {
458489

459490
if (!name.matches("""^[a-zA-Z0-9][a-zA-Z0-9-]*$"""))
460491
throw new IllegalArgumentException(
@@ -552,7 +583,7 @@ private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config,
552583
val mailboxes: Mailboxes = new Mailboxes(settings, eventStream, dynamicAccess, deadLetters)
553584

554585
val dispatchers: Dispatchers = new Dispatchers(settings, DefaultDispatcherPrerequisites(
555-
threadFactory, eventStream, scheduler, dynamicAccess, settings, mailboxes))
586+
threadFactory, eventStream, scheduler, dynamicAccess, settings, mailboxes, defaultExecutionContext))
556587

557588
val dispatcher: ExecutionContextExecutor = dispatchers.defaultGlobalDispatcher
558589

akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
package akka.dispatch
66

77
import java.util.concurrent._
8-
import akka.event.Logging.{ Error, LogEventException }
8+
import akka.event.Logging.{ Debug, Error, LogEventException }
99
import akka.actor._
1010
import akka.dispatch.sysmsg._
11-
import akka.event.EventStream
12-
import com.typesafe.config.Config
11+
import akka.event.{ BusLogging, EventStream }
12+
import com.typesafe.config.{ ConfigFactory, Config }
1313
import akka.util.{ Unsafe, Index }
1414
import scala.annotation.tailrec
1515
import scala.concurrent.forkjoin.{ ForkJoinTask, ForkJoinPool }
@@ -19,6 +19,7 @@ import scala.concurrent.ExecutionContextExecutor
1919
import scala.concurrent.duration.FiniteDuration
2020
import scala.util.control.NonFatal
2121
import scala.util.Try
22+
import java.{ util ju }
2223

2324
final case class Envelope private (val message: Any, val sender: ActorRef)
2425

@@ -318,7 +319,7 @@ abstract class MessageDispatcherConfigurator(_config: Config, val prerequisites:
318319
def dispatcher(): MessageDispatcher
319320

320321
def configureExecutor(): ExecutorServiceConfigurator = {
321-
config.getString("executor") match {
322+
def configurator(executor: String): ExecutorServiceConfigurator = executor match {
322323
case null | "" | "fork-join-executor" new ForkJoinExecutorConfigurator(config.getConfig("fork-join-executor"), prerequisites)
323324
case "thread-pool-executor" new ThreadPoolExecutorConfigurator(config.getConfig("thread-pool-executor"), prerequisites)
324325
case fqcn
@@ -332,6 +333,11 @@ abstract class MessageDispatcherConfigurator(_config: Config, val prerequisites:
332333
.format(fqcn, config.getString("id"), classOf[Config], classOf[DispatcherPrerequisites]), exception)
333334
}).get
334335
}
336+
337+
config.getString("executor") match {
338+
case "default-executor" new DefaultExecutorServiceConfigurator(config.getConfig("default-executor"), prerequisites, configurator(config.getString("default-executor.fallback")))
339+
case other configurator(other)
340+
}
335341
}
336342
}
337343

@@ -423,3 +429,26 @@ class ForkJoinExecutorConfigurator(config: Config, prerequisites: DispatcherPrer
423429
config.getInt("parallelism-max")))
424430
}
425431
}
432+
433+
class DefaultExecutorServiceConfigurator(config: Config, prerequisites: DispatcherPrerequisites, fallback: ExecutorServiceConfigurator) extends ExecutorServiceConfigurator(config, prerequisites) {
434+
val provider: ExecutorServiceFactoryProvider =
435+
prerequisites.defaultExecutionContext match {
436+
case Some(ec)
437+
prerequisites.eventStream.publish(Debug("DefaultExecutorServiceConfigurator", this.getClass, s"Using passed in ExecutionContext as default executor for this ActorSystem. If you want to use a different executor, please specify one in akka.actor.default-dispatcher.default-executor."))
438+
439+
new AbstractExecutorService with ExecutorServiceFactory with ExecutorServiceFactoryProvider {
440+
def createExecutorServiceFactory(id: String, threadFactory: ThreadFactory): ExecutorServiceFactory = this
441+
def createExecutorService: ExecutorService = this
442+
def shutdown(): Unit = ()
443+
def isTerminated: Boolean = false
444+
def awaitTermination(timeout: Long, unit: TimeUnit): Boolean = false
445+
def shutdownNow(): ju.List[Runnable] = ju.Collections.emptyList()
446+
def execute(command: Runnable): Unit = ec.execute(command)
447+
def isShutdown: Boolean = false
448+
}
449+
case None fallback
450+
}
451+
452+
def createExecutorServiceFactory(id: String, threadFactory: ThreadFactory): ExecutorServiceFactory =
453+
provider.createExecutorServiceFactory(id, threadFactory)
454+
}

akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import scala.concurrent.duration.Duration
1313
import akka.ConfigurationException
1414
import akka.actor.Deploy
1515
import akka.util.Helpers.ConfigOps
16+
import scala.concurrent.ExecutionContext
1617

1718
/**
1819
* DispatcherPrerequisites represents useful contextual pieces when constructing a MessageDispatcher
@@ -24,6 +25,7 @@ trait DispatcherPrerequisites {
2425
def dynamicAccess: DynamicAccess
2526
def settings: ActorSystem.Settings
2627
def mailboxes: Mailboxes
28+
def defaultExecutionContext: Option[ExecutionContext]
2729
}
2830

2931
/**
@@ -35,7 +37,8 @@ private[akka] case class DefaultDispatcherPrerequisites(
3537
val scheduler: Scheduler,
3638
val dynamicAccess: DynamicAccess,
3739
val settings: ActorSystem.Settings,
38-
val mailboxes: Mailboxes) extends DispatcherPrerequisites
40+
val mailboxes: Mailboxes,
41+
val defaultExecutionContext: Option[ExecutionContext]) extends DispatcherPrerequisites
3942

4043
object Dispatchers {
4144
/**

akka-docs/rst/java/dispatchers.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ Default dispatcher
1111
------------------
1212

1313
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
14-
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
14+
The default dispatcher can be configured, and is by default a ``Dispatcher`` with the specified ``default-executor``.
15+
If an ActorSystem is created with an ExecutionContext passed in, this ExecutionContext will be used as the default executor for all
16+
dispatchers in this ActorSystem. If no ExecutionContext is given, it will fallback to the executor specified in
17+
``akka.actor.default-dispatcher.default-executor.fallback``. By default this is a "fork-join-executor", which
18+
gives excellent performance in most cases.
1519

1620
.. _dispatcher-lookup-java:
1721

akka-docs/rst/scala/dispatchers.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ Default dispatcher
1111
------------------
1212

1313
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
14-
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
14+
The default dispatcher can be configured, and is by default a ``Dispatcher`` with the specified ``default-executor``.
15+
If an ActorSystem is created with an ExecutionContext passed in, this ExecutionContext will be used as the default executor for all
16+
dispatchers in this ActorSystem. If no ExecutionContext is given, it will fallback to the executor specified in
17+
``akka.actor.default-dispatcher.default-executor.fallback``. By default this is a "fork-join-executor", which
18+
gives excellent performance in most cases.
1519

1620
.. _dispatcher-lookup-scala:
1721

0 commit comments

Comments
 (0)