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

monix.reactive.internal.builders.IteratorAsObservable.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.internal.builders

import monix.execution.Ack.{Continue, Stop}
import monix.execution.cancelables.BooleanCancelable
import monix.execution.schedulers.ExecutionModel
import monix.execution.{Ack, Cancelable, Scheduler}
import monix.reactive.Observable
import monix.reactive.exceptions.MultipleSubscribersException
import monix.reactive.observers.Subscriber
import monix.execution.atomic.Atomic

import scala.annotation.tailrec
import scala.concurrent.Future
import scala.util.control.NonFatal
import scala.util.{Failure, Success}

/** Converts any `Iterator` into an observable */
private[reactive] final class IteratorAsObservable[T](
  iterator: Iterator[T],
  onFinish: Cancelable) extends Observable[T] {

  private[this] val wasSubscribed = Atomic(false)

  def unsafeSubscribeFn(out: Subscriber[T]): Cancelable = {
    if (wasSubscribed.getAndSet(true)) {
      out.onError(new MultipleSubscribersException("InputStreamObservable"))
      Cancelable.empty
    } else {
      startLoop(out)
    }
  }

  private def startLoop(subscriber: Subscriber[T]): Cancelable = {
    import subscriber.{scheduler => s}
    // Protect against contract violations - we are only allowed to
    // call onError if no other terminal event has been called.
    var streamErrors = true
    try {
      val isEmpty = iterator.isEmpty
      streamErrors = false
      // Short-circuiting empty iterators, as there's no reason to
      // start the streaming if we have no elements
      if (isEmpty) {
        subscriber.onComplete()
        Cancelable.empty
      }
      else {
        val cancelable = BooleanCancelable()
        // Starting the synchronous loop
        fastLoop(iterator, subscriber, cancelable, s.executionModel, 0)(s)
        cancelable
      }
    } catch {
      case NonFatal(ex) =>
        // We can only stream onError events if we have a guarantee
        // that no other final events happened, otherwise we could
        // violate the contract.
        if (streamErrors) {
          subscriber.onError(ex)
          Cancelable.empty
        } else {
          triggerCancel(s)
          s.reportFailure(ex)
          Cancelable.empty
        }
    }
  }

  /** Calls the onFinish callback ensuring that it doesn't throw errors,
    * or if it does, log them using our `Scheduler`.
    */
  private def triggerCancel(s: Scheduler): Unit =
    try onFinish.cancel() catch {
      case NonFatal(ex) =>
        s.reportFailure(ex)
    }

  /** In case of an asynchronous boundary, we reschedule the the
    * run-loop on another logical thread. Usage of `onComplete` takes
    * care of that.
    */
  private def reschedule(ack: Future[Ack], iter: Iterator[T],
    out: Subscriber[T], c: BooleanCancelable, em: ExecutionModel)
    (implicit s: Scheduler): Unit = {

    ack.onComplete {
      case Success(next) =>
        if (next == Continue)
          // If fastLoop throws, then it's a contract violation and
          // the only thing we can do is to log it
          try fastLoop(iter, out, c, em, 0) catch {
            case NonFatal(ex) =>
              triggerCancel(s)
              s.reportFailure(ex)
          }
        else {
          // Downstream Stop happened
          triggerCancel(s)
        }
      case Failure(ex) =>
        // The subscriber's `onNext` is not allowed to throw errors
        // because we don't know what to do with it. At this point the
        // behavior is undefined. So if it happens, we log the error
        // and trigger the `onFinish` cancelable.
        triggerCancel(s)
        s.reportFailure(ex)
    }
  }

  /** The `fastLoop` is a tail-recursive function that goes through the
    * elements of our iterator, one by one, and tries to push them
    * synchronously, for as long as the `ExecutionModel` permits.
    *
    * After it encounters an asynchronous boundary (i.e. an
    * uncompleted `Future` returned by `onNext`), then we
    * [[reschedule]] the loop on another logical thread.
    */
  @tailrec private
  def fastLoop(iter: Iterator[T], out: Subscriber[T], c: BooleanCancelable,
    em: ExecutionModel, syncIndex: Int)(implicit s: Scheduler): Unit = {

    // The result of onNext calls, on which we must do back-pressure
    var ack: Future[Ack] = Continue
    // We do not want to catch errors from our interaction with our
    // observer, since SafeObserver should take care of than, hence we
    // must only catch and stream errors related to the interactions
    // with the iterator
    var streamErrors = true
    // True in case our iterator is seen to be empty and we must
    // signal onComplete
    var iteratorIsDone = false
    // non-null in case we caught an iterator related error and we
    // must signal onError
    var iteratorTriggeredError: Throwable = null

    // We need to protect against errors, but we only take care of
    // iterator-related exceptions, otherwise we are dealing with a
    // contract violation and we won't take care of that
    try {
      if (iter.hasNext) {
        val next = iter.next()
        streamErrors = false
        ack = out.onNext(next)
      } else {
        iteratorIsDone = true
      }
    } catch {
      case NonFatal(ex) if streamErrors =>
        iteratorTriggeredError = ex
    }

    // Signaling onComplete
    if (iteratorIsDone) {
      streamErrors = true
      try {
        onFinish.cancel()
        streamErrors = false
        out.onComplete()
      } catch {
        case NonFatal(ex) if streamErrors =>
          out.onError(ex)
      }
    }
    else if (iteratorTriggeredError != null) {
      triggerCancel(s)
      // Signaling error only if the subscription isn't canceled
      if (!c.isCanceled) out.onError(iteratorTriggeredError)
    }
    else {
      // Logic for collapsing execution loops
      val nextIndex =
        if (ack == Continue) em.nextFrameIndex(syncIndex)
        else if (ack == Stop) -1
        else 0

      if (nextIndex > 0)
        fastLoop(iter, out, c, em, nextIndex)
      else if (nextIndex == 0 && !c.isCanceled)
        reschedule(ack, iter, out, c, em)
      else
        // Downstream Stop happened
        triggerCancel(s)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy