!str,htc replace and remove OneBoundedInterpreter

main work by @drewhk with contributions from @2m and @rkuhn

This work uncovered many well-hidden bugs in existing Stages, in
particular StatefulStage. These were hidden by the behavior of
OneBoundedInterpreter that normally behaves more orderly than it
guarantees in general, especially with respect to the timeliness of
delivery of upstream termination signals; the bugs were then that
internal state was not flushed when onComplete arrived “too early”.
This commit is contained in:
Roland Kuhn 2015-10-31 14:46:10 +01:00
parent 20f54435f1
commit 556012b7ee
107 changed files with 2456 additions and 3061 deletions

View file

@ -3,16 +3,18 @@
*/
package akka.stream.impl.fusing
import akka.stream.stage._
import akka.stream.testkit.AkkaSpec
import akka.stream.testkit.Utils.TE
import scala.concurrent.duration._
class LifecycleInterpreterSpec extends InterpreterSpecKit {
class LifecycleInterpreterSpec extends GraphInterpreterSpecKit {
import akka.stream.Supervision._
"Interpreter" must {
"call preStart in order on stages" in new TestSetup(Seq(
"call preStart in order on stages" in new OneBoundedSetup[String](Seq(
PreStartAndPostStopIdentity(onStart = _ testActor ! "start-a"),
PreStartAndPostStopIdentity(onStart = _ testActor ! "start-b"),
PreStartAndPostStopIdentity(onStart = _ testActor ! "start-c"))) {
@ -23,7 +25,7 @@ class LifecycleInterpreterSpec extends InterpreterSpecKit {
upstream.onComplete()
}
"call postStop in order on stages - when upstream completes" in new TestSetup(Seq(
"call postStop in order on stages - when upstream completes" in new OneBoundedSetup[String](Seq(
PreStartAndPostStopIdentity(onUpstreamCompleted = () testActor ! "complete-a", onStop = () testActor ! "stop-a"),
PreStartAndPostStopIdentity(onUpstreamCompleted = () testActor ! "complete-b", onStop = () testActor ! "stop-b"),
PreStartAndPostStopIdentity(onUpstreamCompleted = () testActor ! "complete-c", onStop = () testActor ! "stop-c"))) {
@ -37,7 +39,7 @@ class LifecycleInterpreterSpec extends InterpreterSpecKit {
expectNoMsg(300.millis)
}
"call postStop in order on stages - when upstream onErrors" in new TestSetup(Seq(
"call postStop in order on stages - when upstream onErrors" in new OneBoundedSetup[String](Seq(
PreStartAndPostStopIdentity(
onUpstreamFailed = ex testActor ! ex.getMessage,
onStop = () testActor ! "stop-c"))) {
@ -48,7 +50,7 @@ class LifecycleInterpreterSpec extends InterpreterSpecKit {
expectNoMsg(300.millis)
}
"call postStop in order on stages - when downstream cancels" in new TestSetup(Seq(
"call postStop in order on stages - when downstream cancels" in new OneBoundedSetup[String](Seq(
PreStartAndPostStopIdentity(onStop = () testActor ! "stop-a"),
PreStartAndPostStopIdentity(onStop = () testActor ! "stop-b"),
PreStartAndPostStopIdentity(onStop = () testActor ! "stop-c"))) {
@ -59,7 +61,7 @@ class LifecycleInterpreterSpec extends InterpreterSpecKit {
expectNoMsg(300.millis)
}
"call preStart before postStop" in new TestSetup(Seq(
"call preStart before postStop" in new OneBoundedSetup[String](Seq(
PreStartAndPostStopIdentity(onStart = _ testActor ! "start-a", onStop = () testActor ! "stop-a"))) {
expectMsg("start-a")
expectNoMsg(300.millis)
@ -68,25 +70,25 @@ class LifecycleInterpreterSpec extends InterpreterSpecKit {
expectNoMsg(300.millis)
}
"onError when preStart fails" in new TestSetup(Seq(
"onError when preStart fails" in new OneBoundedSetup[String](Seq(
PreStartFailer(() throw TE("Boom!")))) {
lastEvents() should ===(Set(Cancel, OnError(TE("Boom!"))))
}
"not blow up when postStop fails" in new TestSetup(Seq(
"not blow up when postStop fails" in new OneBoundedSetup[String](Seq(
PostStopFailer(() throw TE("Boom!")))) {
upstream.onComplete()
lastEvents() should ===(Set(OnComplete))
}
"onError when preStart fails with stages after" in new TestSetup(Seq(
"onError when preStart fails with stages after" in new OneBoundedSetup[String](Seq(
Map((x: Int) x, stoppingDecider),
PreStartFailer(() throw TE("Boom!")),
Map((x: Int) x, stoppingDecider))) {
lastEvents() should ===(Set(Cancel, OnError(TE("Boom!"))))
}
"continue with stream shutdown when postStop fails" in new TestSetup(Seq(
"continue with stream shutdown when postStop fails" in new OneBoundedSetup[String](Seq(
PostStopFailer(() throw TE("Boom!")))) {
lastEvents() should ===(Set())
@ -94,7 +96,7 @@ class LifecycleInterpreterSpec extends InterpreterSpecKit {
lastEvents should ===(Set(OnComplete))
}
"postStop when pushAndFinish called if upstream completes with pushAndFinish" in new TestSetup(Seq(
"postStop when pushAndFinish called if upstream completes with pushAndFinish" in new OneBoundedSetup[String](Seq(
new PushFinishStage(onPostStop = () testActor ! "stop"))) {
lastEvents() should be(Set.empty)
@ -107,7 +109,7 @@ class LifecycleInterpreterSpec extends InterpreterSpecKit {
expectMsg("stop")
}
"postStop when pushAndFinish called with pushAndFinish if indirect upstream completes with pushAndFinish" in new TestSetup(Seq(
"postStop when pushAndFinish called with pushAndFinish if indirect upstream completes with pushAndFinish" in new OneBoundedSetup[String](Seq(
Map((x: Any) x, stoppingDecider),
new PushFinishStage(onPostStop = () testActor ! "stop"),
Map((x: Any) x, stoppingDecider))) {
@ -122,7 +124,7 @@ class LifecycleInterpreterSpec extends InterpreterSpecKit {
expectMsg("stop")
}
"postStop when pushAndFinish called with pushAndFinish if upstream completes with pushAndFinish and downstream immediately pulls" in new TestSetup(Seq(
"postStop when pushAndFinish called with pushAndFinish if upstream completes with pushAndFinish and downstream immediately pulls" in new OneBoundedSetup[String](Seq(
new PushFinishStage(onPostStop = () testActor ! "stop"),
Fold("", (x: String, y: String) x + y, stoppingDecider))) {
@ -138,4 +140,54 @@ class LifecycleInterpreterSpec extends InterpreterSpecKit {
}
private[akka] case class PreStartAndPostStopIdentity[T](
onStart: LifecycleContext Unit = _ (),
onStop: () Unit = () (),
onUpstreamCompleted: () Unit = () (),
onUpstreamFailed: Throwable Unit = ex ())
extends PushStage[T, T] {
override def preStart(ctx: LifecycleContext) = onStart(ctx)
override def onPush(elem: T, ctx: Context[T]) = ctx.push(elem)
override def onUpstreamFinish(ctx: Context[T]): TerminationDirective = {
onUpstreamCompleted()
super.onUpstreamFinish(ctx)
}
override def onUpstreamFailure(cause: Throwable, ctx: Context[T]): TerminationDirective = {
onUpstreamFailed(cause)
super.onUpstreamFailure(cause, ctx)
}
override def postStop() = onStop()
}
private[akka] case class PreStartFailer[T](pleaseThrow: () Unit) extends PushStage[T, T] {
override def preStart(ctx: LifecycleContext) =
pleaseThrow()
override def onPush(elem: T, ctx: Context[T]) = ctx.push(elem)
}
private[akka] case class PostStopFailer[T](ex: () Throwable) extends PushStage[T, T] {
override def onUpstreamFinish(ctx: Context[T]) = ctx.finish()
override def onPush(elem: T, ctx: Context[T]) = ctx.push(elem)
override def postStop(): Unit = throw ex()
}
// This test is related to issue #17351
private[akka] class PushFinishStage(onPostStop: () Unit = () ()) extends PushStage[Any, Any] {
override def onPush(elem: Any, ctx: Context[Any]): SyncDirective =
ctx.pushAndFinish(elem)
override def onUpstreamFinish(ctx: Context[Any]): TerminationDirective =
ctx.fail(akka.stream.testkit.Utils.TE("Cannot happen"))
override def postStop(): Unit =
onPostStop()
}
}