Merge pull request #22559 from jrudolph/jr/w/22554-fix-CCE-in-SubSink
=str #22554 fix ClassCastException in SubSink
This commit is contained in:
commit
70253fab3f
2 changed files with 55 additions and 33 deletions
|
|
@ -572,7 +572,24 @@ final class Split[T](val decision: Split.SplitDecision, val p: T ⇒ Boolean, va
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
object SubSink {
|
private[stream] object SubSink {
|
||||||
|
sealed trait State
|
||||||
|
/** Not yet materialized and no command has been scheduled */
|
||||||
|
case object Uninitialized extends State
|
||||||
|
|
||||||
|
/** A command was scheduled before materialization */
|
||||||
|
sealed abstract class CommandScheduledBeforeMaterialization(val command: Command) extends State
|
||||||
|
|
||||||
|
// preallocated instances for both commands
|
||||||
|
/** A RequestOne command was scheduled before materialization */
|
||||||
|
case object RequestOneScheduledBeforeMaterialization extends CommandScheduledBeforeMaterialization(RequestOne)
|
||||||
|
/** A Cancel command was scheduled before materialization */
|
||||||
|
case object CancelScheduledBeforeMaterialization extends CommandScheduledBeforeMaterialization(Cancel)
|
||||||
|
|
||||||
|
/** Steady state: sink has been materialized, commands can be delivered through the callback */
|
||||||
|
// Represented in unwrapped form as AsyncCallback[Command] directly to prevent a level of indirection
|
||||||
|
// case class Materialized(callback: AsyncCallback[Command]) extends State
|
||||||
|
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
case object RequestOne extends Command
|
case object RequestOne extends Command
|
||||||
case object Cancel extends Command
|
case object Cancel extends Command
|
||||||
|
|
@ -581,7 +598,7 @@ object SubSink {
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
final class SubSink[T](name: String, externalCallback: ActorSubscriberMessage ⇒ Unit)
|
private[stream] final class SubSink[T](name: String, externalCallback: ActorSubscriberMessage ⇒ Unit)
|
||||||
extends GraphStage[SinkShape[T]] {
|
extends GraphStage[SinkShape[T]] {
|
||||||
import SubSink._
|
import SubSink._
|
||||||
|
|
||||||
|
|
@ -590,23 +607,27 @@ final class SubSink[T](name: String, externalCallback: ActorSubscriberMessage
|
||||||
override def initialAttributes = Attributes.name(s"SubSink($name)")
|
override def initialAttributes = Attributes.name(s"SubSink($name)")
|
||||||
override val shape = SinkShape(in)
|
override val shape = SinkShape(in)
|
||||||
|
|
||||||
private val status = new AtomicReference[AnyRef]
|
private val status = new AtomicReference[ /* State */ AnyRef](Uninitialized)
|
||||||
|
|
||||||
def pullSubstream(): Unit = {
|
def pullSubstream(): Unit = dispatchCommand(RequestOneScheduledBeforeMaterialization)
|
||||||
|
def cancelSubstream(): Unit = dispatchCommand(CancelScheduledBeforeMaterialization)
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
private def dispatchCommand(newState: CommandScheduledBeforeMaterialization): Unit =
|
||||||
status.get match {
|
status.get match {
|
||||||
case f: AsyncCallback[Any] @unchecked ⇒ f.invoke(RequestOne)
|
case /* Materialized */ callback: AsyncCallback[Command @unchecked] ⇒ callback.invoke(newState.command)
|
||||||
case null ⇒
|
case Uninitialized ⇒
|
||||||
if (!status.compareAndSet(null, RequestOne))
|
if (!status.compareAndSet(Uninitialized, newState))
|
||||||
status.get.asInstanceOf[Command ⇒ Unit](RequestOne)
|
dispatchCommand(newState) // changed to materialized in the meantime
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def cancelSubstream(): Unit = status.get match {
|
case RequestOneScheduledBeforeMaterialization if newState == CancelScheduledBeforeMaterialization ⇒
|
||||||
case f: AsyncCallback[Any] @unchecked ⇒ f.invoke(Cancel)
|
// cancellation is allowed to replace pull
|
||||||
case x ⇒ // a potential RequestOne is overwritten
|
if (!status.compareAndSet(RequestOneScheduledBeforeMaterialization, newState))
|
||||||
if (!status.compareAndSet(x, Cancel))
|
dispatchCommand(RequestOneScheduledBeforeMaterialization)
|
||||||
status.get.asInstanceOf[Command ⇒ Unit](Cancel)
|
|
||||||
}
|
case cmd: CommandScheduledBeforeMaterialization ⇒
|
||||||
|
throw new IllegalStateException(s"${newState.command} on subsink is illegal when ${cmd.command} is still pending")
|
||||||
|
}
|
||||||
|
|
||||||
override def createLogic(attr: Attributes) = new GraphStageLogic(shape) with InHandler {
|
override def createLogic(attr: Attributes) = new GraphStageLogic(shape) with InHandler {
|
||||||
setHandler(in, this)
|
setHandler(in, this)
|
||||||
|
|
@ -615,29 +636,30 @@ final class SubSink[T](name: String, externalCallback: ActorSubscriberMessage
|
||||||
override def onUpstreamFinish(): Unit = externalCallback(ActorSubscriberMessage.OnComplete)
|
override def onUpstreamFinish(): Unit = externalCallback(ActorSubscriberMessage.OnComplete)
|
||||||
override def onUpstreamFailure(ex: Throwable): Unit = externalCallback(ActorSubscriberMessage.OnError(ex))
|
override def onUpstreamFailure(ex: Throwable): Unit = externalCallback(ActorSubscriberMessage.OnError(ex))
|
||||||
|
|
||||||
@tailrec private def setCB(cb: AsyncCallback[Command]): Unit = {
|
@tailrec
|
||||||
|
private def setCallback(callback: Command ⇒ Unit): Unit =
|
||||||
status.get match {
|
status.get match {
|
||||||
case null ⇒
|
case Uninitialized ⇒
|
||||||
if (!status.compareAndSet(null, cb)) setCB(cb)
|
if (!status.compareAndSet(Uninitialized, /* Materialized */ getAsyncCallback[Command](callback)))
|
||||||
case RequestOne ⇒
|
setCallback(callback)
|
||||||
pull(in)
|
|
||||||
if (!status.compareAndSet(RequestOne, cb)) setCB(cb)
|
case cmd: CommandScheduledBeforeMaterialization ⇒
|
||||||
case Cancel ⇒
|
if (status.compareAndSet(cmd, /* Materialized */ getAsyncCallback[Command](callback)))
|
||||||
completeStage()
|
// between those two lines a new command might have been scheduled, but that will go through the
|
||||||
if (!status.compareAndSet(Cancel, cb)) setCB(cb)
|
// async interface, so that the ordering is still kept
|
||||||
case _: AsyncCallback[_] ⇒
|
callback(cmd.command)
|
||||||
|
else
|
||||||
|
setCallback(callback)
|
||||||
|
|
||||||
|
case m: /* Materialized */ AsyncCallback[Command @unchecked] ⇒
|
||||||
failStage(new IllegalStateException("Substream Source cannot be materialized more than once"))
|
failStage(new IllegalStateException("Substream Source cannot be materialized more than once"))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override def preStart(): Unit = {
|
override def preStart(): Unit =
|
||||||
val ourOwnCallback = getAsyncCallback[Command] {
|
setCallback {
|
||||||
case RequestOne ⇒ tryPull(in)
|
case RequestOne ⇒ tryPull(in)
|
||||||
case Cancel ⇒ completeStage()
|
case Cancel ⇒ completeStage()
|
||||||
case _ ⇒ throw new IllegalStateException("Bug")
|
|
||||||
}
|
}
|
||||||
setCB(ourOwnCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def toString: String = name
|
override def toString: String = name
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue