Scala反射:使用protobuf编译akka actor

发布于 2025-01-11 04:35:35 字数 1225 浏览 4 评论 0原文

我正在尝试使用 Scala Reflection Toolbox 编译带有 DynamicMessage 的 Actor

Actor 代码看起来像是

import scala.reflect.runtime.universe
import scala.tools.reflect.ToolBox
val toolbox = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
    val actorCode =
  """
    |import akka.actor._
    |import com.google.protobuf._
    |class SimpleActor extends Actor {
    |override def receive: Receive = {
    | case dynamicMessage: DynamicMessage => println("Dynamic message received!")
    | case _  => println("Whatever!")  // the default, catch-all
    |  }
    |}
    |object SimpleActor {
    |def props() : Props = Props(new SimpleActor())
    |}
    |
    |
    |return SimpleActor.props()
    |""".stripMargin
val tree = toolbox.parse(actorCode)
toolbox.compile(tree)().asInstanceOf[Props]

出现错误

reflective compilation has failed:

illegal cyclic reference involving type T
scala.tools.reflect.ToolBoxError: reflective compilation has failed:

illegal cyclic reference involving type T

如果我在工具箱之外运行代码,它会编译并正常工作。 该错误是从该行给出的

case dynamicMessage: DynamicMessage => println("Dynamic message received!")

任何人都知道该错误的性质以及如何修复它?

I am trying to compile an Actor with DynamicMessage with Scala Reflection Toolbox

The actor code looks like

import scala.reflect.runtime.universe
import scala.tools.reflect.ToolBox
val toolbox = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
    val actorCode =
  """
    |import akka.actor._
    |import com.google.protobuf._
    |class SimpleActor extends Actor {
    |override def receive: Receive = {
    | case dynamicMessage: DynamicMessage => println("Dynamic message received!")
    | case _  => println("Whatever!")  // the default, catch-all
    |  }
    |}
    |object SimpleActor {
    |def props() : Props = Props(new SimpleActor())
    |}
    |
    |
    |return SimpleActor.props()
    |""".stripMargin
val tree = toolbox.parse(actorCode)
toolbox.compile(tree)().asInstanceOf[Props]

I get the error

reflective compilation has failed:

illegal cyclic reference involving type T
scala.tools.reflect.ToolBoxError: reflective compilation has failed:

illegal cyclic reference involving type T

If I run the code outside of the Toolbox it compiles and works fine.
The error is given from the line

case dynamicMessage: DynamicMessage => println("Dynamic message received!")

Anyone knows the nature of this error and how to fix it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

停滞 2025-01-18 04:35:35

在 Scala 中,即使没有反射编译,也会有 bug 与 scala-java 互操作和 F 边界多态性相结合:

scalac 报告有效 Java 类的错误:涉及类型 T 和其他类型的非法循环引用

com.google.protobuf.DynamicMessage 的父母探索 F- bounds

DynamicMessage 
  <: AbstractMessage
       <: AbstractMessageLite[_,_] (such inheritance is allowed in Java but not in Scala) 
            [M <: AbstractMessageLite[M, B],
             B <: AbstractMessageLite.Builder[M, B]]
                                        [M <: AbstractMessageLite[M, B],
                                         B <: AbstractMessageLite.Builder[M, B]]
                                                                    <: MessageLite.Builder
                                                                                     <: MessageLiteOrBuilder
                                                                                     <: Cloneable
            <: MessageLite
                 <: MessageLiteOrBuilder
       <: Message
            <: MessageLite...
            <: MessageOrBuilder
                 <: MessageLiteOrBuilder

但是如果没有反射编译,您的代码就会编译。所以这是反射编译、scala-java 互操作和 F 边界多态性结合在一起的错误。

解决方法是使用真正的编译器而不是工具箱:

import akka.actor.{ActorSystem, Props}
// libraryDependencies += "com.github.os72" % "protobuf-dynamic" % "1.0.1"
import com.github.os72.protobuf.dynamic.{DynamicSchema, MessageDefinition}
import com.google.protobuf.DynamicMessage
import scala.reflect.internal.util.{AbstractFileClassLoader, BatchSourceFile}
import scala.reflect.io.{AbstractFile, VirtualDirectory}
import scala.reflect.runtime
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.nsc.{Global, Settings}

