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

monix.execution.Ack.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2014-2019 by The Monix Project Developers.
 * 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.execution

import scala.util.control.NonFatal
import monix.execution.schedulers.TrampolineExecutionContext.immediate
import scala.concurrent.duration.Duration
import scala.concurrent.{CanAwait, ExecutionContext, Future, Promise}
import scala.util.{Failure, Success, Try}

/** Represents an acknowledgement of processing that a consumer
  * sends back upstream. Useful to implement back-pressure.
  */
sealed abstract class Ack extends Future[Ack] with Serializable {
  val AsSuccess: Success[Ack]

  // For Scala 2.12 compatibility
  final def transform[S](f: (Try[Ack]) => Try[S])(implicit executor: ExecutionContext): Future[S] = {
    val p = Promise[S]()
    onComplete(
      r =>
        p.complete(try f(r)
        catch { case t if NonFatal(t) => Failure(t) }))
    p.future
  }

  // For Scala 2.12 compatibility
  final def transformWith[S](f: (Try[Ack]) => Future[S])(implicit executor: ExecutionContext): Future[S] = {
    val p = Promise[S]()
    onComplete(
      r =>
        p.completeWith(try f(r)
        catch { case t if NonFatal(t) => Future.failed(t) }))
    p.future
  }

  final def onComplete[U](func: Try[Ack] => U)(implicit executor: ExecutionContext): Unit =
    executor.execute(new Runnable {
      def run(): Unit = func(AsSuccess)
    })
}

object Ack {
  /** Acknowledgement of processing that signals upstream that the
    * consumer is interested in receiving more events.
    */
  case object Continue extends Ack { self =>
    val AsSuccess = Success(Continue)
    val value = Some(AsSuccess)
    val isCompleted = true

    final def ready(atMost: Duration)(implicit permit: CanAwait) = self
    final def result(atMost: Duration)(implicit permit: CanAwait) = Continue
  }

  /** Acknowledgement of processing that signals to upstream that the
    * consumer is no longer interested in receiving events.
    */
  case object Stop extends Ack { self =>
    val AsSuccess = Success(Stop)
    val value = Some(AsSuccess)
    val isCompleted = true

    final def ready(atMost: Duration)(implicit permit: CanAwait) = self
    final def result(atMost: Duration)(implicit permit: CanAwait) = Stop
  }

