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

monifu.reactive.observers.DropIncomingBufferedSubscriber.scala Maven / Gradle / Ivy

There is a newer version: 1.2
Show newest version
/*
 * Copyright (c) 2014-2015 by its authors. Some rights reserved.
 * See the project homepage at: http://www.monifu.org
 *
 * 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 monifu.reactive.observers

import monifu.collection.mutable.ConcurrentQueue
import monifu.concurrent.Scheduler
import monifu.concurrent.atomic.padded.Atomic
import monifu.reactive.Ack.{Cancel, Continue}
import monifu.reactive.{Ack, Observer}

import scala.annotation.tailrec
import scala.util.Failure
import scala.util.control.NonFatal

/**
 * A [[BufferedSubscriber]] implementation for the following policies:
 *
 * - [[monifu.reactive.OverflowStrategy.DropNew]]
 * - [[monifu.reactive.OverflowStrategy.DropNewThenSignal]]
 */
final class DropIncomingBufferedSubscriber[-T] private
    (underlying: Observer[T], bufferSize: Int, onOverflow: Long => T = null)
    (implicit val scheduler: Scheduler)
  extends BufferedSubscriber[T] with SynchronousSubscriber[T] { self =>

  require(bufferSize > 0, "bufferSize must be a strictly positive number")

  private[this] val queue = ConcurrentQueue.empty[T]
  // 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
  // for enforcing non-concurrent updates
  private[this] val itemsToPush = Atomic(0)

  private[this] var eventsDropped = 0L

  val observer: SynchronousObserver[T] = new SynchronousObserver[T] {
    def onNext(elem: T): Ack = self.synchronized {
      if (!upstreamIsComplete && !downstreamIsDone) {
        if (itemsToPush.get >= bufferSize) {
          // no more room, dropping event
          eventsDropped += 1
          Continue
        }
        else if (eventsDropped > 0 && onOverflow != null) {
          try {
            val message = onOverflow(eventsDropped)
            eventsDropped = 0
            // first send the overflow message
            queue.offer(message)
            notifyConsumerOfNewEvent()
            // then try to send our current event
            // (recursive non-tailrec call)
            onNext(elem)
          }
          catch {
            case NonFatal(ex) =>
              onError(ex)
              Cancel
          }
        }
        else try {
          queue.offer(elem)
          notifyConsumerOfNewEvent()
          Continue
        }
        catch {
          case NonFatal(ex) =>
            onError(ex)
            Cancel
        }
      }
      else
        Cancel
    }

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

    def onComplete(): Unit = self.synchronized {
      if (!upstreamIsComplete && !downstreamIsDone) {
        if (eventsDropped > 0 && onOverflow != null) try {
          val message = onOverflow(eventsDropped)
          eventsDropped = 0
          queue.offer(message)
          upstreamIsComplete = true
          notifyConsumerOfNewEvent()
        }
        catch {
          case NonFatal(ex) =>
            onError(ex)
        }
        else {
          upstreamIsComplete = true
          notifyConsumerOfNewEvent()
        }
      }
    }
  }

  @tailrec
  private[this] def notifyConsumerOfNewEvent(): Unit = {
    val currentNr = itemsToPush.get

    if (!itemsToPush.compareAndSet(currentNr, currentNr + 1))
      notifyConsumerOfNewEvent()
    else if (currentNr == 0)
      scheduler.execute(new Runnable {
        def run() = fastLoop(0)
      })
  }

  private[this] def rescheduled(processed: Int): Unit = {
    fastLoop(processed)
  }

  @tailrec
  private[this] def fastLoop(processed: Int): Unit = {
    if (!downstreamIsDone) {
      val hasError = errorThrown ne null
      val next = queue.poll()

      if (next != null) {
        underlying.onNext(next) match {
          case sync if sync.isCompleted =>
            sync match {
              case continue if continue == Continue || continue.value.get == Continue.IsSuccess =>
                // process next
                fastLoop(processed + 1)

              case done if done == Cancel || done.value.get == Cancel.IsSuccess =>
                // ending loop
                downstreamIsDone = true
                itemsToPush.set(0)

              case error if error.value.get.isFailure =>
                // ending loop
                downstreamIsDone = true
                itemsToPush.set(0)
                underlying.onError(error.value.get.failed.get)
            }

          case async =>
            async.onComplete {
              case Continue.IsSuccess =>
                // re-run loop (in different thread)
                rescheduled(processed + 1)

              case Cancel.IsSuccess =>
                // ending loop
                downstreamIsDone = true
                itemsToPush.set(0)

              case Failure(ex) =>
                // ending loop
                downstreamIsDone = true
                itemsToPush.set(0)
                underlying.onError(ex)

              case other =>
                // never happens, but to appease Scala's compiler
                downstreamIsDone = true
                itemsToPush.set(0)
                underlying.onError(new MatchError(s"$other"))
            }
        }
      }
      else if (upstreamIsComplete || hasError) {
        // Race-condition check, but if upstreamIsComplete=true is visible,
        // then the queue should be fully published because there's a clear happens-before
        // relationship between queue.offer() and upstreamIsComplete=true
        if (!queue.isEmpty) {
          fastLoop(processed)
        }
        else {
          // ending loop
          downstreamIsDone = true
          itemsToPush.set(0)
          queue.clear() // for GC purposes
          if (errorThrown ne null)
            underlying.onError(errorThrown)
          else
            underlying.onComplete()
        }
      }
      else {
        val remaining = itemsToPush.decrementAndGet(processed)
        // if the queue is non-empty (i.e. concurrent modifications just happened)
        // then start all over again
        if (remaining > 0) fastLoop(0)
      }
    }
  }
}

object DropIncomingBufferedSubscriber {
  /**
   * Returns an instance of a [[DropIncomingBufferedSubscriber]]
   * for the [[monifu.reactive.OverflowStrategy.DropNew DropNew]]
   * overflowStrategy.
   */
  def simple[T](underlying: Observer[T], bufferSize: Int)
      (implicit s: Scheduler): DropIncomingBufferedSubscriber[T] = {

    new DropIncomingBufferedSubscriber[T](underlying, bufferSize, null)
  }

  /**
   * Returns an instance of a [[DropIncomingBufferedSubscriber]]
   * for the [[monifu.reactive.OverflowStrategy.DropNew DropNew]]
   * overflowStrategy.
   */
  def withSignal[T](underlying: Observer[T], bufferSize: Int, onOverflow: Long => T)
      (implicit s: Scheduler): DropIncomingBufferedSubscriber[T] = {

    new DropIncomingBufferedSubscriber[T](underlying, bufferSize, onOverflow)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy