Archives Scala针对closure的生成类在序列化过程中的问题
Post
Cancel

Scala针对closure的生成类在序列化过程中的问题

这个头大的问题纠缠了我一天。不明原因,Akka不能在remote端创建actor。

开始以为是我的模型太复杂,折腾了一下来个最简化(参见这个commit),结果是可以正常工作的。于是使用替换大法,一个类一个类地换,最后定位到某个具体的类,发现是一个第三方库的序列化没做好,导致Akka的Props里的creator不能传到remote端。这个好办,修改了一下这个库的代码,但是问题并没有解决。这回十分诡异了,因为我很确定creator里面的所有东西都是可序列化的了。

无计可施之时,scalap了一下这个creator的生成类:

1
2
3
4
5
6
7
8
9
final class SupervisorActor$$anonfun$...$actor$flow$SupervisorActor$$buildActors0$1$2 extends scala.runtime.AbstractFunction0 with scala.Serializable {
  final val config$1: scala.collection.immutable.Map;
  final val clazz$1: java.lang.String;
  final val curr$1: ....ActorflowVertex;
  final val $outer: ....SupervisorActor;
  def this(....SupervisorActor, ....ActorflowVertex, java.lang.String, scala.collection.immutable.Map): scala.Unit;
  def apply(): scala.Any;
  def apply(): ....ActionActor;
}

注意看这个$outer,它就是这个生成类的外部类,但是这个外部类本身并不能序列化。这就是问题的根源。但是为什么会这样呢?先看看这个生成类的原始状态:

1
2
3
context.actorOf(Props(new
    StartActor(ActorflowGraph(graph.id, graph.g, curr), params)).withRouter(FromConfig),
  curr.routerName(self.path.name.style, graph.id))

这里的graph是外部类的一个成员变量。因为creator里面的closure引用了该成员变量,所以scalac把整个外部类都给打包了进去,也就是上面的$outer。

问题已经很清楚了,但是怎么改?很简单,用局部变量:

1
2
3
4
5
6
val id = graph.id
val g = graph.g
 
context.actorOf(Props(new
    StartActor(ActorflowGraph(id, g, curr), params)).withRouter(FromConfig),
  curr.routerName(self.path.name.style, graph.id))

再来看看生成类:

1
2
3
4
5
6
7
8
9
final class SupervisorActor$$anonfun$...$actor$flow$SupervisorActor$$buildActors0$1$2 extends scala.runtime.AbstractFunction0 with scala.Serializable {
  final val g$1: edu.uci.ics.jung.graph.Graph;
  final val id$1: java.lang.String;
  final val params$1: scala.collection.Seq;
  final val curr$1: ....ActorflowVertex;
  def this(...SupervisorActor, ....ActorflowVertex, scala.collection.Seq, java.lang.String, edu.uci.ics.jung.graph.Graph): scala.Unit;
  def apply(): scala.Any;
  def apply(): ....StartActor;
}

$outer没了,取而代之的是两个局部变量的拷贝g$1和id$1,这两个对象都是可序列化的。至此,问题解决。

这个诡异的问题折腾了我一整天!结论就是,closure是个好东西,但是由于scala最终都要编译成java的字节码,对于closure里面自由变量的处理有些时候还是要理解的。

This post is licensed under CC BY 4.0 by the author.