2018-10-29 17:19:37 +08:00
|
|
|
/*
|
2018-01-04 17:26:29 +00:00
|
|
|
* Copyright (C) 2015-2018 Lightbend Inc. <https://www.lightbend.com>
|
2017-06-06 16:38:05 +02:00
|
|
|
*/
|
2018-03-13 23:45:55 +09:00
|
|
|
|
2017-06-06 16:38:05 +02:00
|
|
|
package akka.stream.impl
|
|
|
|
|
|
2017-06-17 22:51:34 +03:00
|
|
|
import java.util.concurrent.CompletionStage
|
|
|
|
|
|
|
|
|
|
import akka.Done
|
|
|
|
|
import akka.annotation.InternalApi
|
2017-06-06 16:38:05 +02:00
|
|
|
import akka.stream.OverflowStrategies._
|
|
|
|
|
import akka.stream._
|
|
|
|
|
import akka.stream.stage._
|
2017-07-26 16:23:46 +02:00
|
|
|
import akka.stream.scaladsl.SourceQueueWithComplete
|
2017-06-06 16:38:05 +02:00
|
|
|
import scala.compat.java8.FutureConverters._
|
2017-06-17 22:51:34 +03:00
|
|
|
import scala.concurrent.{ Future, Promise }
|
|
|
|
|
import scala.util.control.NonFatal
|
2017-06-06 16:38:05 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
@InternalApi private[akka] object QueueSource {
|
2017-06-17 22:51:34 +03:00
|
|
|
|
2017-06-06 16:38:05 +02:00
|
|
|
sealed trait Input[+T]
|
|
|
|
|
final case class Offer[+T](elem: T, promise: Promise[QueueOfferResult]) extends Input[T]
|
|
|
|
|
case object Completion extends Input[Nothing]
|
|
|
|
|
final case class Failure(ex: Throwable) extends Input[Nothing]
|
2017-06-17 22:51:34 +03:00
|
|
|
|
2017-06-06 16:38:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
@InternalApi private[akka] final class QueueSource[T](maxBuffer: Int, overflowStrategy: OverflowStrategy) extends GraphStageWithMaterializedValue[SourceShape[T], SourceQueueWithComplete[T]] {
|
|
|
|
|
import QueueSource._
|
|
|
|
|
|
|
|
|
|
val out = Outlet[T]("queueSource.out")
|
|
|
|
|
override val shape: SourceShape[T] = SourceShape.of(out)
|
|
|
|
|
|
|
|
|
|
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes) = {
|
|
|
|
|
val completion = Promise[Done]
|
2017-06-17 22:51:34 +03:00
|
|
|
|
2018-12-05 14:31:43 +01:00
|
|
|
val stageLogic = new GraphStageLogic(shape) with OutHandler with SourceQueueWithComplete[T] with StageLogging {
|
|
|
|
|
override protected def logSource: Class[_] = classOf[QueueSource[_]]
|
|
|
|
|
|
2017-06-06 16:38:05 +02:00
|
|
|
var buffer: Buffer[T] = _
|
|
|
|
|
var pendingOffer: Option[Offer[T]] = None
|
|
|
|
|
var terminating = false
|
|
|
|
|
|
|
|
|
|
override def preStart(): Unit = {
|
|
|
|
|
if (maxBuffer > 0) buffer = Buffer(maxBuffer, materializer)
|
|
|
|
|
}
|
2017-07-13 10:57:55 +03:00
|
|
|
override def postStop(): Unit = {
|
2017-08-10 20:20:55 -04:00
|
|
|
val exception = new StreamDetachedException()
|
2017-07-13 10:57:55 +03:00
|
|
|
completion.tryFailure(exception)
|
2017-06-06 16:38:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def enqueueAndSuccess(offer: Offer[T]): Unit = {
|
|
|
|
|
buffer.enqueue(offer.elem)
|
|
|
|
|
offer.promise.success(QueueOfferResult.Enqueued)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def bufferElem(offer: Offer[T]): Unit = {
|
|
|
|
|
if (!buffer.isFull) {
|
|
|
|
|
enqueueAndSuccess(offer)
|
|
|
|
|
} else overflowStrategy match {
|
2018-12-05 14:31:43 +01:00
|
|
|
case s: DropHead ⇒
|
|
|
|
|
log.log(s.logLevel, "Dropping the head element because buffer is full and overflowStrategy is: [DropHead]")
|
2017-06-06 16:38:05 +02:00
|
|
|
buffer.dropHead()
|
|
|
|
|
enqueueAndSuccess(offer)
|
2018-12-05 14:31:43 +01:00
|
|
|
case s: DropTail ⇒
|
|
|
|
|
log.log(s.logLevel, "Dropping the tail element because buffer is full and overflowStrategy is: [DropTail]")
|
2017-06-06 16:38:05 +02:00
|
|
|
buffer.dropTail()
|
|
|
|
|
enqueueAndSuccess(offer)
|
2018-12-05 14:31:43 +01:00
|
|
|
case s: DropBuffer ⇒
|
|
|
|
|
log.log(s.logLevel, "Dropping all the buffered elements because buffer is full and overflowStrategy is: [DropBuffer]")
|
2017-06-06 16:38:05 +02:00
|
|
|
buffer.clear()
|
|
|
|
|
enqueueAndSuccess(offer)
|
2018-12-05 14:31:43 +01:00
|
|
|
case s: DropNew ⇒
|
|
|
|
|
log.log(s.logLevel, "Dropping the new element because buffer is full and overflowStrategy is: [DropNew]")
|
2017-06-06 16:38:05 +02:00
|
|
|
offer.promise.success(QueueOfferResult.Dropped)
|
2018-12-05 14:31:43 +01:00
|
|
|
case s: Fail ⇒
|
|
|
|
|
log.log(s.logLevel, "Failing because buffer is full and overflowStrategy is: [Fail]")
|
2017-06-17 22:51:34 +03:00
|
|
|
val bufferOverflowException = BufferOverflowException(s"Buffer overflow (max capacity was: $maxBuffer)!")
|
2017-06-06 16:38:05 +02:00
|
|
|
offer.promise.success(QueueOfferResult.Failure(bufferOverflowException))
|
|
|
|
|
completion.failure(bufferOverflowException)
|
|
|
|
|
failStage(bufferOverflowException)
|
2018-12-05 14:31:43 +01:00
|
|
|
case s: Backpressure ⇒
|
|
|
|
|
log.log(s.logLevel, "Backpressuring because buffer is full and overflowStrategy is: [Backpressure]")
|
2017-06-06 16:38:05 +02:00
|
|
|
pendingOffer match {
|
|
|
|
|
case Some(_) ⇒
|
|
|
|
|
offer.promise.failure(new IllegalStateException("You have to wait for previous offer to be resolved to send another request"))
|
|
|
|
|
case None ⇒
|
|
|
|
|
pendingOffer = Some(offer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-17 22:51:34 +03:00
|
|
|
private val callback = getAsyncCallback[Input[T]] {
|
2018-09-03 07:03:28 +02:00
|
|
|
case Offer(_, promise) if terminating ⇒
|
|
|
|
|
promise.success(QueueOfferResult.Dropped)
|
|
|
|
|
|
2017-06-06 16:38:05 +02:00
|
|
|
case offer @ Offer(elem, promise) ⇒
|
|
|
|
|
if (maxBuffer != 0) {
|
|
|
|
|
bufferElem(offer)
|
|
|
|
|
if (isAvailable(out)) push(out, buffer.dequeue())
|
|
|
|
|
} else if (isAvailable(out)) {
|
|
|
|
|
push(out, elem)
|
|
|
|
|
promise.success(QueueOfferResult.Enqueued)
|
|
|
|
|
} else if (pendingOffer.isEmpty)
|
|
|
|
|
pendingOffer = Some(offer)
|
|
|
|
|
else overflowStrategy match {
|
2018-12-05 14:31:43 +01:00
|
|
|
case s @ (_: DropHead | _: DropBuffer) ⇒
|
|
|
|
|
log.log(s.logLevel, "Dropping element because buffer is full and overflowStrategy is: [{}]", s)
|
2017-06-06 16:38:05 +02:00
|
|
|
pendingOffer.get.promise.success(QueueOfferResult.Dropped)
|
|
|
|
|
pendingOffer = Some(offer)
|
2018-12-05 14:31:43 +01:00
|
|
|
case s @ (_: DropTail | _: DropNew) ⇒
|
|
|
|
|
log.log(s.logLevel, "Dropping element because buffer is full and overflowStrategy is: [{}]", s)
|
2017-06-06 16:38:05 +02:00
|
|
|
promise.success(QueueOfferResult.Dropped)
|
2018-12-05 14:31:43 +01:00
|
|
|
case s: Fail ⇒
|
|
|
|
|
log.log(s.logLevel, "Failing because buffer is full and overflowStrategy is: [Fail]")
|
2017-06-17 22:51:34 +03:00
|
|
|
val bufferOverflowException = BufferOverflowException(s"Buffer overflow (max capacity was: $maxBuffer)!")
|
2017-06-06 16:38:05 +02:00
|
|
|
promise.success(QueueOfferResult.Failure(bufferOverflowException))
|
|
|
|
|
completion.failure(bufferOverflowException)
|
|
|
|
|
failStage(bufferOverflowException)
|
2018-12-05 14:31:43 +01:00
|
|
|
case s: Backpressure ⇒
|
|
|
|
|
log.log(s.logLevel, "Failing because buffer is full and overflowStrategy is: [Backpressure]")
|
2017-06-06 16:38:05 +02:00
|
|
|
promise.failure(new IllegalStateException("You have to wait for previous offer to be resolved to send another request"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case Completion ⇒
|
|
|
|
|
if (maxBuffer != 0 && buffer.nonEmpty || pendingOffer.nonEmpty) terminating = true
|
|
|
|
|
else {
|
|
|
|
|
completion.success(Done)
|
|
|
|
|
completeStage()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case Failure(ex) ⇒
|
|
|
|
|
completion.failure(ex)
|
|
|
|
|
failStage(ex)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setHandler(out, this)
|
|
|
|
|
|
|
|
|
|
override def onDownstreamFinish(): Unit = {
|
|
|
|
|
pendingOffer match {
|
2017-06-17 22:51:34 +03:00
|
|
|
case Some(Offer(_, promise)) ⇒
|
2017-06-06 16:38:05 +02:00
|
|
|
promise.success(QueueOfferResult.QueueClosed)
|
|
|
|
|
pendingOffer = None
|
|
|
|
|
case None ⇒ // do nothing
|
|
|
|
|
}
|
|
|
|
|
completion.success(Done)
|
|
|
|
|
completeStage()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def onPull(): Unit = {
|
|
|
|
|
if (maxBuffer == 0) {
|
|
|
|
|
pendingOffer match {
|
|
|
|
|
case Some(Offer(elem, promise)) ⇒
|
|
|
|
|
push(out, elem)
|
|
|
|
|
promise.success(QueueOfferResult.Enqueued)
|
|
|
|
|
pendingOffer = None
|
|
|
|
|
if (terminating) {
|
|
|
|
|
completion.success(Done)
|
|
|
|
|
completeStage()
|
|
|
|
|
}
|
|
|
|
|
case None ⇒
|
|
|
|
|
}
|
|
|
|
|
} else if (buffer.nonEmpty) {
|
|
|
|
|
push(out, buffer.dequeue())
|
|
|
|
|
pendingOffer match {
|
|
|
|
|
case Some(offer) ⇒
|
|
|
|
|
enqueueAndSuccess(offer)
|
|
|
|
|
pendingOffer = None
|
|
|
|
|
case None ⇒ //do nothing
|
|
|
|
|
}
|
|
|
|
|
if (terminating && buffer.isEmpty) {
|
|
|
|
|
completion.success(Done)
|
|
|
|
|
completeStage()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-17 22:51:34 +03:00
|
|
|
// SourceQueueWithComplete impl
|
2017-06-06 16:38:05 +02:00
|
|
|
override def watchCompletion() = completion.future
|
|
|
|
|
override def offer(element: T): Future[QueueOfferResult] = {
|
|
|
|
|
val p = Promise[QueueOfferResult]
|
2017-06-17 22:51:34 +03:00
|
|
|
callback.invokeWithFeedback(Offer(element, p))
|
|
|
|
|
.onFailure { case NonFatal(e) ⇒ p.tryFailure(e) }(akka.dispatch.ExecutionContexts.sameThreadExecutionContext)
|
2017-06-06 16:38:05 +02:00
|
|
|
p.future
|
|
|
|
|
}
|
2017-06-17 22:51:34 +03:00
|
|
|
override def complete(): Unit = callback.invoke(Completion)
|
|
|
|
|
|
|
|
|
|
override def fail(ex: Throwable): Unit = callback.invoke(Failure(ex))
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(stageLogic, stageLogic)
|
2017-06-06 16:38:05 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
@InternalApi private[akka] final class SourceQueueAdapter[T](delegate: SourceQueueWithComplete[T]) extends akka.stream.javadsl.SourceQueueWithComplete[T] {
|
|
|
|
|
def offer(elem: T): CompletionStage[QueueOfferResult] = delegate.offer(elem).toJava
|
|
|
|
|
def watchCompletion(): CompletionStage[Done] = delegate.watchCompletion().toJava
|
|
|
|
|
def complete(): Unit = delegate.complete()
|
|
|
|
|
def fail(ex: Throwable): Unit = delegate.fail(ex)
|
|
|
|
|
}
|