You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pekko.apache.org by fa...@apache.org on 2026/03/18 09:03:15 UTC

(pekko) branch main updated: Port akka-core optimizations for typed actor context and system adapter (#2750)

This is an automated email from the ASF dual-hosted git repository.

fanningpj pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/main by this push:
     new e29c133a47 Port akka-core optimizations for typed actor context and system adapter (#2750)
e29c133a47 is described below

commit e29c133a473eb0b25e86c9ca8546e27749c65a0c
Author: PJ Fanning <pj...@users.noreply.github.com>
AuthorDate: Wed Mar 18 10:03:10 2026 +0100

    Port akka-core optimizations for typed actor context and system adapter (#2750)
    
    * Initial plan
    
    * Port akka-core PRs 31843 and 31842: optimize typed actor context and system adapter
    
    Co-authored-by: pjfanning <11...@users.noreply.github.com>
    
    * scalafmt
    
    ---------
    
    Co-authored-by: copilot-swe-agent[bot] <19...@users.noreply.github.com>
    Co-authored-by: pjfanning <11...@users.noreply.github.com>
---
 .../pekko/actor/typed/MailboxSelectorSpec.scala    |  2 +-
 .../typed/internal/adapter/ActorAdapter.scala      |  2 +-
 .../internal/adapter/ActorContextAdapter.scala     | 46 +++++++++++-----------
 .../internal/adapter/ActorSystemAdapter.scala      | 17 +++++++-
 .../scala/org/apache/pekko/actor/ActorSystem.scala |  7 ++++
 5 files changed, 47 insertions(+), 27 deletions(-)

diff --git a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/MailboxSelectorSpec.scala b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/MailboxSelectorSpec.scala
index 36a029a634..0b7bfbb04f 100644
--- a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/MailboxSelectorSpec.scala
+++ b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/MailboxSelectorSpec.scala
@@ -45,7 +45,7 @@ class MailboxSelectorSpec extends ScalaTestWithActorTestKit("""
         case WhatsYourMailbox(replyTo) =>
           val mailbox = context match {
             case adapter: ActorContextAdapter[_] =>
-              adapter.classicContext match {
+              adapter.classicActorContext match {
                 case cell: ActorCell =>
                   cell.mailbox.messageQueue
                 case unexpected => throw new RuntimeException(s"Unexpected: $unexpected")
diff --git a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorAdapter.scala b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorAdapter.scala
index da1604cd3b..872cfc4298 100644
--- a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorAdapter.scala
+++ b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorAdapter.scala
@@ -72,7 +72,7 @@ import pekko.util.OptionVal
   // when the adapter is used for the user guardian (which avoids touching context until it is safe)
   private var _ctx: ActorContextAdapter[T] = _
   def ctx: ActorContextAdapter[T] = {
-    if (_ctx eq null) _ctx = new ActorContextAdapter[T](context, this)
+    if (_ctx eq null) _ctx = new ActorContextAdapter[T](this)
     _ctx
   }
 
diff --git a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorContextAdapter.scala b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorContextAdapter.scala
index 018d5c1e72..95be6cd5de 100644
--- a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorContextAdapter.scala
+++ b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorContextAdapter.scala
@@ -28,7 +28,7 @@ private[pekko] object ActorContextAdapter {
 
   private def toClassicImp[U](context: TypedActorContext[_]): classic.ActorContext =
     context match {
-      case adapter: ActorContextAdapter[_] => adapter.classicContext
+      case adapter: ActorContextAdapter[_] => adapter.classicActorContext
       case _                               =>
         throw new UnsupportedOperationException(
           "Only adapted classic ActorContext permissible " +
@@ -45,9 +45,7 @@ private[pekko] object ActorContextAdapter {
 /**
  * INTERNAL API. Wrapping an [[pekko.actor.ActorContext]] as an [[TypedActorContext]].
  */
-@InternalApi private[pekko] final class ActorContextAdapter[T](
-    val classicContext: classic.ActorContext,
-    adapter: ActorAdapter[T])
+@InternalApi private[pekko] final class ActorContextAdapter[T](adapter: ActorAdapter[T])
     extends ActorContextImpl[T] {
 
   import ActorRefAdapter.toClassic
@@ -60,28 +58,28 @@ private[pekko] object ActorContextAdapter {
   private var _self: OptionVal[ActorRef[T]] = OptionVal.None
   override def self: ActorRef[T] = {
     if (_self.isEmpty) {
-      _self = OptionVal.Some(ActorRefAdapter(classicContext.self))
+      _self = OptionVal.Some(ActorRefAdapter(classicActorContext.self))
     }
     _self.get
   }
-  final override val system = ActorSystemAdapter(classicContext.system)
-  private[pekko] def classicActorContext = classicContext
+  override def system = ActorSystemAdapter(classicActorContext.system)
+  private[pekko] def classicActorContext = adapter.context
   override def children: Iterable[ActorRef[Nothing]] = {
     checkCurrentActorThread()
-    classicContext.children.map(ActorRefAdapter(_))
+    classicActorContext.children.map(ActorRefAdapter(_))
   }
   override def child(name: String): Option[ActorRef[Nothing]] = {
     checkCurrentActorThread()
-    classicContext.child(name).map(ActorRefAdapter(_))
+    classicActorContext.child(name).map(ActorRefAdapter(_))
   }
   override def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U] = {
     checkCurrentActorThread()
-    ActorRefFactoryAdapter.spawnAnonymous(classicContext, behavior, props, rethrowTypedFailure = true)
+    ActorRefFactoryAdapter.spawnAnonymous(classicActorContext, behavior, props, rethrowTypedFailure = true)
   }
 
   override def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U] = {
     checkCurrentActorThread()
-    ActorRefFactoryAdapter.spawn(classicContext, behavior, name, props, rethrowTypedFailure = true)
+    ActorRefFactoryAdapter.spawn(classicActorContext, behavior, name, props, rethrowTypedFailure = true)
   }
 
   override def stop[U](child: ActorRef[U]): Unit = {
@@ -89,12 +87,12 @@ private[pekko] object ActorContextAdapter {
     if (child.path.parent == self.path) { // only if a direct child
       toClassic(child) match {
         case f: pekko.actor.FunctionRef =>
-          val cell = classicContext.asInstanceOf[pekko.actor.ActorCell]
+          val cell = classicActorContext.asInstanceOf[pekko.actor.ActorCell]
           cell.removeFunctionRef(f)
         case c =>
-          classicContext.child(child.path.name) match {
+          classicActorContext.child(child.path.name) match {
             case Some(`c`) =>
-              classicContext.stop(c)
+              classicActorContext.stop(c)
             case _ =>
             // child that was already stopped
           }
@@ -115,37 +113,37 @@ private[pekko] object ActorContextAdapter {
 
   override def watch[U](other: ActorRef[U]): Unit = {
     checkCurrentActorThread()
-    classicContext.watch(toClassic(other))
+    classicActorContext.watch(toClassic(other))
   }
   override def watchWith[U](other: ActorRef[U], msg: T): Unit = {
     checkCurrentActorThread()
-    classicContext.watchWith(toClassic(other), msg)
+    classicActorContext.watchWith(toClassic(other), msg)
   }
   override def unwatch[U](other: ActorRef[U]): Unit = {
     checkCurrentActorThread()
-    classicContext.unwatch(toClassic(other))
+    classicActorContext.unwatch(toClassic(other))
   }
   var receiveTimeoutMsg: T = null.asInstanceOf[T]
   override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = {
     checkCurrentActorThread()
     receiveTimeoutMsg = msg
-    classicContext.setReceiveTimeout(d)
+    classicActorContext.setReceiveTimeout(d)
   }
   override def cancelReceiveTimeout(): Unit = {
     checkCurrentActorThread()
 
     receiveTimeoutMsg = null.asInstanceOf[T]
-    classicContext.setReceiveTimeout(Duration.Undefined)
+    classicActorContext.setReceiveTimeout(Duration.Undefined)
   }
-  override def executionContext: ExecutionContextExecutor = classicContext.dispatcher
+  override def executionContext: ExecutionContextExecutor = classicActorContext.dispatcher
   override def scheduleOnce[U](delay: FiniteDuration, target: ActorRef[U], msg: U): classic.Cancellable = {
-    import classicContext.dispatcher
-    classicContext.system.scheduler.scheduleOnce(delay, toClassic(target), msg)
+    classicActorContext.system.scheduler.scheduleOnce(delay, toClassic(target), msg)(classicActorContext.dispatcher)
   }
   override private[pekko] def internalSpawnMessageAdapter[U](f: U => T, _name: String): ActorRef[U] = {
-    val cell = classicContext.asInstanceOf[pekko.actor.ActorCell]
+    val cell = classicActorContext.asInstanceOf[pekko.actor.ActorCell]
     // apply the function inside the actor by wrapping the msg and f, handled by ActorAdapter
-    val ref = cell.addFunctionRef((_, msg) => classicContext.self ! AdaptMessage[U, T](msg.asInstanceOf[U], f), _name)
+    val ref =
+      cell.addFunctionRef((_, msg) => classicActorContext.self ! AdaptMessage[U, T](msg.asInstanceOf[U], f), _name)
     ActorRefAdapter[U](ref)
   }
 
diff --git a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorSystemAdapter.scala b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorSystemAdapter.scala
index 2cf322c878..7c9bfb6ca8 100644
--- a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorSystemAdapter.scala
+++ b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/adapter/ActorSystemAdapter.scala
@@ -41,6 +41,7 @@ import pekko.actor.typed.internal.PropsImpl.DispatcherSameAsParent
 import pekko.actor.typed.internal.SystemMessage
 import pekko.actor.typed.scaladsl.Behaviors
 import pekko.annotation.InternalApi
+import pekko.util.OptionVal
 
 import org.slf4j.{ Logger, LoggerFactory }
 
@@ -141,7 +142,21 @@ import org.slf4j.{ Logger, LoggerFactory }
 }
 
 private[pekko] object ActorSystemAdapter {
-  def apply(system: classic.ActorSystem): ActorSystem[Nothing] = AdapterExtension(system).adapter
+  def apply(system: classic.ActorSystem): ActorSystem[Nothing] = {
+    system match {
+      case system: classic.ActorSystemImpl =>
+        // Optimization to cut out going through adapter lookup if possible
+        system.typedSystem match {
+          case OptionVal.Some(typedSystem: ActorSystem[_]) => typedSystem
+          case _                                           =>
+            val typedSystem: ActorSystem[_] = AdapterExtension(system).adapter
+            system.typedSystem = OptionVal.Some(typedSystem)
+            typedSystem
+        }
+      case _ =>
+        AdapterExtension(system).adapter
+    }
+  }
 
   // to make sure we do never create more than one adapter for the same actor system
   class AdapterExtension(system: classic.ExtendedActorSystem) extends classic.Extension {
diff --git a/actor/src/main/scala/org/apache/pekko/actor/ActorSystem.scala b/actor/src/main/scala/org/apache/pekko/actor/ActorSystem.scala
index 3a6cfe2900..95a263241b 100644
--- a/actor/src/main/scala/org/apache/pekko/actor/ActorSystem.scala
+++ b/actor/src/main/scala/org/apache/pekko/actor/ActorSystem.scala
@@ -848,6 +848,13 @@ private[pekko] class ActorSystemImpl(
 
   private val _dynamicAccess: DynamicAccess = createDynamicAccess()
 
+  /**
+   * Optimistic optimization: Tries to avoid going through the extension infrastructure if possible when using
+   * the typed system. Will contain a org.apache.pekko.actor.typed.ActorSystem if set, should not be touched by
+   * anything but the typed ActorSystemAdapter.
+   */
+  private[pekko] var typedSystem: OptionVal[AnyRef] = OptionVal.None
+
   final val settings: Settings = {
     val config = Settings.amendSlf4jConfig(
       applicationConfig.withFallback(ConfigFactory.defaultReference(classLoader)),


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pekko.apache.org
For additional commands, e-mail: commits-help@pekko.apache.org