val actorCode = """
  |import akka.actor._
  |import com.google.protobuf._
  |
  |class SimpleActor extends Actor {
  |  override def receive: Receive = {
  |    case dynamicMessage: DynamicMessage => println("Dynamic message received!")
  |    case _  => println("Whatever!")  // the default, catch-all
  |  }
  |}
  |
  |object SimpleActor {
  |    def props() : Props = Props(new SimpleActor())
  |}
  |""".stripMargin

val directory = new VirtualDirectory("(memory)", None)
val runtimeMirror = createRuntimeMirror(directory, runtime.currentMirror)
compileCode(actorCode, List(), directory)
val props = runObjectMethod("SimpleActor", runtimeMirror, "props")
  .asInstanceOf[Props]

val actorSystem = ActorSystem("actorSystem")
val actor = actorSystem.actorOf(props, "helloActor")

val msg = makeDynamicMessage()

actor ! "hello" // Whatever!
actor ! msg // Dynamic message received!

actorSystem.terminate()

//see (*)
def makeDynamicMessage(): DynamicMessage = {
  val schemaBuilder = DynamicSchema.newBuilder
  schemaBuilder.setName("PersonSchemaDynamic.proto")

  val msgDef = MessageDefinition.newBuilder("Person")
    .addField("required", "int32", "id", 1)
    .build

  schemaBuilder.addMessageDefinition(msgDef)
  val schema = schemaBuilder.build

  val msgBuilder = schema.newMessageBuilder("Person")
  val msgDesc = msgBuilder.getDescriptorForType
  msgBuilder
    .setField(msgDesc.findFieldByName("id"), 1)
    .build
}

def compileCode(
  code: String, 
  classpathDirectories: List[AbstractFile], 
  outputDirectory: AbstractFile
): Unit = {
  val settings = new Settings
  classpathDirectories.foreach(dir => settings.classpath.prepend(dir.toString))
  settings.outputDirs.setSingleOutput(outputDirectory)
  settings.usejavacp.value = true
  val global = new Global(settings)
  (new global.Run).compileSources(List(new BatchSourceFile("(inline)", code)))
}

def runObjectMethod(
  objectName: String,
  runtimeMirror: Mirror, 
  methodName: String, 
  arguments: Any*
): Any = {
  val objectSymbol         = runtimeMirror.staticModule(objectName)
  val objectModuleMirror   = runtimeMirror.reflectModule(objectSymbol)
  val objectInstance       = objectModuleMirror.instance
  val objectType           = objectSymbol.typeSignature
  val methodSymbol         = objectType.decl(TermName(methodName)).asMethod
  val objectInstanceMirror = runtimeMirror.reflect(objectInstance)
  val methodMirror         = objectInstanceMirror.reflectMethod(methodSymbol)
  methodMirror(arguments: _*)
}

def createRuntimeMirror(directory: AbstractFile, parentMirror: Mirror): Mirror = {
  val classLoader = new AbstractFileClassLoader(directory, parentMirror.classLoader)
  universe.runtimeMirror(classLoader)
}

Scala 反射中的 Tensorflow (这里有一个类似的反射编译、Scala-Java 互操作和路径相关类型组合出现错误的情况)

运行时动态编译多个 Scala 类

如何评估代码使用 InterfaceStability 注释(因“涉及类 InterfaceStability 的非法循环引用”而失败)?(反射编译期间也是“非法循环引用”)

Scala 演示编译器 - 最小示例

(*) 运行时生成的协议缓冲区对象

In Scala even without reflective compilation there are bugs in combination of scala-java interop and F-bounded polymorphism:

scalac reports error on valid Java class: illegal cyclic reference involving type T

and others.

And parents of com.google.protobuf.DynamicMessage explore F-bounds:

DynamicMessage 
  <: AbstractMessage
       <: AbstractMessageLite[_,_] (such inheritance is allowed in Java but not in Scala) 
            [M <: AbstractMessageLite[M, B],
             B <: AbstractMessageLite.Builder[M, B]]
                                        [M <: AbstractMessageLite[M, B],
                                         B <: AbstractMessageLite.Builder[M, B]]
                                                                    <: MessageLite.Builder
                                                                                     <: MessageLiteOrBuilder
                                                                                     <: Cloneable
            <: MessageLite
                 <: MessageLiteOrBuilder
       <: Message
            <: MessageLite...
            <: MessageOrBuilder
                 <: MessageLiteOrBuilder

