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

monix.execution.cancelables.ChainedCancelable.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.cancelables

import monix.execution.Cancelable
import monix.execution.Cancelable.IsDummy

/** Represents a [[monix.execution.Cancelable]] whose underlying
  * cancelable reference can be swapped for another. It can
  * be "chained" to another `ChainedCancelable`, forwarding all
  * operations to it.
  *
  * For most purposes it works like a [[OrderedCancelable]]:
  *
  * {{{
  *   val s = ChainedCancelable()
  *   s := c1 // sets the underlying cancelable to c1
  *   s := c2 // swaps the underlying cancelable to c2
  *
  *   s.cancel() // also cancels c2
  *
  *   s := c3 // also cancels c3, because s is already canceled
  * }}}
  *
  * However it can also be linked to another `ChainedCancelable`
  * reference, forwarding all requests to it:
  *
  * {{{
  *   val source = ChainedCancelable()
  *   val child1 = ChainedCancelable()
  *   val child2 = ChainedCancelable()
  *
  *   // Hence forth forwards all operations on `child1` to `source`
  *   child1.chainTo(source)
  *
  *   // Also forwarding all `child2` operations to `source`.
  *   // This happens because `child1` was linked to `source` first
  *   // but order matters, as `child2` will be linked directly
  *   // to `source` and not to `child1`, in order for `child1` to
  *   // be garbage collected if it goes out of scope ;-)
  *   child2.chainTo(child1)
  *
  *   // Source will be updated with a new Cancelable ref
  *   child1 := Cancelable(() => println("Cancelling (1)"))
  *
  *   // Source will be updated with another Cancelable ref
  *   child2 := Cancelable(() => println("Cancelling (2)"))
  *
  *   source.cancel()
  *   //=> Cancelling (2)
  * }}}
  *
  * This implementation is a special purpose [[AssignableCancelable]],
  * much like [[StackedCancelable]], to be used in `flatMap`
  * implementations that need it.
  *
  * The problem that it solves in Monix's codebase is that various
  * `flatMap` implementations need to be memory safe.
  * By "chaining" cancelable references, we allow the garbage collector
  * to get rid of references created in a `flatMap` loop, the goal
  * being to consume a constant amount of memory. Thus this
  * implementation is used for
  * [[monix.execution.CancelableFuture CancelableFuture]].
  *
  * If unsure about what to use, then you probably don't need
  * [[ChainedCancelable]]. Use [[OrderedCancelable]] or
  * [[SingleAssignCancelable]] for most purposes.
  */
final class ChainedCancelable private (private var stateRef: AnyRef) extends AssignableCancelable {

  import ChainedCancelable.{Canceled, WeakRef}
  private type CC = ChainedCancelable

  // States of `state`:
  //
  //  - null: in case this is a dummy
  //  - Cancelled: if it was cancelled
  //  - _: WeakReference[ChainedCancelable]: in case it was chained
  //  - _: Cancelable: in case it has an underlying reference

  override def cancel(): Unit = {
    val prevRef = stateRef
    stateRef = Canceled

    prevRef match {
      case null | Canceled => ()
      case ref: Cancelable => ref.cancel()
      case WeakRef(cc) =>
        if (cc != null) cc.cancel()
    }
  }

  override def `:=`(value: Cancelable): this.type = {
    stateRef match {
      case Canceled =>
        value.cancel()
      case WeakRef(cc) =>
        if (cc != null) cc := value
      case _ =>
        stateRef = value
    }
    this
  }

  /** Chains this `ChainedCancelable` to another reference,
    * such that all operations are forwarded to `other`.
    *
    * {{{
    *   val source = ChainedCancelable()
    *   val child1 = ChainedCancelable()
    *   val child2 = ChainedCancelable()
    *
    *   // Hence forth forwards all operations on `child1` to `source`
    *   child1.chainTo(source)
    *
    *   // Also forwarding all `child2` operations to `source`
    *   // (this happens because `child1` was linked to `source` first
    *   // but order matters ;-))
    *   child2.chainTo(child1)
    *
    *   // Source will be updated with a new Cancelable ref
    *   child1 := Cancelable(() => println("Cancelling (1)"))
    *
    *   // Source will be updated with another Cancelable ref
    *   child2 := Cancelable(() => println("Cancelling (2)"))
    *
    *   source.cancel()
    *   //=> Cancelling (2)
    * }}}
    */
  def forwardTo(other: ChainedCancelable): Unit = {
    type CC = ChainedCancelable

    // Short-circuit in case we have the same reference
    val newRoot = {
      var cursor = other
      var continue = true

      while (continue) {
        // Short-circuit if we discover a cycle
        if (cursor eq this) return
        cursor.stateRef match {
          case WeakRef(ref2) =>
            cursor = ref2
            continue = cursor ne null
          case ref2 =>
            if (ref2 eq Canceled) {
              cancel(); return
            }
            continue = false
        }
      }
      cursor
    }

    if (newRoot != null) {
      val prevRef = stateRef
      stateRef = WeakRef(newRoot)

      prevRef match {
        case null => ()
        case Canceled => cancel()
        case _: IsDummy => ()
        case WeakRef(cc) =>
          if (cc != null) cc := newRoot
        case prev: Cancelable =>
          newRoot := prev
      }
    }
  }
}

object ChainedCancelable {
  def apply(): ChainedCancelable =
    apply(null)

  def apply(ref: Cancelable): ChainedCancelable =
    new ChainedCancelable(ref)

  /** Internal marker to use for chained cancellables that
    * have been cancelled.
    */
  private object Canceled

  /** To use instead of a weak reference. */
  private case class WeakRef(ref: ChainedCancelable)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy