act #20332 Functionality to automatically load library extensions
This commit is contained in:
parent
2418e610ab
commit
14c08df74a
8 changed files with 209 additions and 53 deletions
|
|
@ -1,4 +1,7 @@
|
|||
akka {
|
||||
# for the akka.actor.ExtensionSpec
|
||||
library-extensions += "akka.actor.InstanceCountingExtension"
|
||||
|
||||
actor {
|
||||
serialize-messages = on
|
||||
warn-about-java-serializer-usage = off
|
||||
|
|
|
|||
|
|
@ -19,33 +19,6 @@ import akka.util.Switch
|
|||
import akka.util.Helpers.ConfigOps
|
||||
import scala.util.control.NoStackTrace
|
||||
|
||||
class JavaExtensionSpec extends JavaExtension with JUnitSuiteLike
|
||||
|
||||
object TestExtension extends ExtensionId[TestExtension] with ExtensionIdProvider {
|
||||
def lookup = this
|
||||
def createExtension(s: ExtendedActorSystem) = new TestExtension(s)
|
||||
}
|
||||
|
||||
// Dont't place inside ActorSystemSpec object, since it will not be garbage collected and reference to system remains
|
||||
class TestExtension(val system: ExtendedActorSystem) extends Extension
|
||||
|
||||
object FailingTestExtension extends ExtensionId[FailingTestExtension] with ExtensionIdProvider {
|
||||
def lookup = this
|
||||
def createExtension(s: ExtendedActorSystem) = new FailingTestExtension(s)
|
||||
|
||||
class TestException extends IllegalArgumentException("ERR") with NoStackTrace
|
||||
}
|
||||
|
||||
// Dont't place inside ActorSystemSpec object, since it will not be garbage collected and reference to system remains
|
||||
class FailingTestExtension(val system: ExtendedActorSystem) extends Extension {
|
||||
// first time the actor is created
|
||||
val ref = system.actorOf(Props.empty, "uniqueName")
|
||||
// but the extension initialization fails
|
||||
// second time it will throw exception when trying to create actor with same name,
|
||||
// but we want to see the first exception every time
|
||||
throw new FailingTestExtension.TestException
|
||||
}
|
||||
|
||||
object ActorSystemSpec {
|
||||
|
||||
class Waves extends Actor {
|
||||
|
|
@ -140,7 +113,6 @@ object ActorSystemSpec {
|
|||
}
|
||||
|
||||
val config = s"""
|
||||
akka.extensions = ["akka.actor.TestExtension"]
|
||||
slow {
|
||||
type="${classOf[SlowDispatcher].getName}"
|
||||
}"""
|
||||
|
|
@ -177,23 +149,6 @@ class ActorSystemSpec extends AkkaSpec(ActorSystemSpec.config) with ImplicitSend
|
|||
shutdown(ActorSystem("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"))
|
||||
}
|
||||
|
||||
"support extensions" in {
|
||||
// TestExtension is configured and should be loaded at startup
|
||||
system.hasExtension(TestExtension) should ===(true)
|
||||
TestExtension(system).system should ===(system)
|
||||
system.extension(TestExtension).system should ===(system)
|
||||
}
|
||||
|
||||
"handle extensions that fail to initialize" in {
|
||||
intercept[FailingTestExtension.TestException] {
|
||||
FailingTestExtension(system)
|
||||
}
|
||||
// same exception should be reported next time
|
||||
intercept[FailingTestExtension.TestException] {
|
||||
FailingTestExtension(system)
|
||||
}
|
||||
}
|
||||
|
||||
"log dead letters" in {
|
||||
val sys = ActorSystem("LogDeadLetters", ConfigFactory.parseString("akka.loglevel=INFO").withFallback(AkkaSpec.testConf))
|
||||
try {
|
||||
|
|
|
|||
140
akka-actor-tests/src/test/scala/akka/actor/ExtensionSpec.scala
Normal file
140
akka-actor-tests/src/test/scala/akka/actor/ExtensionSpec.scala
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package akka.actor
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import akka.testkit.EventFilter
|
||||
import akka.testkit.TestKit._
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
import org.scalatest.junit.JUnitSuiteLike
|
||||
|
||||
import scala.util.control.NoStackTrace
|
||||
|
||||
|
||||
class JavaExtensionSpec extends JavaExtension with JUnitSuiteLike
|
||||
|
||||
object TestExtension extends ExtensionId[TestExtension] with ExtensionIdProvider {
|
||||
def lookup = this
|
||||
def createExtension(s: ExtendedActorSystem) = new TestExtension(s)
|
||||
}
|
||||
|
||||
// Dont't place inside ActorSystemSpec object, since it will not be garbage collected and reference to system remains
|
||||
class TestExtension(val system: ExtendedActorSystem) extends Extension
|
||||
|
||||
object FailingTestExtension extends ExtensionId[FailingTestExtension] with ExtensionIdProvider {
|
||||
def lookup = this
|
||||
def createExtension(s: ExtendedActorSystem) = new FailingTestExtension(s)
|
||||
|
||||
class TestException extends IllegalArgumentException("ERR") with NoStackTrace
|
||||
}
|
||||
|
||||
object InstanceCountingExtension extends ExtensionId[DummyExtensionImpl] with ExtensionIdProvider {
|
||||
val createCount = new AtomicInteger(0)
|
||||
override def createExtension(system: ExtendedActorSystem): DummyExtensionImpl = {
|
||||
createCount.addAndGet(1)
|
||||
new DummyExtensionImpl
|
||||
}
|
||||
override def lookup(): ExtensionId[_ <: Extension] = this
|
||||
}
|
||||
|
||||
class DummyExtensionImpl extends Extension
|
||||
|
||||
// Dont't place inside ActorSystemSpec object, since it will not be garbage collected and reference to system remains
|
||||
class FailingTestExtension(val system: ExtendedActorSystem) extends Extension {
|
||||
// first time the actor is created
|
||||
val ref = system.actorOf(Props.empty, "uniqueName")
|
||||
// but the extension initialization fails
|
||||
// second time it will throw exception when trying to create actor with same name,
|
||||
// but we want to see the first exception every time
|
||||
throw new FailingTestExtension.TestException
|
||||
}
|
||||
|
||||
|
||||
class ExtensionSpec extends WordSpec with Matchers {
|
||||
|
||||
"The ActorSystem extensions support" should {
|
||||
|
||||
"support extensions" in {
|
||||
val config = ConfigFactory.parseString("""akka.extensions = ["akka.actor.TestExtension"]""")
|
||||
val system = ActorSystem("extensions", config)
|
||||
|
||||
// TestExtension is configured and should be loaded at startup
|
||||
system.hasExtension(TestExtension) should ===(true)
|
||||
TestExtension(system).system should ===(system)
|
||||
system.extension(TestExtension).system should ===(system)
|
||||
|
||||
shutdownActorSystem(system)
|
||||
}
|
||||
|
||||
"handle extensions that fail to initialize" in {
|
||||
val system = ActorSystem("extensions")
|
||||
|
||||
intercept[FailingTestExtension.TestException] {
|
||||
FailingTestExtension(system)
|
||||
}
|
||||
// same exception should be reported next time
|
||||
intercept[FailingTestExtension.TestException] {
|
||||
FailingTestExtension(system)
|
||||
}
|
||||
|
||||
shutdownActorSystem(system)
|
||||
}
|
||||
|
||||
|
||||
"fail the actor system if an extension listed in akka.extensions fails to start" in {
|
||||
intercept[RuntimeException]{
|
||||
val system = ActorSystem("failing", ConfigFactory.parseString(
|
||||
"""
|
||||
akka.extensions = ["akka.actor.FailingTestExtension"]
|
||||
"""))
|
||||
|
||||
shutdownActorSystem(system)
|
||||
}
|
||||
}
|
||||
|
||||
"log an error if an extension listed in akka.extensions cannot be loaded" in {
|
||||
val system = ActorSystem("failing", ConfigFactory.parseString(
|
||||
"""
|
||||
akka.extensions = ["akka.actor.MissingExtension"]
|
||||
"""))
|
||||
EventFilter.error("While trying to load extension [akka.actor.MissingExtension], skipping...").intercept()(system)
|
||||
shutdownActorSystem(system)
|
||||
|
||||
}
|
||||
|
||||
"allow for auto-loading of library-extensions" in {
|
||||
val system = ActorSystem("extensions")
|
||||
val listedExtensions = system.settings.config.getStringList("akka.library-extensions")
|
||||
listedExtensions.size should be > 0
|
||||
// could be initalized by other tests, so at least once
|
||||
InstanceCountingExtension.createCount.get() should be > 0
|
||||
|
||||
shutdownActorSystem(system)
|
||||
}
|
||||
|
||||
"fail the actor system if a library-extension fails to start" in {
|
||||
intercept[FailingTestExtension.TestException] {
|
||||
ActorSystem("failing", ConfigFactory.parseString(
|
||||
"""
|
||||
akka.library-extensions += "akka.actor.FailingTestExtension"
|
||||
""").withFallback(ConfigFactory.load()).resolve())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"fail the actor system if a library-extension cannot be loaded" in {
|
||||
intercept[RuntimeException] {
|
||||
ActorSystem("failing", ConfigFactory.parseString(
|
||||
"""
|
||||
akka.library-extensions += "akka.actor.MissingExtension"
|
||||
""").withFallback(ConfigFactory.load()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,14 +7,15 @@ package akka.config
|
|||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.event.DefaultLoggingFilter
|
||||
import akka.event.Logging.DefaultLogger
|
||||
import akka.testkit.AkkaSpec
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.Assertions
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import akka.event.DefaultLoggingFilter
|
||||
|
||||
class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference(ActorSystem.findClassLoader())) {
|
||||
class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference(ActorSystem.findClassLoader())) with Assertions {
|
||||
|
||||
"The default configuration file (i.e. reference.conf)" must {
|
||||
"contain all configuration properties for akka-actor that are used in code with their correct defaults" in {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,16 @@ akka {
|
|||
# setting.
|
||||
log-dead-letters-during-shutdown = on
|
||||
|
||||
# List FQCN of extensions which shall be loaded at actor system startup.
|
||||
# Library extensions are regular extensions that are loaded at startup and are
|
||||
# available for third party library authors to enable auto-loading of extensions when
|
||||
# present on the classpath. This is done by appending entries:
|
||||
# 'library-extensions += "Extension"' in the library `reference.conf`.
|
||||
#
|
||||
# Should not be set by end user applications in 'application.conf', use the extensions property for that
|
||||
#
|
||||
library-extensions = ${?akka.library-extensions} []
|
||||
|
||||
# List FQCN of extensions which shall be loaded at actor system startup.
|
||||
# Should be on the format: 'extensions = ["foo", "bar"]' etc.
|
||||
# See the Akka Documentation for more info about Extensions
|
||||
|
|
|
|||
|
|
@ -779,14 +779,26 @@ private[akka] class ActorSystemImpl(
|
|||
def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean = findExtension(ext) != null
|
||||
|
||||
private def loadExtensions() {
|
||||
immutableSeq(settings.config.getStringList("akka.extensions")) foreach { fqcn ⇒
|
||||
dynamicAccess.getObjectFor[AnyRef](fqcn) recoverWith { case _ ⇒ dynamicAccess.createInstanceFor[AnyRef](fqcn, Nil) } match {
|
||||
case Success(p: ExtensionIdProvider) ⇒ registerExtension(p.lookup())
|
||||
case Success(p: ExtensionId[_]) ⇒ registerExtension(p)
|
||||
case Success(other) ⇒ log.error("[{}] is not an 'ExtensionIdProvider' or 'ExtensionId', skipping...", fqcn)
|
||||
case Failure(problem) ⇒ log.error(problem, "While trying to load extension [{}], skipping...", fqcn)
|
||||
/**
|
||||
* @param throwOnLoadFail Throw exception when an extension fails to load (needed for backwards compatibility)
|
||||
*/
|
||||
def loadExtensions(key: String, throwOnLoadFail: Boolean): Unit = {
|
||||
immutableSeq(settings.config.getStringList(key)) foreach { fqcn ⇒
|
||||
dynamicAccess.getObjectFor[AnyRef](fqcn) recoverWith { case _ ⇒ dynamicAccess.createInstanceFor[AnyRef](fqcn, Nil) } match {
|
||||
case Success(p: ExtensionIdProvider) ⇒ registerExtension(p.lookup())
|
||||
case Success(p: ExtensionId[_]) ⇒ registerExtension(p)
|
||||
case Success(other)⇒
|
||||
if (!throwOnLoadFail) log.error("[{}] is not an 'ExtensionIdProvider' or 'ExtensionId', skipping...", fqcn)
|
||||
else throw new RuntimeException(s"[$fqcn] is not an 'ExtensionIdProvider' or 'ExtensionId'")
|
||||
case Failure(problem) ⇒
|
||||
if (!throwOnLoadFail) log.error(problem, "While trying to load extension [{}], skipping...", fqcn)
|
||||
else throw new RuntimeException(s"While trying to load extension [$fqcn]", problem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadExtensions("akka.library-extensions", throwOnLoadFail = true)
|
||||
loadExtensions("akka.extensions", throwOnLoadFail = false)
|
||||
}
|
||||
|
||||
override def toString: String = lookupRoot.path.root.address.toString
|
||||
|
|
|
|||
|
|
@ -94,3 +94,20 @@ Use it:
|
|||
.. includecode:: code/docs/extension/SettingsExtensionDocTest.java
|
||||
:include: extension-usage-actor
|
||||
|
||||
Library extensions
|
||||
==================
|
||||
A third part library may register it's extension for auto-loading on actor system startup by appending it to
|
||||
``akka.library-extensions`` in its ``reference.conf``.
|
||||
|
||||
::
|
||||
|
||||
akka.library-extensions += "docs.extension.ExampleExtension"
|
||||
|
||||
|
||||
As there is no way to selectively remove such extensions, it should be used with care and only when there is no case
|
||||
where the user would ever want it disabled or have specific support for disabling such sub-features. One example where
|
||||
this could be important is in tests.
|
||||
|
||||
.. warning::
|
||||
The``akka.library-extensions`` must never be assigned (``= ["Extension"]``) instead of appending as this will break
|
||||
the library-extension mechanism and make behavior depend on class path ordering.
|
||||
|
|
|
|||
|
|
@ -87,3 +87,21 @@ Use it:
|
|||
.. includecode:: code/docs/extension/SettingsExtensionDocSpec.scala
|
||||
:include: extension-usage-actor
|
||||
|
||||
|
||||
Library extensions
|
||||
==================
|
||||
A third part library may register it's extension for auto-loading on actor system startup by appending it to
|
||||
``akka.library-extensions`` in its ``reference.conf``.
|
||||
|
||||
::
|
||||
|
||||
akka.library-extensions += "docs.extension.ExampleExtension"
|
||||
|
||||
|
||||
As there is no way to selectively remove such extensions, it should be used with care and only when there is no case
|
||||
where the user would ever want it disabled or have specific support for disabling such sub-features. One example where
|
||||
this could be important is in tests.
|
||||
|
||||
.. warning::
|
||||
The``akka.library-extensions`` must never be assigned (``= ["Extension"]``) instead of appending as this will break
|
||||
the library-extension mechanism and make behavior depend on class path ordering.
|
||||
Loading…
Add table
Add a link
Reference in a new issue