2018-10-29 17:19:37 +08:00
|
|
|
/*
|
2020-01-02 07:24:59 -05:00
|
|
|
* Copyright (C) 2014-2020 Lightbend Inc. <https://www.lightbend.com>
|
2014-03-27 17:56:03 +01:00
|
|
|
*/
|
2018-03-13 23:45:55 +09:00
|
|
|
|
2014-03-27 17:56:03 +01:00
|
|
|
package akka.stream.impl
|
|
|
|
|
|
|
|
|
|
import scala.annotation.tailrec
|
2020-04-27 20:32:18 +08:00
|
|
|
|
|
|
|
|
import org.reactivestreams.{ Subscriber, Subscription }
|
2014-03-27 17:56:03 +01:00
|
|
|
|
2014-03-28 12:13:57 +01:00
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
private[akka] object SubscriberManagement {
|
2014-03-27 17:56:03 +01:00
|
|
|
|
|
|
|
|
sealed trait EndOfStream {
|
2014-07-22 12:21:53 +02:00
|
|
|
def apply[T](subscriber: Subscriber[T]): Unit
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object NotReached extends EndOfStream {
|
2014-07-22 12:21:53 +02:00
|
|
|
def apply[T](subscriber: Subscriber[T]): Unit = throw new IllegalStateException("Called apply on NotReached")
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object Completed extends EndOfStream {
|
2015-02-03 11:34:11 +01:00
|
|
|
import ReactiveStreamsCompliance._
|
|
|
|
|
def apply[T](subscriber: Subscriber[T]): Unit = tryOnComplete(subscriber)
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
2015-01-23 17:18:09 +01:00
|
|
|
final case class ErrorCompleted(cause: Throwable) extends EndOfStream {
|
2015-02-03 11:34:11 +01:00
|
|
|
import ReactiveStreamsCompliance._
|
|
|
|
|
def apply[T](subscriber: Subscriber[T]): Unit = tryOnError(subscriber, cause)
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
2015-03-27 13:13:44 +01:00
|
|
|
val ShutDown = new ErrorCompleted(ActorPublisher.NormalShutdownReason)
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
2014-03-28 12:13:57 +01:00
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2014-07-22 12:21:53 +02:00
|
|
|
private[akka] trait SubscriptionWithCursor[T] extends Subscription with ResizableMultiReaderRingBuffer.Cursor {
|
2015-02-03 11:34:11 +01:00
|
|
|
import ReactiveStreamsCompliance._
|
|
|
|
|
|
2014-08-21 16:07:09 +02:00
|
|
|
def subscriber: Subscriber[_ >: T]
|
2014-03-27 17:56:03 +01:00
|
|
|
|
2015-02-03 11:34:11 +01:00
|
|
|
def dispatch(element: T): Unit = tryOnNext(subscriber, element)
|
2014-03-27 17:56:03 +01:00
|
|
|
|
2014-04-01 10:28:27 +02:00
|
|
|
var active = true
|
2014-08-21 16:07:09 +02:00
|
|
|
|
|
|
|
|
/** Do not increment directly, use `moreRequested(Long)` instead (it provides overflow protection)! */
|
|
|
|
|
var totalDemand: Long = 0 // number of requested but not yet dispatched elements
|
2014-04-01 10:28:27 +02:00
|
|
|
var cursor: Int = 0 // buffer cursor, managed by buffer
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
2014-03-28 12:13:57 +01:00
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
private[akka] trait SubscriberManagement[T] extends ResizableMultiReaderRingBuffer.Cursors {
|
2014-03-27 17:56:03 +01:00
|
|
|
import SubscriberManagement._
|
2014-03-30 21:49:11 +02:00
|
|
|
type S <: SubscriptionWithCursor[T]
|
2014-03-27 17:56:03 +01:00
|
|
|
type Subscriptions = List[S]
|
|
|
|
|
|
|
|
|
|
def initialBufferSize: Int
|
|
|
|
|
def maxBufferSize: Int
|
|
|
|
|
|
2014-03-30 21:49:11 +02:00
|
|
|
/**
|
|
|
|
|
* called when we are ready to consume more elements from our upstream
|
|
|
|
|
* MUST NOT call pushToDownstream
|
|
|
|
|
*/
|
2014-08-21 16:07:09 +02:00
|
|
|
protected def requestFromUpstream(elements: Long): Unit
|
2014-03-30 21:49:11 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* called before `shutdown()` if the stream is *not* being regularly completed
|
2015-09-28 22:23:59 -07:00
|
|
|
* but shut-down due to the last subscriber having canceled its subscription
|
2014-03-30 21:49:11 +02:00
|
|
|
*/
|
|
|
|
|
protected def cancelUpstream(): Unit
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* called when the spi.Publisher/Processor is ready to be shut down
|
|
|
|
|
*/
|
|
|
|
|
protected def shutdown(completed: Boolean): Unit
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Use to register a subscriber
|
|
|
|
|
*/
|
2014-08-21 16:07:09 +02:00
|
|
|
protected def createSubscription(subscriber: Subscriber[_ >: T]): S
|
2014-03-30 21:49:11 +02:00
|
|
|
|
2014-03-27 17:56:03 +01:00
|
|
|
private[this] val buffer = new ResizableMultiReaderRingBuffer[T](initialBufferSize, maxBufferSize, this)
|
|
|
|
|
|
2014-03-30 21:49:11 +02:00
|
|
|
protected def bufferDebug: String = buffer.toString
|
|
|
|
|
|
2014-03-27 17:56:03 +01:00
|
|
|
// optimize for small numbers of subscribers by keeping subscribers in a plain list
|
|
|
|
|
private[this] var subscriptions: Subscriptions = Nil
|
|
|
|
|
|
|
|
|
|
// number of elements already requested but not yet received from upstream
|
|
|
|
|
private[this] var pendingFromUpstream: Long = 0
|
|
|
|
|
|
|
|
|
|
// if non-null, holds the end-of-stream state
|
|
|
|
|
private[this] var endOfStream: EndOfStream = NotReached
|
|
|
|
|
|
|
|
|
|
def cursors = subscriptions
|
|
|
|
|
|
2014-03-30 21:49:11 +02:00
|
|
|
/**
|
|
|
|
|
* more demand was signaled from a given subscriber
|
|
|
|
|
*/
|
2014-08-21 16:07:09 +02:00
|
|
|
protected def moreRequested(subscription: S, elements: Long): Unit =
|
2014-04-01 10:28:27 +02:00
|
|
|
if (subscription.active) {
|
2014-12-08 23:10:04 +01:00
|
|
|
import ReactiveStreamsCompliance._
|
|
|
|
|
// check for illegal demand See 3.9
|
|
|
|
|
if (elements < 1) {
|
|
|
|
|
try tryOnError(subscription.subscriber, numberOfElementsInRequestMustBePositiveException)
|
|
|
|
|
finally unregisterSubscriptionInternal(subscription)
|
|
|
|
|
} else {
|
|
|
|
|
endOfStream match {
|
2019-02-09 15:25:39 +01:00
|
|
|
case eos @ (NotReached | Completed) =>
|
2015-03-03 10:57:25 +01:00
|
|
|
val d = subscription.totalDemand + elements
|
|
|
|
|
// Long overflow, Reactive Streams Spec 3:17: effectively unbounded
|
|
|
|
|
val demand = if (d < 1) Long.MaxValue else d
|
|
|
|
|
subscription.totalDemand = demand
|
|
|
|
|
// returns Long.MinValue if the subscription is to be terminated
|
|
|
|
|
@tailrec def dispatchFromBufferAndReturnRemainingRequested(requested: Long, eos: EndOfStream): Long =
|
|
|
|
|
if (requested == 0) {
|
|
|
|
|
// if we are at end-of-stream and have nothing more to read we complete now rather than after the next `requestMore`
|
|
|
|
|
if ((eos ne NotReached) && buffer.count(subscription) == 0) Long.MinValue else 0
|
|
|
|
|
} else if (buffer.count(subscription) > 0) {
|
|
|
|
|
val goOn = try {
|
|
|
|
|
subscription.dispatch(buffer.read(subscription))
|
|
|
|
|
true
|
|
|
|
|
} catch {
|
2019-02-09 15:25:39 +01:00
|
|
|
case _: SpecViolation =>
|
2015-03-03 10:57:25 +01:00
|
|
|
unregisterSubscriptionInternal(subscription)
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
if (goOn) dispatchFromBufferAndReturnRemainingRequested(requested - 1, eos)
|
|
|
|
|
else Long.MinValue
|
|
|
|
|
} else if (eos ne NotReached) Long.MinValue
|
|
|
|
|
else requested
|
|
|
|
|
|
|
|
|
|
dispatchFromBufferAndReturnRemainingRequested(demand, eos) match {
|
2019-02-09 15:25:39 +01:00
|
|
|
case Long.MinValue =>
|
2015-03-03 10:57:25 +01:00
|
|
|
eos(subscription.subscriber)
|
|
|
|
|
unregisterSubscriptionInternal(subscription)
|
2019-02-09 15:25:39 +01:00
|
|
|
case x =>
|
2015-03-03 10:57:25 +01:00
|
|
|
subscription.totalDemand = x
|
|
|
|
|
requestFromUpstreamIfRequired()
|
2014-12-08 23:10:04 +01:00
|
|
|
}
|
2019-02-09 15:25:39 +01:00
|
|
|
case ErrorCompleted(_) => // ignore, the Subscriber might not have seen our error event yet
|
2014-12-08 23:10:04 +01:00
|
|
|
}
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private[this] final def requestFromUpstreamIfRequired(): Unit = {
|
|
|
|
|
@tailrec def maxRequested(remaining: Subscriptions, result: Long = 0): Long =
|
|
|
|
|
remaining match {
|
2019-02-09 15:25:39 +01:00
|
|
|
case head :: tail => maxRequested(tail, math.max(head.totalDemand, result))
|
|
|
|
|
case _ => result
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
2019-03-11 10:38:24 +01:00
|
|
|
val desired =
|
|
|
|
|
Math.min(Int.MaxValue, Math.min(maxRequested(subscriptions), buffer.maxAvailable) - pendingFromUpstream).toInt
|
2014-03-30 21:49:11 +02:00
|
|
|
if (desired > 0) {
|
|
|
|
|
pendingFromUpstream += desired
|
|
|
|
|
requestFromUpstream(desired)
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-30 21:49:11 +02:00
|
|
|
/**
|
|
|
|
|
* this method must be called by the implementing class whenever a new value is available to be pushed downstream
|
|
|
|
|
*/
|
2014-03-27 17:56:03 +01:00
|
|
|
protected def pushToDownstream(value: T): Unit = {
|
2014-03-30 21:49:11 +02:00
|
|
|
@tailrec def dispatch(remaining: Subscriptions, sent: Boolean = false): Boolean =
|
2014-03-27 17:56:03 +01:00
|
|
|
remaining match {
|
2019-02-09 15:25:39 +01:00
|
|
|
case head :: tail =>
|
2014-08-21 16:07:09 +02:00
|
|
|
if (head.totalDemand > 0) {
|
2014-03-30 21:49:11 +02:00
|
|
|
val element = buffer.read(head)
|
|
|
|
|
head.dispatch(element)
|
2014-08-21 16:07:09 +02:00
|
|
|
head.totalDemand -= 1
|
2015-08-01 00:13:14 +02:00
|
|
|
dispatch(tail, sent = true)
|
2014-03-30 21:49:11 +02:00
|
|
|
} else dispatch(tail, sent)
|
2019-02-09 15:25:39 +01:00
|
|
|
case _ => sent
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
endOfStream match {
|
2019-02-09 15:25:39 +01:00
|
|
|
case NotReached =>
|
2014-03-27 17:56:03 +01:00
|
|
|
pendingFromUpstream -= 1
|
|
|
|
|
if (!buffer.write(value)) throw new IllegalStateException("Output buffer overflow")
|
2014-03-30 21:49:11 +02:00
|
|
|
if (dispatch(subscriptions)) requestFromUpstreamIfRequired()
|
2019-02-09 15:25:39 +01:00
|
|
|
case _ =>
|
2014-03-27 17:56:03 +01:00
|
|
|
throw new IllegalStateException("pushToDownStream(...) after completeDownstream() or abortDownstream(...)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-30 21:49:11 +02:00
|
|
|
/**
|
|
|
|
|
* this method must be called by the implementing class whenever
|
|
|
|
|
* it has been determined that no more elements will be produced
|
|
|
|
|
*/
|
2014-03-27 17:56:03 +01:00
|
|
|
protected def completeDownstream(): Unit = {
|
|
|
|
|
if (endOfStream eq NotReached) {
|
|
|
|
|
@tailrec def completeDoneSubscriptions(remaining: Subscriptions, result: Subscriptions = Nil): Subscriptions =
|
|
|
|
|
remaining match {
|
2019-02-09 15:25:39 +01:00
|
|
|
case head :: tail =>
|
2014-03-30 21:49:11 +02:00
|
|
|
if (buffer.count(head) == 0) {
|
2014-04-01 10:28:27 +02:00
|
|
|
head.active = false
|
2014-03-30 21:49:11 +02:00
|
|
|
Completed(head.subscriber)
|
|
|
|
|
completeDoneSubscriptions(tail, result)
|
|
|
|
|
} else completeDoneSubscriptions(tail, head :: result)
|
2019-02-09 15:25:39 +01:00
|
|
|
case _ => result
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
endOfStream = Completed
|
|
|
|
|
subscriptions = completeDoneSubscriptions(subscriptions)
|
2014-03-30 21:49:11 +02:00
|
|
|
if (subscriptions.isEmpty) shutdown(completed = true)
|
2014-03-27 17:56:03 +01:00
|
|
|
} // else ignore, we need to be idempotent
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-30 21:49:11 +02:00
|
|
|
/**
|
|
|
|
|
* this method must be called by the implementing class to push an error downstream
|
|
|
|
|
*/
|
2014-03-27 17:56:03 +01:00
|
|
|
protected def abortDownstream(cause: Throwable): Unit = {
|
|
|
|
|
endOfStream = ErrorCompleted(cause)
|
2019-02-09 15:25:39 +01:00
|
|
|
subscriptions.foreach(s => endOfStream(s.subscriber))
|
2014-03-27 17:56:03 +01:00
|
|
|
subscriptions = Nil
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-30 21:49:11 +02:00
|
|
|
/**
|
|
|
|
|
* Register a new subscriber.
|
|
|
|
|
*/
|
2014-08-21 16:07:09 +02:00
|
|
|
protected def registerSubscriber(subscriber: Subscriber[_ >: T]): Unit = endOfStream match {
|
2019-03-11 10:38:24 +01:00
|
|
|
case NotReached if subscriptions.exists(_.subscriber == subscriber) =>
|
|
|
|
|
ReactiveStreamsCompliance.rejectDuplicateSubscriber(subscriber)
|
|
|
|
|
case NotReached => addSubscription(subscriber)
|
2019-02-09 15:25:39 +01:00
|
|
|
case Completed if buffer.nonEmpty => addSubscription(subscriber)
|
2019-03-11 10:38:24 +01:00
|
|
|
case eos => eos(subscriber)
|
2014-12-08 23:10:04 +01:00
|
|
|
}
|
|
|
|
|
|
2015-02-26 11:58:29 +01:00
|
|
|
private def addSubscription(subscriber: Subscriber[_ >: T]): Unit = {
|
|
|
|
|
import ReactiveStreamsCompliance._
|
2014-12-08 23:10:04 +01:00
|
|
|
val newSubscription = createSubscription(subscriber)
|
|
|
|
|
subscriptions ::= newSubscription
|
|
|
|
|
buffer.initCursor(newSubscription)
|
2015-02-26 11:58:29 +01:00
|
|
|
try tryOnSubscribe(subscriber, newSubscription)
|
|
|
|
|
catch {
|
2019-02-09 15:25:39 +01:00
|
|
|
case _: SpecViolation => unregisterSubscriptionInternal(newSubscription)
|
2015-02-26 11:58:29 +01:00
|
|
|
}
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 21:49:11 +02:00
|
|
|
/**
|
|
|
|
|
* called from `Subscription::cancel`, i.e. from another thread,
|
|
|
|
|
* override to add synchronization with itself, `subscribe` and `moreRequested`
|
|
|
|
|
*/
|
2014-03-27 17:56:03 +01:00
|
|
|
protected def unregisterSubscription(subscription: S): Unit =
|
|
|
|
|
unregisterSubscriptionInternal(subscription)
|
|
|
|
|
|
|
|
|
|
// must be idempotent
|
|
|
|
|
private def unregisterSubscriptionInternal(subscription: S): Unit = {
|
2014-03-30 21:49:11 +02:00
|
|
|
@tailrec def removeFrom(remaining: Subscriptions, result: Subscriptions = Nil): Subscriptions =
|
2014-03-27 17:56:03 +01:00
|
|
|
remaining match {
|
2019-02-09 15:17:14 +01:00
|
|
|
case head :: tail => if (head eq subscription) result.reverse_:::(tail) else removeFrom(tail, head :: result)
|
2019-02-09 15:25:39 +01:00
|
|
|
case _ => throw new IllegalStateException("Subscription to unregister not found")
|
2014-03-27 17:56:03 +01:00
|
|
|
}
|
2014-04-01 10:28:27 +02:00
|
|
|
if (subscription.active) {
|
2014-03-27 17:56:03 +01:00
|
|
|
subscriptions = removeFrom(subscriptions)
|
|
|
|
|
buffer.onCursorRemoved(subscription)
|
2014-04-01 10:28:27 +02:00
|
|
|
subscription.active = false
|
2014-03-27 17:56:03 +01:00
|
|
|
if (subscriptions.isEmpty) {
|
|
|
|
|
if (endOfStream eq NotReached) {
|
|
|
|
|
endOfStream = ShutDown
|
|
|
|
|
cancelUpstream()
|
|
|
|
|
}
|
2014-03-30 21:49:11 +02:00
|
|
|
shutdown(completed = false)
|
2014-03-27 17:56:03 +01:00
|
|
|
} else requestFromUpstreamIfRequired() // we might have removed a "blocking" subscriber and can continue now
|
|
|
|
|
} // else ignore, we need to be idempotent
|
|
|
|
|
}
|
|
|
|
|
}
|