Skip to content

Instantly share code, notes, and snippets.

@WojciechMazur
Created September 20, 2024 08:48
Pekko 3.3.4 reproducer + fix
object MyEventSourcedBehaviorLoggingSpec {
object ChattyEventSourcingBehavior {
def apply() = Behaviors.setup{ (ctx: ActorContext[Any]) =>
EventSourcedBehavior.create()
}
}
def main(args: Array[String]): Unit = {
val behaviour =
ChattyEventSourcingBehavior().asInstanceOf[EventSourcedBehavior]
println(behaviour.loggerClass)
assert(
behaviour.loggerClass == ChattyEventSourcingBehavior.getClass()
)
}
}
class Behavior[T]
trait ActorContext[T]
object Behaviors {
def setup[T](factory: ActorContext[T] => Behavior[T]): Behavior[T] = factory(
new ActorContext[T] {}
)
}
class EventSourcedBehavior(val loggerClass: Class[_]) extends Behavior[Any]
object EventSourcedBehavior {
private val logPrefixSkipList = classOf[EventSourcedBehavior].getName :: Nil
println(s"skip=${logPrefixSkipList}")
def create() = {
val loggerClass = LoggerClass.detectLoggerClassFromStack(
classOf[EventSourcedBehavior],
logPrefixSkipList
)
new EventSourcedBehavior(loggerClass)
}
}
sealed trait Command
trait Event
object LoggerClass {
private final class TrickySecurityManager extends SecurityManager {
def getClassStack: Array[Class[_]] = getClassContext
}
private val defaultPrefixesToSkip = List("scala.runtime", "LoggerClass")
def detectLoggerClassFromStack(
default: Class[_],
additionalPrefixesToSkip: List[String] = Nil
): Class[_] = {
try {
def skip(name: String): Boolean = {
def loop(skipList: List[String]): Boolean = skipList match {
case Nil => false
case head :: tail =>
if (name.startsWith(head)) true
else loop(tail)
}
loop(additionalPrefixesToSkip ::: defaultPrefixesToSkip)
}
val trace = new TrickySecurityManager().getClassStack
trace.map(_.getName()).foreach(println)
var suitableClass: Option[Class[_]] = None
var idx = 1 // skip this method/class and right away
while (suitableClass.isEmpty && idx < trace.length) {
val clazz = trace(idx)
val name = clazz.getName
if (!skip(name)) suitableClass = Some(clazz)
idx += 1
}
suitableClass match {
case Some(cls) =>
// Fix start
val lambdaClsOwner = for {
nextCaller <- trace.lift(idx)
nextName = nextCaller.getName()
lambdaNameIdx = nextName.indexOf("$$Lambda$")
if nextName.startsWith(cls.getName()) && lambdaNameIdx > 0
lambdaClsOwner = nextName.substring(0, lambdaNameIdx)
} yield Class.forName(lambdaClsOwner)
// Todo: can potentially guard for ClassNotFoundException, but seems unlikely
lambdaClsOwner.getOrElse(cls)
case _ => default
}
} catch {
case scala.util.control.NonFatal(_) => default
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment