2014-10-08 18:16:57 +02:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
|
|
|
*/
|
|
|
|
|
package akka.stream.impl.fusing
|
|
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
import scala.collection.immutable
|
2014-10-08 18:16:57 +02:00
|
|
|
import akka.stream.impl.FixedSizeBuffer
|
2014-11-12 10:43:39 +01:00
|
|
|
import akka.stream.stage._
|
2015-04-09 22:28:16 +02:00
|
|
|
import akka.stream._
|
2015-02-04 09:26:32 +01:00
|
|
|
import akka.stream.Supervision
|
2015-04-09 22:28:16 +02:00
|
|
|
import scala.concurrent.{ ExecutionContext, Future }
|
|
|
|
|
import scala.util.{ Try, Success, Failure }
|
|
|
|
|
import scala.annotation.tailrec
|
|
|
|
|
import scala.util.control.NonFatal
|
|
|
|
|
import akka.stream.impl.ReactiveStreamsCompliance
|
2014-10-08 18:16:57 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2015-02-04 09:26:32 +01:00
|
|
|
private[akka] final case class Map[In, Out](f: In ⇒ Out, decider: Supervision.Decider) extends PushStage[In, Out] {
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: In, ctx: Context[Out]): SyncDirective = ctx.push(f(elem))
|
2015-02-04 09:26:32 +01:00
|
|
|
|
|
|
|
|
override def decide(t: Throwable): Supervision.Directive = decider(t)
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2015-02-04 09:26:32 +01:00
|
|
|
private[akka] final case class Filter[T](p: T ⇒ Boolean, decider: Supervision.Decider) extends PushStage[T, T] {
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: T, ctx: Context[T]): SyncDirective =
|
2014-11-12 10:43:39 +01:00
|
|
|
if (p(elem)) ctx.push(elem)
|
|
|
|
|
else ctx.pull()
|
2015-02-04 09:26:32 +01:00
|
|
|
|
|
|
|
|
override def decide(t: Throwable): Supervision.Directive = decider(t)
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
2014-11-09 21:09:50 +01:00
|
|
|
private[akka] final object Collect {
|
|
|
|
|
// Cached function that can be used with PartialFunction.applyOrElse to ensure that A) the guard is only applied once,
|
|
|
|
|
// and the caller can check the returned value with Collect.notApplied to query whether the PF was applied or not.
|
|
|
|
|
// Prior art: https://github.com/scala/scala/blob/v2.11.4/src/library/scala/collection/immutable/List.scala#L458
|
|
|
|
|
final val NotApplied: Any ⇒ Any = _ ⇒ Collect.NotApplied
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-04 09:26:32 +01:00
|
|
|
private[akka] final case class Collect[In, Out](decider: Supervision.Decider)(pf: PartialFunction[In, Out]) extends PushStage[In, Out] {
|
2014-11-09 21:09:50 +01:00
|
|
|
import Collect.NotApplied
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: In, ctx: Context[Out]): SyncDirective =
|
2014-11-09 21:09:50 +01:00
|
|
|
pf.applyOrElse(elem, NotApplied) match {
|
2015-01-23 17:18:09 +01:00
|
|
|
case NotApplied ⇒ ctx.pull()
|
|
|
|
|
case result: Out @unchecked ⇒ ctx.push(result)
|
2014-11-09 21:09:50 +01:00
|
|
|
}
|
2015-02-04 09:26:32 +01:00
|
|
|
|
|
|
|
|
override def decide(t: Throwable): Supervision.Directive = decider(t)
|
2014-11-09 21:09:50 +01:00
|
|
|
}
|
|
|
|
|
|
2014-10-08 18:16:57 +02:00
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2015-02-04 09:26:32 +01:00
|
|
|
private[akka] final case class MapConcat[In, Out](f: In ⇒ immutable.Seq[Out], decider: Supervision.Decider) extends PushPullStage[In, Out] {
|
2014-10-08 18:16:57 +02:00
|
|
|
private var currentIterator: Iterator[Out] = Iterator.empty
|
|
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: In, ctx: Context[Out]): SyncDirective = {
|
2014-10-08 18:16:57 +02:00
|
|
|
currentIterator = f(elem).iterator
|
2014-11-12 10:43:39 +01:00
|
|
|
if (currentIterator.isEmpty) ctx.pull()
|
|
|
|
|
else ctx.push(currentIterator.next())
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPull(ctx: Context[Out]): SyncDirective =
|
2014-11-12 10:43:39 +01:00
|
|
|
if (currentIterator.hasNext) ctx.push(currentIterator.next())
|
|
|
|
|
else if (ctx.isFinishing) ctx.finish()
|
|
|
|
|
else ctx.pull()
|
2014-11-19 19:50:23 +01:00
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onUpstreamFinish(ctx: Context[Out]): TerminationDirective =
|
|
|
|
|
ctx.absorbTermination()
|
2015-02-04 09:26:32 +01:00
|
|
|
|
|
|
|
|
override def decide(t: Throwable): Supervision.Directive = decider(t)
|
|
|
|
|
|
|
|
|
|
override def restart(): MapConcat[In, Out] = copy()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2015-03-03 10:57:25 +01:00
|
|
|
private[akka] final case class Take[T](count: Long) extends PushStage[T, T] {
|
|
|
|
|
private var left: Long = count
|
2014-10-08 18:16:57 +02:00
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: T, ctx: Context[T]): SyncDirective = {
|
2014-10-08 18:16:57 +02:00
|
|
|
left -= 1
|
2014-11-12 10:43:39 +01:00
|
|
|
if (left > 0) ctx.push(elem)
|
|
|
|
|
else if (left == 0) ctx.pushAndFinish(elem)
|
|
|
|
|
else ctx.finish() //Handle negative take counts
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2015-03-03 10:57:25 +01:00
|
|
|
private[akka] final case class Drop[T](count: Long) extends PushStage[T, T] {
|
|
|
|
|
private var left: Long = count
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: T, ctx: Context[T]): SyncDirective =
|
2014-10-08 18:16:57 +02:00
|
|
|
if (left > 0) {
|
|
|
|
|
left -= 1
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pull()
|
|
|
|
|
} else ctx.push(elem)
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2015-02-04 09:26:32 +01:00
|
|
|
private[akka] final case class Scan[In, Out](zero: Out, f: (Out, In) ⇒ Out, decider: Supervision.Decider) extends PushPullStage[In, Out] {
|
2014-11-09 21:09:50 +01:00
|
|
|
private var aggregator = zero
|
|
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: In, ctx: Context[Out]): SyncDirective = {
|
2014-11-09 21:09:50 +01:00
|
|
|
val old = aggregator
|
|
|
|
|
aggregator = f(old, elem)
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.push(old)
|
2014-11-09 21:09:50 +01:00
|
|
|
}
|
|
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPull(ctx: Context[Out]): SyncDirective =
|
2014-11-12 10:43:39 +01:00
|
|
|
if (ctx.isFinishing) ctx.pushAndFinish(aggregator)
|
|
|
|
|
else ctx.pull()
|
2014-11-09 21:09:50 +01:00
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onUpstreamFinish(ctx: Context[Out]): TerminationDirective = ctx.absorbTermination()
|
2015-02-04 09:26:32 +01:00
|
|
|
|
|
|
|
|
override def decide(t: Throwable): Supervision.Directive = decider(t)
|
|
|
|
|
|
|
|
|
|
override def restart(): Scan[In, Out] = copy()
|
2014-11-09 21:09:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2015-02-04 09:26:32 +01:00
|
|
|
private[akka] final case class Fold[In, Out](zero: Out, f: (Out, In) ⇒ Out, decider: Supervision.Decider) extends PushPullStage[In, Out] {
|
2014-10-08 18:16:57 +02:00
|
|
|
private var aggregator = zero
|
|
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: In, ctx: Context[Out]): SyncDirective = {
|
2014-10-08 18:16:57 +02:00
|
|
|
aggregator = f(aggregator, elem)
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pull()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPull(ctx: Context[Out]): SyncDirective =
|
2014-11-12 10:43:39 +01:00
|
|
|
if (ctx.isFinishing) ctx.pushAndFinish(aggregator)
|
|
|
|
|
else ctx.pull()
|
2014-10-08 18:16:57 +02:00
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onUpstreamFinish(ctx: Context[Out]): TerminationDirective = ctx.absorbTermination()
|
2015-02-04 09:26:32 +01:00
|
|
|
|
|
|
|
|
override def decide(t: Throwable): Supervision.Directive = decider(t)
|
|
|
|
|
|
|
|
|
|
override def restart(): Fold[In, Out] = copy()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2014-11-12 10:43:39 +01:00
|
|
|
private[akka] final case class Grouped[T](n: Int) extends PushPullStage[T, immutable.Seq[T]] {
|
2014-11-09 21:09:50 +01:00
|
|
|
private val buf = {
|
|
|
|
|
val b = Vector.newBuilder[T]
|
|
|
|
|
b.sizeHint(n)
|
|
|
|
|
b
|
|
|
|
|
}
|
|
|
|
|
private var left = n
|
2014-10-08 18:16:57 +02:00
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: T, ctx: Context[immutable.Seq[T]]): SyncDirective = {
|
2014-11-09 21:09:50 +01:00
|
|
|
buf += elem
|
|
|
|
|
left -= 1
|
|
|
|
|
if (left == 0) {
|
|
|
|
|
val emit = buf.result()
|
|
|
|
|
buf.clear()
|
|
|
|
|
left = n
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.push(emit)
|
|
|
|
|
} else ctx.pull()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPull(ctx: Context[immutable.Seq[T]]): SyncDirective =
|
2014-11-12 10:43:39 +01:00
|
|
|
if (ctx.isFinishing) {
|
2014-11-09 21:09:50 +01:00
|
|
|
val elem = buf.result()
|
|
|
|
|
buf.clear() //FIXME null out the reference to the `buf`?
|
|
|
|
|
left = n
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pushAndFinish(elem)
|
|
|
|
|
} else ctx.pull()
|
2014-10-08 18:16:57 +02:00
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onUpstreamFinish(ctx: Context[immutable.Seq[T]]): TerminationDirective =
|
|
|
|
|
if (left == n) ctx.finish()
|
|
|
|
|
else ctx.absorbTermination()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2014-11-12 10:43:39 +01:00
|
|
|
private[akka] final case class Buffer[T](size: Int, overflowStrategy: OverflowStrategy) extends DetachedStage[T, T] {
|
2014-10-08 18:16:57 +02:00
|
|
|
import OverflowStrategy._
|
|
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
private val buffer = FixedSizeBuffer[T](size)
|
2014-10-08 18:16:57 +02:00
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onPush(elem: T, ctx: DetachedContext[T]): UpstreamDirective =
|
2015-04-09 22:28:16 +02:00
|
|
|
if (ctx.isHoldingDownstream) ctx.pushAndPull(elem)
|
2014-11-12 10:43:39 +01:00
|
|
|
else enqueueAction(ctx, elem)
|
2014-10-08 18:16:57 +02:00
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onPull(ctx: DetachedContext[T]): DownstreamDirective = {
|
|
|
|
|
if (ctx.isFinishing) {
|
2014-10-08 18:16:57 +02:00
|
|
|
val elem = buffer.dequeue().asInstanceOf[T]
|
2014-11-12 10:43:39 +01:00
|
|
|
if (buffer.isEmpty) ctx.pushAndFinish(elem)
|
|
|
|
|
else ctx.push(elem)
|
2015-04-09 22:28:16 +02:00
|
|
|
} else if (ctx.isHoldingUpstream) ctx.pushAndPull(buffer.dequeue().asInstanceOf[T])
|
|
|
|
|
else if (buffer.isEmpty) ctx.holdDownstream()
|
2014-11-12 10:43:39 +01:00
|
|
|
else ctx.push(buffer.dequeue().asInstanceOf[T])
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onUpstreamFinish(ctx: DetachedContext[T]): TerminationDirective =
|
|
|
|
|
if (buffer.isEmpty) ctx.finish()
|
|
|
|
|
else ctx.absorbTermination()
|
2014-10-08 18:16:57 +02:00
|
|
|
|
|
|
|
|
val enqueueAction: (DetachedContext[T], T) ⇒ UpstreamDirective = {
|
|
|
|
|
overflowStrategy match {
|
2014-11-12 10:43:39 +01:00
|
|
|
case DropHead ⇒ { (ctx, elem) ⇒
|
2014-10-08 18:16:57 +02:00
|
|
|
if (buffer.isFull) buffer.dropHead()
|
|
|
|
|
buffer.enqueue(elem)
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pull()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
2014-11-12 10:43:39 +01:00
|
|
|
case DropTail ⇒ { (ctx, elem) ⇒
|
2014-10-08 18:16:57 +02:00
|
|
|
if (buffer.isFull) buffer.dropTail()
|
|
|
|
|
buffer.enqueue(elem)
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pull()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
2014-11-12 10:43:39 +01:00
|
|
|
case DropBuffer ⇒ { (ctx, elem) ⇒
|
2014-10-08 18:16:57 +02:00
|
|
|
if (buffer.isFull) buffer.clear()
|
|
|
|
|
buffer.enqueue(elem)
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pull()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
2014-11-12 10:43:39 +01:00
|
|
|
case Backpressure ⇒ { (ctx, elem) ⇒
|
2014-10-08 18:16:57 +02:00
|
|
|
buffer.enqueue(elem)
|
2015-04-09 22:28:16 +02:00
|
|
|
if (buffer.isFull) ctx.holdUpstream()
|
2014-11-12 10:43:39 +01:00
|
|
|
else ctx.pull()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
2015-01-30 10:30:56 +01:00
|
|
|
case Fail ⇒ { (ctx, elem) ⇒
|
|
|
|
|
if (buffer.isFull) ctx.fail(new Fail.BufferOverflowException(s"Buffer overflow (max capacity was: $size)!"))
|
2014-10-08 18:16:57 +02:00
|
|
|
else {
|
|
|
|
|
buffer.enqueue(elem)
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pull()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2014-11-12 10:43:39 +01:00
|
|
|
private[akka] final case class Completed[T]() extends PushPullStage[T, T] {
|
2015-04-09 22:28:16 +02:00
|
|
|
override def onPush(elem: T, ctx: Context[T]): SyncDirective = ctx.finish()
|
|
|
|
|
override def onPull(ctx: Context[T]): SyncDirective = ctx.finish()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2015-02-04 09:26:32 +01:00
|
|
|
private[akka] final case class Conflate[In, Out](seed: In ⇒ Out, aggregate: (Out, In) ⇒ Out,
|
|
|
|
|
decider: Supervision.Decider) extends DetachedStage[In, Out] {
|
2014-10-08 18:16:57 +02:00
|
|
|
private var agg: Any = null
|
|
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onPush(elem: In, ctx: DetachedContext[Out]): UpstreamDirective = {
|
2015-02-04 09:26:32 +01:00
|
|
|
agg =
|
|
|
|
|
if (agg == null) seed(elem)
|
|
|
|
|
else aggregate(agg.asInstanceOf[Out], elem)
|
2014-10-08 18:16:57 +02:00
|
|
|
|
2015-04-09 22:28:16 +02:00
|
|
|
if (!ctx.isHoldingDownstream) ctx.pull()
|
2014-11-09 21:09:50 +01:00
|
|
|
else {
|
2014-10-08 18:16:57 +02:00
|
|
|
val result = agg.asInstanceOf[Out]
|
|
|
|
|
agg = null
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pushAndPull(result)
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onPull(ctx: DetachedContext[Out]): DownstreamDirective = {
|
|
|
|
|
if (ctx.isFinishing) {
|
|
|
|
|
if (agg == null) ctx.finish()
|
2014-10-08 18:16:57 +02:00
|
|
|
else {
|
|
|
|
|
val result = agg.asInstanceOf[Out]
|
|
|
|
|
agg = null
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pushAndFinish(result)
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
2015-04-09 22:28:16 +02:00
|
|
|
} else if (agg == null) ctx.holdDownstream()
|
2014-10-08 18:16:57 +02:00
|
|
|
else {
|
|
|
|
|
val result = agg.asInstanceOf[Out]
|
2015-02-04 09:26:32 +01:00
|
|
|
if (result == null) throw new NullPointerException
|
2014-10-08 18:16:57 +02:00
|
|
|
agg = null
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.push(result)
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onUpstreamFinish(ctx: DetachedContext[Out]): TerminationDirective = ctx.absorbTermination()
|
2015-02-04 09:26:32 +01:00
|
|
|
|
|
|
|
|
override def decide(t: Throwable): Supervision.Directive = decider(t)
|
|
|
|
|
|
|
|
|
|
override def restart(): Conflate[In, Out] = copy()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2014-11-12 10:43:39 +01:00
|
|
|
private[akka] final case class Expand[In, Out, Seed](seed: In ⇒ Seed, extrapolate: Seed ⇒ (Out, Seed)) extends DetachedStage[In, Out] {
|
2014-11-25 12:26:24 +01:00
|
|
|
private var s: Seed = _
|
|
|
|
|
private var started: Boolean = false
|
|
|
|
|
private var expanded: Boolean = false
|
2014-10-08 18:16:57 +02:00
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onPush(elem: In, ctx: DetachedContext[Out]): UpstreamDirective = {
|
2014-10-08 18:16:57 +02:00
|
|
|
s = seed(elem)
|
2014-11-25 12:26:24 +01:00
|
|
|
started = true
|
|
|
|
|
expanded = false
|
2015-04-09 22:28:16 +02:00
|
|
|
if (ctx.isHoldingDownstream) {
|
2014-11-25 12:26:24 +01:00
|
|
|
val (emit, newS) = extrapolate(s)
|
2014-10-08 18:16:57 +02:00
|
|
|
s = newS
|
2014-11-25 12:26:24 +01:00
|
|
|
expanded = true
|
2014-11-12 10:43:39 +01:00
|
|
|
ctx.pushAndPull(emit)
|
2015-04-09 22:28:16 +02:00
|
|
|
} else ctx.holdUpstream()
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
2014-11-12 10:43:39 +01:00
|
|
|
override def onPull(ctx: DetachedContext[Out]): DownstreamDirective = {
|
2014-11-25 12:26:24 +01:00
|
|
|
if (ctx.isFinishing) {
|
|
|
|
|
if (!started) ctx.finish()
|
|
|
|
|
else ctx.pushAndFinish(extrapolate(s)._1)
|
2015-04-09 22:28:16 +02:00
|
|
|
} else if (!started) ctx.holdDownstream()
|
2014-10-08 18:16:57 +02:00
|
|
|
else {
|
2014-11-25 12:26:24 +01:00
|
|
|
val (emit, newS) = extrapolate(s)
|
2014-10-08 18:16:57 +02:00
|
|
|
s = newS
|
2014-11-25 12:26:24 +01:00
|
|
|
expanded = true
|
2015-04-09 22:28:16 +02:00
|
|
|
if (ctx.isHoldingUpstream) ctx.pushAndPull(emit)
|
2014-11-12 10:43:39 +01:00
|
|
|
else ctx.push(emit)
|
2014-10-08 18:16:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2014-11-25 12:26:24 +01:00
|
|
|
|
|
|
|
|
override def onUpstreamFinish(ctx: DetachedContext[Out]): TerminationDirective = {
|
|
|
|
|
if (expanded) ctx.finish()
|
|
|
|
|
else ctx.absorbTermination()
|
|
|
|
|
}
|
2015-02-04 09:26:32 +01:00
|
|
|
|
|
|
|
|
final override def decide(t: Throwable): Supervision.Directive = Supervision.Stop
|
|
|
|
|
|
|
|
|
|
final override def restart(): Expand[In, Out, Seed] =
|
|
|
|
|
throw new UnsupportedOperationException("Expand doesn't support restart")
|
2014-11-19 19:50:23 +01:00
|
|
|
}
|
2015-04-09 22:28:16 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
private[akka] object MapAsync {
|
|
|
|
|
val NotYetThere = Failure(new Exception)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
private[akka] final case class MapAsync[In, Out](parallelism: Int, f: In ⇒ Future[Out], decider: Supervision.Decider)
|
|
|
|
|
extends AsyncStage[In, Out, (Int, Try[Out])] {
|
|
|
|
|
import MapAsync._
|
|
|
|
|
|
|
|
|
|
type Notification = (Int, Try[Out])
|
|
|
|
|
|
|
|
|
|
private var callback: AsyncCallback[Notification] = _
|
|
|
|
|
private val elemsInFlight = FixedSizeBuffer[Try[Out]](parallelism)
|
|
|
|
|
|
|
|
|
|
override def initAsyncInput(ctx: AsyncContext[Out, Notification]): Unit = {
|
|
|
|
|
callback = ctx.getAsyncCallback()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def decide(ex: Throwable) = decider(ex)
|
|
|
|
|
|
|
|
|
|
override def onPush(elem: In, ctx: AsyncContext[Out, Notification]) = {
|
|
|
|
|
val future = f(elem)
|
|
|
|
|
val idx = elemsInFlight.enqueue(NotYetThere)
|
|
|
|
|
future.onComplete(t ⇒ callback.invoke((idx, t)))(akka.dispatch.ExecutionContexts.sameThreadExecutionContext)
|
|
|
|
|
if (elemsInFlight.isFull) ctx.holdUpstream()
|
|
|
|
|
else ctx.pull()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def onPull(ctx: AsyncContext[Out, (Int, Try[Out])]) = {
|
|
|
|
|
@tailrec def rec(hasFreedUpSpace: Boolean): DownstreamDirective =
|
|
|
|
|
if (elemsInFlight.isEmpty && ctx.isFinishing) ctx.finish()
|
|
|
|
|
else if (elemsInFlight.isEmpty || elemsInFlight.peek == NotYetThere) {
|
|
|
|
|
if (hasFreedUpSpace && ctx.isHoldingUpstream) ctx.holdDownstreamAndPull()
|
|
|
|
|
else ctx.holdDownstream()
|
|
|
|
|
} else elemsInFlight.dequeue() match {
|
|
|
|
|
case Failure(ex) ⇒ rec(true)
|
|
|
|
|
case Success(elem) ⇒
|
|
|
|
|
if (ctx.isHoldingUpstream) ctx.pushAndPull(elem)
|
|
|
|
|
else ctx.push(elem)
|
|
|
|
|
}
|
|
|
|
|
rec(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def onAsyncInput(input: (Int, Try[Out]), ctx: AsyncContext[Out, Notification]) = {
|
|
|
|
|
@tailrec def rec(): Directive =
|
|
|
|
|
if (elemsInFlight.isEmpty && ctx.isFinishing) ctx.finish()
|
|
|
|
|
else if (elemsInFlight.isEmpty || elemsInFlight.peek == NotYetThere) ctx.ignore()
|
|
|
|
|
else elemsInFlight.dequeue() match {
|
|
|
|
|
case Failure(ex) ⇒ rec()
|
|
|
|
|
case Success(elem) ⇒
|
|
|
|
|
if (ctx.isHoldingUpstream) ctx.pushAndPull(elem)
|
|
|
|
|
else ctx.push(elem)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
input match {
|
|
|
|
|
case (idx, f @ Failure(ex)) ⇒
|
|
|
|
|
if (decider(ex) != Supervision.Stop) {
|
|
|
|
|
elemsInFlight.put(idx, f)
|
|
|
|
|
if (ctx.isHoldingDownstream) rec()
|
|
|
|
|
else ctx.ignore()
|
|
|
|
|
} else ctx.fail(ex)
|
|
|
|
|
case (idx, s: Success[_]) ⇒
|
|
|
|
|
val ex = try {
|
|
|
|
|
ReactiveStreamsCompliance.requireNonNullElement(s.value)
|
|
|
|
|
elemsInFlight.put(idx, s)
|
|
|
|
|
null: Exception
|
|
|
|
|
} catch {
|
|
|
|
|
case NonFatal(ex) ⇒
|
|
|
|
|
if (decider(ex) != Supervision.Stop) {
|
|
|
|
|
elemsInFlight.put(idx, Failure(ex))
|
|
|
|
|
null: Exception
|
|
|
|
|
} else ex
|
|
|
|
|
}
|
|
|
|
|
if (ex != null) ctx.fail(ex)
|
|
|
|
|
else if (ctx.isHoldingDownstream) rec()
|
|
|
|
|
else ctx.ignore()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def onUpstreamFinish(ctx: AsyncContext[Out, Notification]) =
|
|
|
|
|
if (ctx.isHoldingUpstream || !elemsInFlight.isEmpty) ctx.absorbTermination()
|
|
|
|
|
else ctx.finish()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
private[akka] final case class MapAsyncUnordered[In, Out](parallelism: Int, f: In ⇒ Future[Out], decider: Supervision.Decider)
|
|
|
|
|
extends AsyncStage[In, Out, Try[Out]] {
|
|
|
|
|
|
|
|
|
|
private var callback: AsyncCallback[Try[Out]] = _
|
|
|
|
|
private var inFlight = 0
|
|
|
|
|
private val buffer = FixedSizeBuffer[Out](parallelism)
|
|
|
|
|
|
|
|
|
|
private def todo = inFlight + buffer.used
|
|
|
|
|
|
|
|
|
|
override def initAsyncInput(ctx: AsyncContext[Out, Try[Out]]): Unit = {
|
|
|
|
|
callback = ctx.getAsyncCallback()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def decide(ex: Throwable) = decider(ex)
|
|
|
|
|
|
|
|
|
|
override def onPush(elem: In, ctx: AsyncContext[Out, Try[Out]]) = {
|
|
|
|
|
val future = f(elem)
|
|
|
|
|
inFlight += 1
|
|
|
|
|
future.onComplete(callback.invoke)(akka.dispatch.ExecutionContexts.sameThreadExecutionContext)
|
|
|
|
|
if (todo == parallelism) ctx.holdUpstream()
|
|
|
|
|
else ctx.pull()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def onPull(ctx: AsyncContext[Out, Try[Out]]) =
|
|
|
|
|
if (buffer.isEmpty) {
|
|
|
|
|
if (ctx.isFinishing && inFlight == 0) ctx.finish() else ctx.holdDownstream()
|
|
|
|
|
} else {
|
|
|
|
|
val elem = buffer.dequeue()
|
|
|
|
|
if (ctx.isHoldingUpstream) ctx.pushAndPull(elem)
|
|
|
|
|
else ctx.push(elem)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def onAsyncInput(input: Try[Out], ctx: AsyncContext[Out, Try[Out]]) = {
|
|
|
|
|
def ignoreOrFail(ex: Throwable) =
|
|
|
|
|
if (decider(ex) == Supervision.Stop) ctx.fail(ex)
|
|
|
|
|
else if (ctx.isHoldingUpstream) ctx.pull()
|
2015-04-14 12:39:24 +02:00
|
|
|
else if (ctx.isFinishing && todo == 0) ctx.finish()
|
2015-04-09 22:28:16 +02:00
|
|
|
else ctx.ignore()
|
|
|
|
|
|
|
|
|
|
inFlight -= 1
|
|
|
|
|
input match {
|
|
|
|
|
case Failure(ex) ⇒ ignoreOrFail(ex)
|
|
|
|
|
case Success(elem) ⇒
|
|
|
|
|
if (elem == null) {
|
|
|
|
|
val ex = ReactiveStreamsCompliance.elementMustNotBeNullException
|
|
|
|
|
ignoreOrFail(ex)
|
|
|
|
|
} else if (ctx.isHoldingDownstream) {
|
|
|
|
|
if (ctx.isHoldingUpstream) ctx.pushAndPull(elem)
|
|
|
|
|
else ctx.push(elem)
|
|
|
|
|
} else {
|
|
|
|
|
buffer.enqueue(elem)
|
|
|
|
|
ctx.ignore()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def onUpstreamFinish(ctx: AsyncContext[Out, Try[Out]]) =
|
|
|
|
|
if (todo > 0) ctx.absorbTermination()
|
|
|
|
|
else ctx.finish()
|
|
|
|
|
}
|