我正在使用rxscala并发现了一个非常微妙的问题,我的代码简化为以下内容:
import rx.lang.scala.Observable
import rx.lang.scala.subjects.PublishSubject
object SubtleBug extends App {
case class Projects(projects: List[Project] = Nil)
case class Project(name: String, docs: List[Doc] = Nil)
case class Doc(path: String, baseContent: String)
sealed trait ServerEvent
case class ProjectNames(projects: Seq[String]) extends ServerEvent
case class NewDocument(projectName: String, path: String, version: Int, content: String) extends ServerEvent
val receivedEvents = PublishSubject[ServerEvent]
new Thread(new Runnable {
override def run(): Unit = {
val events = Seq(
new ProjectNames(Seq("p1", "p2")),
NewDocument("p1", "/aaa", 1, "my-content"),
NewDocument("p1", "/bbb", 1, "my-content")
)
events.foreach { event =>
receivedEvents.onNext(event)
Thread.sleep(200)
}
}
}).start()
lazy val projects: Observable[Option[Projects]] = receivedEvents.scan(Option.empty[Projects]) {
case (_, ProjectNames(names)) => {
Some(Projects(names.map(name => Project(name)).toList))
}
case (Some(ps), NewDocument(projectName, docPath, version, content)) => {
val doc = Doc(docPath, content)
val newPs = ps.copy(projects = ps.projects.map {
case project if project.name == projectName => project.copy(docs = project.docs ::: List(doc))
case p => p
})
Some(newPs)
}
case _ => None
}.collect({
case Some(p) => Some(p)
})
projects.foreach(ps => {
println("### 111: " + ps.map(_.projects))
projects.foreach(x => println("### 222: " + ps.map(_.projects))) // !!!(2)
})
Thread.sleep(2000)
}
关键点是!!!(2)
行,它在projects
本身内。
它打印以下内容:
### 111: Some(List(Project(p1,List()), Project(p2,List())))
### 111: Some(List(Project(p1,List(Doc(/aaa,my-content))), Project(p2,List())))
### 111: Some(List(Project(p1,List(Doc(/aaa,my-content), Doc(/bbb,my-content))), Project(p2,List())))
问题是没有### 222:
行!
但是,如果我更改collect
部分,并添加None
案例:
.collect({
case Some(p) => Some(p)
case None => None // added case
})
它会按照我的预期打印### 222:
行:
### 111: None
### 111: Some(List(Project(p1,List()), Project(p2,List())))
### 111: Some(List(Project(p1,List(Doc(/aaa,my-content))), Project(p2,List())))
### 222: None
### 222: None
### 222: Some(List(Project(p1,List()), Project(p2,List())))
### 222: Some(List(Project(p1,List()), Project(p2,List())))
### 111: Some(List(Project(p1,List(Doc(/aaa,my-content), Doc(/bbb,my-content))), Project(p2,List())))
### 222: None
### 222: Some(List(Project(p1,List()), Project(p2,List())))
### 222: Some(List(Project(p1,List(Doc(/aaa,my-content))), Project(p2,List())))
### 222: Some(List(Project(p1,List(Doc(/aaa,my-content))), Project(p2,List())))
我无法理解为什么。
PS:您可以在此处克隆代码:https://github.com/freewind/rxscala-test/blob/master/src/main/scala/myrx/SubtleBug.scala
答案 0 :(得分:0)
您的代码中存在竞争条件。 PublishSubject
会在您订阅之前删除任何事件。因此,如果receivedEvents.onNext(event)
在projects.foreach
之前运行,则会删除事件。在scan
中,如果第一个事件被删除,模式匹配将不再有效。
您可以使用ReplaySubject
进行修复。