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

He-Pin pushed a commit to branch 1.7.x
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/1.7.x by this push:
     new f0bdd61795 [1.7.x] fix: detect logger class with StackWalker on JDK 9+ 
(#3614)
f0bdd61795 is described below

commit f0bdd617951c78735951b95f77424e997465e4e7
Author: PJ Fanning <[email protected]>
AuthorDate: Sun Oct 11 06:17:11 2026 +0100

    [1.7.x] fix: detect logger class with StackWalker on JDK 9+ (#3614)
    
    * fix: detect logger class with StackWalker on JDK 9+ in 1.7.x
    
    Motivation:
    On JDK 25 with Scala 3, ctx.log picks the enclosing outer object as the
    logger class instead of the lambda owner. SecurityManager.getClassContext
    does not report the hidden lambda frames there, so the Scala 3 lambda
    owner detection in LoggerClass never matches. The JDK 25 / Scala 3.3.x
    job of the 1.7 nightly fails in EventSourcedBehaviorLoggingSpec.
    
    Modification:
    Read the class stack with java.lang.StackWalker (RETAIN_CLASS_REFERENCE,
    SHOW_HIDDEN_FRAMES) when available, through reflection since 1.7.x is
    compiled for Java 8. Fall back to the SecurityManager approach on Java 8.
    
    Result:
    Logger class detection works on JDK 25 with Scala 3, matching main which
    already uses StackWalker.
    
    Tests:
    - JDK 25 + Scala 3.3.7: EventSourcedBehaviorLogging* specs fail before (5 
failures), pass after; ActorLoggingSpec passes
    - JDK 8 + Scala 2.13: EventSourcedBehaviorLogging* specs and 
ActorLoggingSpec pass
    - actor-typed/mimaReportBinaryIssues: no issues
    
    References:
    None - fixes the JDK 25 / Scala 3.3.x failure in the Nightly Builds (1.7) 
workflow
    
    * fix: avoid Scala 2.12 missing interpolator lint in LoggerClass
    
    * chore: use OptionVal instead of Option in LoggerClass
---
 .../pekko/actor/typed/internal/LoggerClass.scala   | 38 ++++++++++++++++++++--
 1 file changed, 35 insertions(+), 3 deletions(-)

diff --git 
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/LoggerClass.scala
 
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/LoggerClass.scala
index b73d46b43f..4e5c8849bc 100644
--- 
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/LoggerClass.scala
+++ 
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/LoggerClass.scala
@@ -25,18 +25,50 @@ import pekko.util.OptionVal
 @InternalApi
 private[pekko] object LoggerClass {
 
-  // just to get access to the class context
+  // just to get access to the class context, only used on Java 8 where 
StackWalker is not available
   private final class TrickySecurityManager extends SecurityManager {
     def getClassStack: Array[Class[_]] = getClassContext
   }
 
+  // java.lang.StackWalker (Java 9+) is accessed through reflection since this 
module is compiled for Java 8.
+  // SHOW_HIDDEN_FRAMES is needed to include the lambda classes, which 
SecurityManager.getClassContext
+  // does not report on JDK 24+ (required by the lambda owner detection below 
for Scala 3)
+  private val stackWalkerClassStack: OptionVal[() => Array[Class[_]]] =
+    try {
+      val walkerClass = Class.forName("java.lang.StackWalker")
+      // nested class names are built with '$' + name to avoid Scala 2.12's 
missing interpolator lint
+      val optionClass = Class.forName(walkerClass.getName + '$' + "Option")
+      val getDeclaringClass = Class.forName(walkerClass.getName + '$' + 
"StackFrame").getMethod("getDeclaringClass")
+      val options = new java.util.HashSet[AnyRef]()
+      options.add(optionClass.getField("RETAIN_CLASS_REFERENCE").get(null))
+      options.add(optionClass.getField("SHOW_HIDDEN_FRAMES").get(null))
+      val walker = walkerClass.getMethod("getInstance", 
classOf[java.util.Set[_]]).invoke(null, options)
+      val walk = walkerClass.getMethod("walk", 
classOf[java.util.function.Function[_, _]])
+      val toClasses = new 
java.util.function.Function[java.util.stream.Stream[AnyRef], Array[Class[_]]] {
+        override def apply(frames: java.util.stream.Stream[AnyRef]): 
Array[Class[_]] =
+          frames.toArray.map(frame => 
getDeclaringClass.invoke(frame).asInstanceOf[Class[_]])
+      }
+      OptionVal.Some(() => walk.invoke(walker, 
toClasses).asInstanceOf[Array[Class[_]]])
+    } catch {
+      case NonFatal(_) => OptionVal.None
+    }
+
+  private def getClassStack: Array[Class[_]] = stackWalkerClassStack match {
+    case OptionVal.Some(walk) =>
+      // drop the reflection frames so that the stack starts at this class, 
like getClassContext
+      val trace = walk()
+      val start = trace.indexWhere(_ eq LoggerClass.getClass)
+      if (start > 0) trace.drop(start) else trace
+    case _ =>
+      new TrickySecurityManager().getClassStack
+  }
+
   private val defaultPrefixesToSkip = List("scala.runtime", 
"org.apache.pekko.actor.typed.internal")
 
   /**
    * Try to extract a logger class from the call stack, if not possible the 
provided default is used
    */
   def detectLoggerClassFromStack(default: Class[_], additionalPrefixesToSkip: 
List[String] = Nil): Class[_] = {
-    // TODO use stack walker API when we no longer need to support Java 8
     try {
       def skip(name: String): Boolean = {
         def loop(skipList: List[String]): Boolean = skipList match {
@@ -49,7 +81,7 @@ private[pekko] object LoggerClass {
         loop(additionalPrefixesToSkip ::: defaultPrefixesToSkip)
       }
 
-      val trace = new TrickySecurityManager().getClassStack
+      val trace = getClassStack
       var suitableClass: OptionVal[Class[_]] = OptionVal.None
       var idx = 1 // skip this method/class and right away
       while (suitableClass.isEmpty && idx < trace.length) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to