Delay operator emit early fix #28269
* Bugfix for delay stage with EmitEarly #28269 Emit early would make it pull eagerly even if out was not available, when element arrived and buffer was full and out not available that element would be left in the input port and never end up in the buffer. * Additional cleanup of the Delay stage
This commit is contained in:
parent
bfabdebace
commit
583fe3e2a4
3 changed files with 38 additions and 24 deletions
|
|
@ -1700,37 +1700,46 @@ private[stream] object Collect {
|
|||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi private[akka] final class Delay[T](
|
||||
private[this] val delayStrategySupplier: () => DelayStrategy[_ >: T],
|
||||
private[this] val overflowStrategy: DelayOverflowStrategy)
|
||||
extends SimpleLinearGraphStage[T] {
|
||||
private[this] def timerName = "DelayedTimer"
|
||||
@InternalApi object Delay {
|
||||
private val TimerName = "DelayedTimer"
|
||||
private val DelayPrecisionMS = 10
|
||||
}
|
||||
|
||||
private[this] val DelayPrecisionMS = 10
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi private[akka] final class Delay[T](
|
||||
delayStrategySupplier: () => DelayStrategy[T],
|
||||
overflowStrategy: DelayOverflowStrategy)
|
||||
extends SimpleLinearGraphStage[T] {
|
||||
|
||||
override def initialAttributes: Attributes = DefaultAttributes.delay
|
||||
|
||||
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
|
||||
new TimerGraphStageLogic(shape) with InHandler with OutHandler {
|
||||
import Delay._
|
||||
|
||||
private[this] val size = inheritedAttributes.mandatoryAttribute[InputBuffer].max
|
||||
|
||||
private[this] val delayStrategy = delayStrategySupplier()
|
||||
|
||||
private[this] var buffer
|
||||
: BufferImpl[(Long, T)] = _ // buffer has pairs timestamp of expected push with upstream element
|
||||
|
||||
override def preStart(): Unit = buffer = BufferImpl(size, inheritedAttributes)
|
||||
// buffer has pairs of timestamp of expected push and element
|
||||
private[this] val buffer = BufferImpl[(Long, T)](size, inheritedAttributes)
|
||||
|
||||
private[this] val onPushWhenBufferFull: () => Unit = overflowStrategy match {
|
||||
case EmitEarly =>
|
||||
() => {
|
||||
if (isAvailable(out)) {
|
||||
if (isTimerActive(timerName))
|
||||
cancelTimer(timerName)
|
||||
if (isTimerActive(TimerName)) {
|
||||
cancelTimer(TimerName)
|
||||
}
|
||||
|
||||
push(out, buffer.dequeue()._2)
|
||||
grabAndPull()
|
||||
completeIfReady()
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"Was configured to emitEarly and got element when out is not ready and buffer is full, should not be possible.")
|
||||
}
|
||||
}
|
||||
case _: DropHead =>
|
||||
|
|
@ -1746,7 +1755,7 @@ private[stream] object Collect {
|
|||
case _: DropNew =>
|
||||
() => {
|
||||
grab(in)
|
||||
if (pullCondition) pull(in)
|
||||
if (shouldPull) pull(in)
|
||||
}
|
||||
case _: DropBuffer =>
|
||||
() => {
|
||||
|
|
@ -1768,39 +1777,41 @@ private[stream] object Collect {
|
|||
onPushWhenBufferFull()
|
||||
else {
|
||||
grabAndPull()
|
||||
if (!isTimerActive(timerName)) {
|
||||
if (!isTimerActive(TimerName)) {
|
||||
val waitTime = nextElementWaitTime()
|
||||
if (waitTime <= DelayPrecisionMS && isAvailable(out)) {
|
||||
push(out, buffer.dequeue()._2)
|
||||
completeIfReady()
|
||||
} else
|
||||
scheduleOnce(timerName, waitTime.millis)
|
||||
scheduleOnce(TimerName, waitTime.millis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def pullCondition: Boolean =
|
||||
!overflowStrategy.isBackpressure || buffer.used < size
|
||||
private def shouldPull: Boolean =
|
||||
buffer.used < size || !overflowStrategy.isBackpressure ||
|
||||
// we can only emit early if output is ready
|
||||
(overflowStrategy == EmitEarly && isAvailable(out))
|
||||
|
||||
private def grabAndPull(): Unit = {
|
||||
val element = grab(in)
|
||||
buffer.enqueue((System.nanoTime() + delayStrategy.nextDelay(element).toNanos, element))
|
||||
if (pullCondition) pull(in)
|
||||
if (shouldPull) pull(in)
|
||||
}
|
||||
|
||||
override def onUpstreamFinish(): Unit =
|
||||
completeIfReady()
|
||||
|
||||
def onPull(): Unit = {
|
||||
if (!isTimerActive(timerName) && !buffer.isEmpty) {
|
||||
if (!isTimerActive(TimerName) && !buffer.isEmpty) {
|
||||
val waitTime = nextElementWaitTime()
|
||||
if (waitTime <= DelayPrecisionMS)
|
||||
push(out, buffer.dequeue()._2)
|
||||
else
|
||||
scheduleOnce(timerName, waitTime.millis)
|
||||
scheduleOnce(TimerName, waitTime.millis)
|
||||
}
|
||||
|
||||
if (!isClosed(in) && !hasBeenPulled(in) && pullCondition)
|
||||
if (!isClosed(in) && !hasBeenPulled(in) && shouldPull)
|
||||
pull(in)
|
||||
|
||||
completeIfReady()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue