【问题标题】:Scala match on Option[Object] not exhaustive with Some(o) and None in Akka receive functionOption[Object] 上的 Scala 匹配在 Akka 接收函数中使用 Some(o) 和 None 并不详尽
【发布时间】:2014-03-08 08:53:52
【问题描述】:

对于下面 Option[Entity] 的匹配,第三种情况需要是详尽的。为什么?

entitiesMap 是一个 var,其中包含一个不可变的 Map[UUID,Entity]。它在单个 Akka Actor 中进行访问和更新,以避免线程问题。下面是 receive 函数的摘录:

class WorldActor extends Actor {

  var world: World = null

  override def receive = {

    case WorldSet(w) =>
      world = w

    // get that state and apply it to its entity
    case s: State if (world != null) =>
      val uuid = new UUID(s.getUuid().getL1(), s.getUuid().getL2())
      world.entitiesMap.get(uuid) match {
        case Some(ent) =>
          // Update entity's state
          ent.setEntityState(s)
        case None =>
          Log.error("World doesn't contain entity uuid: " + uuid)
        case other =>
          Log.error("Received unknown message: " + other)
      }

    case AddEntity(ent) if (world != null && ent != null) =>
      if (!world.entitiesMap.contains(ent.uuid))
        world.entitiesMap += ent.uuid -> ent

    case RemoveEntity(ent) if (world != null && ent != null) =>
      if (world.entitiesMap.contains(ent.uuid))
        world.entitiesMap -= ent.uuid

    case other => // ignore
  }

}

class World {
  // Entity container
  var entitiesMap = Map[UUID,Entity]()
}

上面的代码时不时会报告:

Received unknown message: None

为什么上面的 case None 模式没有捕捉到它?

编辑

我发现当消息 State 出现在消息 AddEntity 之前,即 entitiesMap 还没有包含 实体由消息状态引用。

01:52 ERROR: [State] Received unknown message: None
uuid: 1b234d30-92ae-11e3-aa12-7071bcb09717
Thread: 152

01:52 ERROR: [State] Received unknown message: None
uuid: 1b234d30-92ae-11e3-aa12-7071bcb09717
Thread: 32

01:52  INFO: [AddEntity] 1b234d30-92ae-11e3-aa12-7071bcb09717: Cube@2f9c3beb
Thread: 152

这毕竟是线程问题吗?在 Actor 内?

编辑 2

按照下面海报的建议,我记录了 otherNone 的类名、类加载器引用和类文件路径。它们都是一样的。

编辑 3

other == Nonefalse

other.eq(None)false

other.equals(None)false

other.hashCode == None.hashCode

System.identityHashCode(other) != System.identityHashCode(None)

【问题讨论】:

  • 您确定您的“其他”案例没有打印相同的信息吗? ☺
  • 您是否收到编译器警告,例如无法访问的代码?
  • 如果我删除第一个“case other”,有时程序会崩溃,说它无法匹配 scala.None。
  • 另外,请提供world的定义。另一件事要尝试:记录other.getClass.getNameNone.getClass.getName。如果他们都是scala.None$,那就太奇怪了。
  • 有趣。是否可以提供包含修改地图的逻辑的完整代码?

标签: multithreading scala jvm akka actor


【解决方案1】:

这听起来像是类加载器/类路径问题,其中None$ 被两个类加载器加载,并且两个 None$.Module$ 比较不相等。

由于 Map 是不可变的,添加到它之后,您就创建了一个新 Map。假设new Map的类被外星加载器加载;那么当该地图返回 None 时,它​​将返回外星人 None。

请注意,当您添加到非常小的地图时,它会使用 new 创建下一个更大的地图;但经过几次这样的分配,它切换到 HashMap。因此,竞争的类加载器可能会改变您返回的地图的明显行为。

我不仅会打印出类名,还会打印出类加载器以及加载类的位置。

【讨论】:

  • 我非常有信心这会解决我的问题,但我刚刚确认不仅类名相同:类加载器和加载类的位置也是如此。对于 otherscala.None.
  • 我刚刚尝试了 other == None 并且它返回 false,所以你有一些东西,但我无法指出它正确。
【解决方案2】:

根据每个人的建议,告诉我这个问题是类加载器问题和printing stack trace at None's construction time,我已将其范围缩小到我使用 Kryo 在通过网络传输消息之前对消息进行序列化。

这似乎是 Kryo 使用singleton objects can lead to my problem exactly 的默认行为。 Kryo 每次反序列化时都会重新加载 None,导致 NoneSystem.identityHashCode 不同。

那么问题出在class reloading,解决方案是使用一个知道如何正确处理Scala 对象以进行序列化的库,例如chill-scala

package com.twitter.chill

// Singletons are easy, you just return the singleton and don't read:
// It's important you actually do this, or Kryo will generate Nil != Nil, or None != None

class SingletonSerializer[T](obj: T) extends KSerializer[T] {
  def write(kser: Kryo, out: Output, obj: T) {}
  def read(kser: Kryo, in: Input, cls: Class[T]): T = obj
}

【讨论】:

  • 我不记得建议在链接线程上重新加载类,但现在看,这让我怀疑在多个论坛上拆分线程是否是最佳选择。如果它在不丢失线程的情况下获得更多意见,那可能是最佳选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-30
  • 2017-10-18
  • 1970-01-01
  • 1970-01-01
  • 2021-06-28
  • 2016-03-07
  • 1970-01-01
相关资源
最近更新 更多