=str #22881 test also resume case for throwing mid-stream mapAsync (#22915)

* =str #22880 fix mapAsync racy failure handling

* =str #22881 test also resume case for throwing mid-stream mapAsync

* address review feedback, the handle calls must be exactly like that
This commit is contained in:
Konrad `ktoso` Malawski 2017-09-11 14:17:13 +02:00 committed by GitHub
parent 8344443d55
commit a40826e1cb
2 changed files with 102 additions and 30 deletions

View file

@ -11,7 +11,7 @@ import akka.event.{ LogSource, Logging, LoggingAdapter }
import akka.stream.Attributes.{ InputBuffer, LogLevels }
import akka.stream.OverflowStrategies._
import akka.stream.impl.fusing.GraphStages.SimpleLinearGraphStage
import akka.stream.impl.{ ReactiveStreamsCompliance, Stages, Buffer BufferImpl }
import akka.stream.impl.{ ConstantFun, ReactiveStreamsCompliance, Stages, Buffer BufferImpl }
import akka.stream.scaladsl.{ Source, SourceQueue }
import akka.stream.stage._
import akka.stream.{ Supervision, _ }
@ -1103,11 +1103,12 @@ private[stream] object Collect {
@InternalApi private[akka] object MapAsync {
final class Holder[T](var elem: Try[T], val cb: AsyncCallback[Holder[T]]) extends (Try[T] Unit) {
def setElem(t: Try[T]): Unit =
def setElem(t: Try[T]): Unit = {
elem = t match {
case Success(null) Failure[T](ReactiveStreamsCompliance.elementMustNotBeNullException)
case other other
}
}
override def apply(t: Try[T]): Unit = {
setElem(t)
@ -1141,12 +1142,16 @@ private[stream] object Collect {
lazy val decider = inheritedAttributes.get[SupervisionStrategy].map(_.decider).getOrElse(Supervision.stoppingDecider)
var buffer: BufferImpl[Holder[Out]] = _
def holderCompleted(h: Holder[Out]): Unit = {
h.elem match {
case Failure(e) if decider(e) == Supervision.Stop failStage(e)
case _ if (isAvailable(out)) pushOne()
}
private val handleSuccessElem: PartialFunction[Try[Out], Unit] = {
case Success(elem)
push(out, elem)
if (todo < parallelism && !hasBeenPulled(in)) tryPull(in)
}
private val handleFailureOrPushElem: PartialFunction[Try[Out], Unit] = {
case Failure(e) if decider(e) == Supervision.Stop failStage(e)
case _ if (isAvailable(out)) pushOne() // skip this element
}
private def holderCompleted(holder: Holder[Out]) = handleFailureOrPushElem.apply(holder.elem)
val futureCB = getAsyncCallback[Holder[Out]](holderCompleted)
@ -1154,18 +1159,13 @@ private[stream] object Collect {
override def preStart(): Unit = buffer = BufferImpl(parallelism, materializer)
@tailrec private def pushOne(): Unit =
private def pushOne(): Unit =
if (buffer.isEmpty) {
if (isClosed(in)) completeStage()
else if (!hasBeenPulled(in)) pull(in)
} else if (buffer.peek().elem == NotYetThere) {
if (todo < parallelism && !hasBeenPulled(in)) tryPull(in)
} else buffer.dequeue().elem match {
case Success(elem)
push(out, elem)
if (todo < parallelism && !hasBeenPulled(in)) tryPull(in)
case Failure(ex) pushOne()
}
} else handleSuccessElem.applyOrElse(buffer.dequeue().elem, handleFailureOrPushElem)
override def onPush(): Unit = {
try {
@ -1179,7 +1179,7 @@ private[stream] object Collect {
case None future.onComplete(holder)(akka.dispatch.ExecutionContexts.sameThreadExecutionContext)
case Some(v)
holder.setElem(v)
holderCompleted(holder)
handleFailureOrPushElem(v)
}
} catch {