But without reflective compilation your code compiles. So this is a bug in combination of reflective compilation, scala-java interop and F-bounded polymorphism.

A workaround is to use real compiler instead of toolbox:

import akka.actor.{ActorSystem, Props}
// libraryDependencies += "com.github.os72" % "protobuf-dynamic" % "1.0.1"
import com.github.os72.protobuf.dynamic.{DynamicSchema, MessageDefinition}
import com.google.protobuf.DynamicMessage
import scala.reflect.internal.util.{AbstractFileClassLoader, BatchSourceFile}
import scala.reflect.io.{AbstractFile, VirtualDirectory}
import scala.reflect.runtime
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.nsc.{Global, Settings}

val actorCode = """
  |import akka.actor._
  |import com.google.protobuf._
  |
  |class SimpleActor extends Actor {
  |  override def receive: Receive = {
  |    case dynamicMessage: DynamicMessage => println("Dynamic message received!")
  |    case _  => println("Whatever!")  // the default, catch-all
  |  }
  |}
  |
  |object SimpleActor {
  |    def props() : Props = Props(new SimpleActor())
  |}
  |""".stripMargin

val directory = new VirtualDirectory("(memory)", None)
val runtimeMirror = createRuntimeMirror(directory, runtime.currentMirror)
compileCode(actorCode, List(), directory)
val props = runObjectMethod("SimpleActor", runtimeMirror, "props")
  .asInstanceOf[Props]

val actorSystem = ActorSystem("actorSystem")
val actor = actorSystem.actorOf(props, "helloActor")

val msg = makeDynamicMessage()

actor ! "hello" // Whatever!
actor ! msg // Dynamic message received!

actorSystem.terminate()

//see (*)
def makeDynamicMessage(): DynamicMessage = {
  val schemaBuilder = DynamicSchema.newBuilder
  schemaBuilder.setName("PersonSchemaDynamic.proto")

  val msgDef = MessageDefinition.newBuilder("Person")
    .addField("required", "int32", "id", 1)
    .build

  schemaBuilder.addMessageDefinition(msgDef)
  val schema = schemaBuilder.build

  val msgBuilder = schema.newMessageBuilder("Person")
  val msgDesc = msgBuilder.getDescriptorForType
  msgBuilder
    .setField(msgDesc.findFieldByName("id"), 1)
    .build
}

def compileCode(
  code: String, 
  classpathDirectories: List[AbstractFile], 
  outputDirectory: AbstractFile
): Unit = {
  val settings = new Settings
  classpathDirectories.foreach(dir => settings.classpath.prepend(dir.toString))
  settings.outputDirs.setSingleOutput(outputDirectory)
  settings.usejavacp.value = true
  val global = new Global(settings)
  (new global.Run).compileSources(List(new BatchSourceFile("(inline)", code)))
}

def runObjectMethod(
  objectName: String,
  runtimeMirror: Mirror, 
  methodName: String, 
  arguments: Any*
): Any = {
  val objectSymbol         = runtimeMirror.staticModule(objectName)
  val objectModuleMirror   = runtimeMirror.reflectModule(objectSymbol)
  val objectInstance       = objectModuleMirror.instance
  val objectType           = objectSymbol.typeSignature
  val methodSymbol         = objectType.decl(TermName(methodName)).asMethod
  val objectInstanceMirror = runtimeMirror.reflect(objectInstance)
  val methodMirror         = objectInstanceMirror.reflectMethod(methodSymbol)
  methodMirror(arguments: _*)
}

def createRuntimeMirror(directory: AbstractFile, parentMirror: Mirror): Mirror = {
  val classLoader = new AbstractFileClassLoader(directory, parentMirror.classLoader)
  universe.runtimeMirror(classLoader)
}

Tensorflow in Scala reflection (here was a similar situation with a bug in combination of reflective compilation, Scala-Java interop, and path-dependent types)

Dynamic compilation of multiple Scala classes at runtime

How to eval code that uses InterfaceStability annotation (that fails with "illegal cyclic reference involving class InterfaceStability")? (also "illegal cyclic reference" during reflective compilation)

Scala Presentation Compiler - Minimal Example

(*) Protocol buffer objects generated at runtime

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文