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 java.lang.ref.WeakReference
import monix.execution.Cancelable
import monix.execution.Cancelable.IsDummy
import monix.execution.atomic.{AtomicAny, PaddingStrategy}

/** 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]].
  *
  * The implementation is also relaxed about the thread-safety of
  * the [[forwardTo]] operation, treating it like a semi-final state and
  * using Java 8 `getAndSet` platform intrinsics for performance
  * reasons.
  *
  * 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 val state: AtomicAny[AnyRef]) extends AssignableCancelable {

  import ChainedCancelable.Canceled
  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 = {
    // Using getAndSet, which on Java 8 should be faster than
    // a compare-and-set.
    state.getAndSet(Canceled) match {
      case null | Canceled => ()
      case ref: Cancelable => ref.cancel()
      case wr: WeakReference[_] =>
        val cc = wr.get.asInstanceOf[CC]
        if (cc != null) cc.cancel()
    }
  }

  private def update(value: Cancelable): Unit = {
    val state = this.state

    while (true) {
      state.get match {
        case Canceled =>
          value.cancel()
          return
        case wr: WeakReference[_] =>
          val cc = wr.get.asInstanceOf[CC]
          if (cc != null) cc.update(value)
          return
        case current =>
          if (state.compareAndSet(current, value)) return
      }
    }
  }

  override def `:=`(value: Cancelable): this.type = {
    update(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.state.get match {
          case ref2: WeakReference[_] =>
            cursor = ref2.get.asInstanceOf[CC]
            if (cursor eq null) {
              cursor = null
              continue = false
            }
          case ref2 =>
            if (ref2 eq Canceled) {
              cancel(); return
            }
            continue = false
        }
      }
      cursor
    }

    if (newRoot != null) {
      // Using Java 8 platform intrinsics, should be faster than
      // normal getAndSet, while introducing unsafety; but in case a
      // concurrent cancellation happened, then we say sorry
      state.getAndSet(new WeakReference(newRoot)) match {
        case null => ()
        case Canceled => cancel()
        case _: IsDummy => ()
        case w: WeakReference[_] =>
          val cc = w.get
          if (cc != null) cc.asInstanceOf[CC].update(newRoot)
        case prev: Cancelable =>
          newRoot.update(prev)
      }
    }
  }
}

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

  def apply(ref: Cancelable): ChainedCancelable = {
    val state = AtomicAny.withPadding[AnyRef](ref, PaddingStrategy.LeftRight128)
    new ChainedCancelable(state)
  }

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy