All Downloads are FREE. Search and download functionalities are using the official Maven repository.

monix.reactive.observers.buffers.EvictingBufferedSubscriber.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2014-2016 by its authors. Some rights reserved.
 * See the project homepage at: https://monix.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package monix.reactive.observers.buffers

import monix.execution.Ack
import monix.execution.Ack.{Continue, Stop}
import monix.execution.internal.Platform
import monix.execution.internal.collection.{DropAllOnOverflowQueue, DropHeadOnOverflowQueue, EvictingQueue}
import monix.reactive.observers.{BufferedSubscriber, Subscriber}
import scala.annotation.tailrec
import scala.util.Failure
import scala.util.control.NonFatal

/** A [[BufferedSubscriber]] implementation for the
  * [[monix.reactive.OverflowStrategy.DropNew DropNew]] overflow strategy.
  */
private[buffers] final class EvictingBufferedSubscriber[-T] private
  (underlying: Subscriber[T], buffer: EvictingQueue[AnyRef], onOverflow: Long => Option[T] = null)
  extends BufferedSubscriber[T] with Subscriber.Sync[T] { self =>

  implicit val scheduler = underlying.scheduler
  // to be modified only in onError, before upstreamIsComplete
  private[this] var errorThrown: Throwable = null
  // to be modified only in onError / onComplete
  @volatile private[this] var upstreamIsComplete = false
  // to be modified only by consumer
  @volatile private[this] var downstreamIsDone = false
  // MUST BE synchronized with `self`, represents an
  // indicator that there's a loop in progress
  private[this] var isLoopStarted = false
  // events being dropped
  private[this] var eventsDropped = 0L
  // MUST only be accessed within the consumer loop
  private[this] val consumerBuffer = new Array[AnyRef](Platform.recommendedBatchSize)

  def onNext(elem: T): Ack = self.synchronized {
    if (!upstreamIsComplete && !downstreamIsDone) {
      try {
        eventsDropped += buffer.offer(elem.asInstanceOf[AnyRef])
        consume()
        Continue
      }
      catch {
        case NonFatal(ex) =>
          onError(ex)
          Stop
      }
    }
    else
      Stop
  }

  def onError(ex: Throwable): Unit = self.synchronized {
    if (!upstreamIsComplete && !downstreamIsDone) {
      errorThrown = ex
      upstreamIsComplete = true
      consume()
    }
  }

  def onComplete(): Unit = self.synchronized {
    if (!upstreamIsComplete && !downstreamIsDone) {
      upstreamIsComplete = true
      consume()
    }
  }

  private[this] def consume() = {
    // no synchronization here, because we are calling
    // this on the producer's side, which is already synchronized
    if (!isLoopStarted) {
      isLoopStarted = true
      scheduler.execute(consumer)
    }
  }

  private[this] val consumer = new Runnable { consumer =>
    def run(): Unit = self.synchronized {
      val count =
        if (eventsDropped > 0 && onOverflow != null) {
          try {
            onOverflow(eventsDropped) match {
              case Some(message) =>
                eventsDropped = 0
                consumerBuffer(0) = message.asInstanceOf[AnyRef]
                1 + buffer.pollMany(consumerBuffer, 1)
              case None =>
                buffer.pollMany(consumerBuffer)
            }
          } catch {
            case NonFatal(ex) =>
              errorThrown = ex
              upstreamIsComplete = true
              0
          }
        }
        else {
          buffer.pollMany(consumerBuffer)
        }

      if (count > 0) {
        isLoopStarted = true
        fastLoop(consumerBuffer, count, 0)
      }
      else if (upstreamIsComplete || (errorThrown ne null)) {
        // ending loop
        downstreamIsDone = true
        isLoopStarted = false

        if (errorThrown ne null)
          underlying.onError(errorThrown)
        else
          underlying.onComplete()
      }
      else {
        isLoopStarted = false
      }
    }

    def loop(array: Array[AnyRef], arrayLength: Int, processed: Int): Unit = {
      fastLoop(array, arrayLength, processed)
    }

    @tailrec
    def fastLoop(array: Array[AnyRef], arrayLength: Int, processed: Int): Unit = {
      if (processed < arrayLength) {
        val next = array(processed)

        underlying.onNext(next.asInstanceOf[T]) match {
          case sync if sync.isCompleted =>
            sync match {
              case continue if continue == Continue || continue.value.get == Continue.AsSuccess =>
                // process next
                fastLoop(array, arrayLength, processed + 1)

              case done if done == Stop || done.value.get == Stop.AsSuccess =>
                self.synchronized {
                  // ending loop
                  downstreamIsDone = true
                  isLoopStarted = false
                }

              case error if error.value.get.isFailure =>
                try underlying.onError(error.value.get.failed.get) finally
                  self.synchronized {
                    // ending loop
                    downstreamIsDone = true
                    isLoopStarted = false
                  }
            }

          case async =>
            async.onComplete {
              case Continue.AsSuccess =>
                // re-run loop (in different thread)
                loop(array, arrayLength, processed + 1)

              case Stop.AsSuccess =>
                self.synchronized {
                  // ending loop
                  downstreamIsDone = true
                  isLoopStarted = false
                }

              case Failure(ex) =>
                try underlying.onError(ex) finally {
                  // ending loop
                  downstreamIsDone = true
                  isLoopStarted = false
                }

              case other =>
                try underlying.onError(new MatchError(s"$other")) finally
                  self.synchronized {
                    // never happens, but to appease the Scala compiler
                    downstreamIsDone = true
                    isLoopStarted = false
                  }
            }
        }
      }
      else {
        // consume next batch of items
        scheduler.execute(consumer)
      }
    }
  }
}

private[monix] object EvictingBufferedSubscriber {
  /**
   * Returns an instance of a [[EvictingBufferedSubscriber]]
   * for the [[monix.reactive.OverflowStrategy.DropOld DropOld]]
   * overflow strategy.
   */
  def dropOld[A](underlying: Subscriber[A], bufferSize: Int): Subscriber.Sync[A] = {
    require(bufferSize > 1,
      "bufferSize must be a strictly positive number, bigger than 1")

    val buffer = DropHeadOnOverflowQueue[AnyRef](bufferSize)
    new EvictingBufferedSubscriber[A](underlying, buffer, null)
  }

  /**
   * Returns an instance of a [[EvictingBufferedSubscriber]]
   * for the [[monix.reactive.OverflowStrategy.DropOld DropOld]]
   * overflow strategy, with signaling of the number of events that
   * were dropped.
   */
  def dropOldAndSignal[A](underlying: Subscriber[A],
    bufferSize: Int, onOverflow: Long => Option[A]): Subscriber.Sync[A] = {

    require(bufferSize > 1,
      "bufferSize must be a strictly positive number, bigger than 1")

    val buffer = DropHeadOnOverflowQueue[AnyRef](bufferSize)
    new EvictingBufferedSubscriber[A](underlying, buffer, onOverflow)
  }

  /**
   * Returns an instance of a [[EvictingBufferedSubscriber]] for the
   * [[monix.reactive.OverflowStrategy.ClearBuffer ClearBuffer]]
   * overflow strategy.
   */
  def clearBuffer[A](underlying: Subscriber[A], bufferSize: Int): Subscriber.Sync[A] = {
    require(bufferSize > 1,
      "bufferSize must be a strictly positive number, bigger than 1")

    val buffer = DropAllOnOverflowQueue[AnyRef](bufferSize)
    new EvictingBufferedSubscriber[A](underlying, buffer, null)
  }

  /**
   * Returns an instance of a [[EvictingBufferedSubscriber]]
   * for the [[monix.reactive.OverflowStrategy.ClearBuffer ClearBuffer]]
   * overflow strategy, with signaling of the number of events that
   * were dropped.
   */
  def clearBufferAndSignal[A](underlying: Subscriber[A],
    bufferSize: Int, onOverflow: Long => Option[A]): Subscriber.Sync[A] = {

    require(bufferSize > 1,
      "bufferSize must be a strictly positive number, bigger than 1")

    val buffer = DropAllOnOverflowQueue[AnyRef](bufferSize)
    new EvictingBufferedSubscriber[A](underlying, buffer, onOverflow)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy