* =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:
parent
8344443d55
commit
a40826e1cb
2 changed files with 102 additions and 30 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue