Repository navigation
Mailbox selection for Akka Typed actors #27183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
da93a03
36a592c
2c27135
0c53ddc
f6d24db
cb6ef8c
bd5fcd6
87a924d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /* | ||
| * Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com> | ||
| */ | ||
|
|
||
| package akka.actor.typed; | ||
|
|
||
| public class MailboxSelectorTest { | ||
| // Compile time only test to verify | ||
| // mailbox factories are accessible from Java | ||
|
|
||
| private MailboxSelector def = MailboxSelector.defaultMailbox(); | ||
| private MailboxSelector bounded = MailboxSelector.bounded(1000); | ||
| private MailboxSelector conf = MailboxSelector.fromConfig("somepath"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /* | ||
| * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> | ||
| */ | ||
|
|
||
| package jdocs.akka.typed; | ||
|
|
||
| import akka.Done; | ||
| import akka.actor.testkit.typed.javadsl.TestKitJunitResource; | ||
| import akka.actor.testkit.typed.javadsl.TestProbe; | ||
| import akka.actor.typed.ActorRef; | ||
| import akka.actor.typed.Behavior; | ||
| import akka.actor.typed.MailboxSelector; | ||
| import akka.actor.typed.javadsl.Behaviors; | ||
| import com.typesafe.config.ConfigFactory; | ||
| import org.junit.ClassRule; | ||
| import org.junit.Test; | ||
|
|
||
| public class MailboxDocTest { | ||
|
|
||
| @ClassRule | ||
| public static final TestKitJunitResource testKit = | ||
| new TestKitJunitResource(ConfigFactory.load("mailbox-config-sample.conf")); | ||
|
|
||
| @Test | ||
| public void startSomeActorsWithDifferentMailboxes() { | ||
| TestProbe<Done> testProbe = testKit.createTestProbe(); | ||
| Behavior<String> childBehavior = Behaviors.empty(); | ||
|
|
||
| Behavior<Void> setup = | ||
| Behaviors.setup( | ||
| context -> { | ||
| // #select-mailbox | ||
| context.spawn(childBehavior, "bounded-mailbox-child", MailboxSelector.bounded(100)); | ||
|
|
||
| context.spawn( | ||
| childBehavior, | ||
| "from-config-mailbox-child", | ||
| MailboxSelector.fromConfig("my-app.my-special-mailbox")); | ||
| // #select-mailbox | ||
|
|
||
| testProbe.ref().tell(Done.getInstance()); | ||
| return Behaviors.stopped(); | ||
| }); | ||
|
|
||
| ActorRef<Void> ref = testKit.spawn(setup); | ||
| testProbe.receiveMessage(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| my-app { | ||
| my-special-mailbox { | ||
| mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| /* | ||
| * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> | ||
| */ | ||
|
|
||
| package akka.actor.typed | ||
|
|
||
| import akka.actor.ActorCell | ||
| import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit | ||
| import akka.actor.typed.internal.adapter.ActorContextAdapter | ||
| import akka.actor.typed.scaladsl.AskPattern._ | ||
| import akka.actor.typed.scaladsl.Behaviors | ||
| import akka.dispatch.BoundedMessageQueueSemantics | ||
| import akka.dispatch.BoundedNodeMessageQueue | ||
| import akka.dispatch.MessageQueue | ||
| import akka.dispatch.UnboundedMessageQueueSemantics | ||
| import akka.testkit.EventFilter | ||
| import akka.testkit.TestLatch | ||
| import org.scalatest.WordSpecLike | ||
|
|
||
| import scala.concurrent.Await | ||
| import scala.concurrent.duration._ | ||
|
|
||
| class MailboxSelectorSpec extends ScalaTestWithActorTestKit(""" | ||
| specific-mailbox { | ||
| mailbox-type = "akka.dispatch.NonBlockingBoundedMailbox" | ||
| mailbox-capacity = 4 | ||
| } | ||
| akka.loggers = [ akka.testkit.TestEventListener ] | ||
| """) with WordSpecLike { | ||
|
|
||
| // FIXME #24348: eventfilter support in typed testkit | ||
| import scaladsl.adapter._ | ||
| implicit val untypedSystem = system.toUntyped | ||
|
|
||
| case class WhatsYourMailbox(replyTo: ActorRef[MessageQueue]) | ||
| private def behavior: Behavior[WhatsYourMailbox] = | ||
| Behaviors.setup { context => | ||
| Behaviors.receiveMessage[WhatsYourMailbox] { | ||
| case WhatsYourMailbox(replyTo) => | ||
| val mailbox = context match { | ||
| case adapter: ActorContextAdapter[_] => | ||
| adapter.untypedContext match { | ||
| case cell: ActorCell => | ||
| cell.mailbox.messageQueue | ||
| } | ||
| } | ||
|
|
||
| replyTo ! mailbox | ||
| Behaviors.stopped | ||
| } | ||
| } | ||
|
|
||
| "The Mailbox selectors" must { | ||
| "default to unbounded" in { | ||
| val actor = spawn(behavior) | ||
| val mailbox = actor.ask(WhatsYourMailbox).futureValue | ||
| mailbox shouldBe a[UnboundedMessageQueueSemantics] | ||
| } | ||
|
|
||
| "select a bounded mailbox" in { | ||
| val actor = spawn(behavior, MailboxSelector.bounded(3)) | ||
| val mailbox = actor.ask(WhatsYourMailbox).futureValue | ||
| mailbox shouldBe a[BoundedMessageQueueSemantics] | ||
| // capacity is private so only way to test is to fill mailbox | ||
| } | ||
|
|
||
| "set capacity on a bounded mailbox" in { | ||
| val latch = TestLatch(1) | ||
| val actor = spawn(Behaviors.receiveMessage[String] { | ||
| case "one" => | ||
| // block here so we can fill mailbox up | ||
| Await.ready(latch, 10.seconds) | ||
| Behaviors.same | ||
| case _ => | ||
| Behaviors.same | ||
| }, MailboxSelector.bounded(2)) | ||
| actor ! "one" // actor will block here | ||
| actor ! "two" | ||
| EventFilter.warning(start = "received dead letter:", occurrences = 1).intercept { | ||
| // one or both of these doesn't fit in mailbox | ||
| // depending on race with how fast actor consumes | ||
| actor ! "three" | ||
| actor ! "four" | ||
| } | ||
| latch.open() | ||
| } | ||
|
|
||
| "select an arbitrary mailbox from config" in { | ||
| val actor = spawn(behavior, MailboxSelector.fromConfig("specific-mailbox")) | ||
| val mailbox = actor.ask(WhatsYourMailbox).futureValue | ||
| mailbox shouldBe a[BoundedMessageQueueSemantics] | ||
| mailbox.asInstanceOf[BoundedNodeMessageQueue].capacity should ===(4) | ||
|
|
||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| /* | ||
| * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> | ||
| */ | ||
|
|
||
| package docs.akka.typed | ||
|
|
||
| import akka.Done | ||
| import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit | ||
| import akka.actor.typed.Behavior | ||
| import akka.actor.typed.MailboxSelector | ||
| import akka.actor.typed.scaladsl.Behaviors | ||
| import com.typesafe.config.ConfigFactory | ||
| import org.scalatest.WordSpecLike | ||
|
|
||
| class MailboxDocSpec | ||
| extends ScalaTestWithActorTestKit(ConfigFactory.load("mailbox-config-sample.conf")) | ||
| with WordSpecLike { | ||
|
|
||
| "Specifying mailbox through props" must { | ||
| "work" in { | ||
| val probe = createTestProbe[Done]() | ||
| val childBehavior: Behavior[String] = Behaviors.empty | ||
| val parent: Behavior[Unit] = Behaviors.setup { context => | ||
| // #select-mailbox | ||
| context.spawn(childBehavior, "bounded-mailbox-child", MailboxSelector.bounded(100)) | ||
|
|
||
| val props = MailboxSelector.fromConfig("my-app.my-special-mailbox") | ||
| context.spawn(childBehavior, "from-config-mailbox-child", props) | ||
| // #select-mailbox | ||
|
|
||
| probe.ref ! Done | ||
| Behaviors.stopped | ||
| } | ||
| spawn(parent) | ||
|
|
||
| probe.receiveMessage() | ||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,8 @@ package akka.dispatch | |
|
|
||
| import java.lang.reflect.ParameterizedType | ||
| import java.util.concurrent.ConcurrentHashMap | ||
| import java.util.concurrent.atomic.AtomicReference | ||
|
|
||
| import akka.ConfigurationException | ||
| import akka.actor.{ Actor, ActorRef, ActorSystem, DeadLetter, Deploy, DynamicAccess, Props } | ||
| import akka.dispatch.sysmsg.{ | ||
|
|
@@ -18,13 +20,15 @@ import akka.event.EventStream | |
| import akka.event.Logging.Warning | ||
| import akka.util.Reflect | ||
| import com.typesafe.config.{ Config, ConfigFactory } | ||
|
|
||
| import scala.util.control.NonFatal | ||
| import java.util.concurrent.atomic.AtomicReference | ||
| import scala.annotation.tailrec | ||
| import scala.concurrent.duration.Duration | ||
|
johanandren marked this conversation as resolved.
|
||
|
|
||
| object Mailboxes { | ||
| final val DefaultMailboxId = "akka.actor.default-mailbox" | ||
| final val NoMailboxRequirement = "" | ||
| final val BoundedCapacityPrefix = "bounded-capacity:" | ||
| } | ||
|
|
||
| private[akka] class Mailboxes( | ||
|
|
@@ -202,8 +206,14 @@ private[akka] class Mailboxes( | |
| // It doesn't matter if we create a mailbox type configurator that isn't used due to concurrent lookup. | ||
| val newConfigurator = id match { | ||
| // TODO RK remove these two for Akka 2.3 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Woops, that TODO slipped it seems... |
||
| case "unbounded" => UnboundedMailbox() | ||
| case "bounded" => new BoundedMailbox(settings, config(id)) | ||
| case "unbounded" => UnboundedMailbox() | ||
| case "bounded" => new BoundedMailbox(settings, config(id)) | ||
| case _ if id.startsWith(BoundedCapacityPrefix) => | ||
| // hack to allow programmatic set of capacity through props in akka-typed but still share | ||
| // mailbox configurators for the same size | ||
| val capacity = id.split(':')(1).toInt | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it's ok for us if that fails with a cryptic exception in case we or someone else doesn't follow the format.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it's not public API in any way |
||
| new BoundedMailbox(capacity, Duration.Zero) | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not exactly my proudest moment encoding this in strings but allowed for minimal changes in untyped + caching of the configurator.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The caching argument is good. I think it's ok. |
||
| case _ => | ||
| if (!settings.config.hasPath(id)) throw new ConfigurationException(s"Mailbox Type [$id] not configured") | ||
| val conf = config(id) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it need parentheses for the Scala version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, maybe not, on the fence, on one hand it is a constant/no side effect on the other hand maybe that fact is an implementation detail. This is aligned with the dispatcher selector though.