  /** Helpers for dealing with synchronous `Future[Ack]` results,
    * powered by macros.
    *
    * @define syncModel Execution will happen without any hard
    *         asynchronous boundaries — in case the `source`
    *         is an `Ack` value (e.g. `Continue` or `Stop`) then
    *         execution will be immediate, otherwise execution
    *         will be trampolined (being execute on Monix's
    *         `TrampolineExecutionContext`).
    *
    *         WARN: in case the source is an `Ack` value
    *         (e.g. `Continue` or `Stop`) and the execution being
    *         immediate, with no async boundaries, this means that
    *         application of this function is *stack unsafe*!
    *
    *         Use with great care as an optimization. Don't use
    *         it in tail-recursive loops!
    */
  implicit class AckExtensions[Self <: Future[Ack]](val source: Self) extends AnyVal {
    /** Returns `true` if self is a direct reference to
      * `Continue` or `Stop`, `false` otherwise.
      */
    def isSynchronous: Boolean =
      (source eq Continue) || (source eq Stop)

    /** Executes the given `callback` on `Continue`.
      *
      * $syncModel
      *
      * @param r is an exception reporter used for reporting errors
      *        triggered by `thunk`
      */
    def syncOnContinue(thunk: => Unit)(implicit r: UncaughtExceptionReporter): Self = {
      if (source eq Continue)
        try thunk
        catch {
          case e if NonFatal(e) => r.reportFailure(e)
        } else if (source ne Stop)
        source.onComplete {
          case Success(Continue) =>
            try thunk
            catch { case e if NonFatal(e) => r.reportFailure(e) }
          case _ =>
            ()
        }(immediate)
      source
    }

    /** Executes the given `callback` on `Stop` or on `Failure(ex)`.
      *
      * $syncModel
      *
      * @param r is an exception reporter used for reporting errors
      *        triggered by `cb`
      */
    def syncOnStopOrFailure(cb: Option[Throwable] => Unit)(implicit r: UncaughtExceptionReporter): Self = {
      if (source eq Stop)
        try cb(None)
        catch {
          case e if NonFatal(e) => r.reportFailure(e)
        } else if (source ne Continue)
        source.onComplete { ack =>
          try ack match {
            case Success(Stop) => cb(None)
            case Failure(e) => cb(Some(e))
            case _ => ()
          } catch {
            case e if NonFatal(e) => r.reportFailure(e)
          }
        }(immediate)
      source
    }

    /** Given a mapping function, returns a new future reference that
      * is the result of a `map` operation applied to the source.
      *
      * $syncModel
      *
      * @param f is the mapping function used to transform the source
      * @param r is an exception reporter used for reporting errors
      *        triggered by `f`
      */
    def syncMap(f: Ack => Ack)(implicit r: UncaughtExceptionReporter): Future[Ack] =
      syncFlatMap(f)(r)

    /** Given a mapping function, returns a new future reference that
      * is the result of a `flatMap` operation applied to the source.
      *
      * $syncModel
      *
      * @param f is the mapping function used to transform the source
      * @param r is an exception reporter used for reporting errors
      *        triggered by `f`
      */
    def syncFlatMap(f: Ack => Future[Ack])(implicit r: UncaughtExceptionReporter): Future[Ack] = {
      if ((source eq Continue) || (source eq Stop))
        try f(source.asInstanceOf[Ack])
        catch {
          case e if NonFatal(e) =>
            r.reportFailure(e)
            Stop
        } else
        source.flatMap(_.syncFlatMap(f)(r))(immediate)
    }

    /** When the source future is completed, either through an exception,
      * or a value, apply the provided function.
      *
      * $syncModel
      *
      * @param r is a reporter for exceptions thrown by `f`
      */
    def syncOnComplete(f: Try[Ack] => Unit)(implicit r: UncaughtExceptionReporter): Unit = {
      if ((source eq Continue) || (source eq Stop))
        try f(Success(source.asInstanceOf[Ack]))
        catch {
          case e if NonFatal(e) => r.reportFailure(e)
        } else
        source.onComplete { ack =>
          try f(ack)
          catch { case e if NonFatal(e) => r.reportFailure(e) }
        }(immediate)
    }

    /** If the source completes with a `Stop`, then complete the given
      * promise with a value.
      */
    def syncOnContinueFollow[A](p: Promise[A], value: A): Self = {
      if (source eq Continue)
        p.trySuccess(value)
      else if (source ne Stop)
        source.onComplete { r =>
          if (r.isSuccess && (r.get eq Continue))
            p.trySuccess(value)
        }(immediate)
      source
    }

    /** If the source completes with a `Stop`, then complete the given
      * promise with a value.
      */
    def syncOnStopFollow[A](p: Promise[A], value: A): Self = {
      if (source eq Stop)
        p.trySuccess(value)
      else if (source ne Continue)
        source.onComplete { r =>
          if (r.isSuccess && (r.get eq Stop))
            p.trySuccess(value)
        }(immediate)
      source
    }

    /** Tries converting an already completed `Future[Ack]` into a direct
      * reference to `Continue` or `Stop`. Useful for collapsing async
      * pipelines.
      */
    def syncTryFlatten(implicit r: UncaughtExceptionReporter): Future[Ack] =
      if (source == Continue || source == Stop) source
      else {
        if (source.isCompleted)
          source.value.get match {
            case Success(ack) => ack
            case Failure(ex) =>
              r.reportFailure(ex)
              Stop
          } else
          source
      }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy