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

monix.execution.internal.GenericVar.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.internal

import monix.execution.atomic.{AtomicAny, PaddingStrategy}
import monix.execution.internal.collection.LinkedMap
import scala.annotation.tailrec

/**
  * Internal API — common implementation between:
  *
  *  - `monix.execution.AsyncVar`
  *  - `monix.catnap.MVar`
  */
private[monix] abstract class GenericVar[A, CancelToken] protected (initial: Option[A], ps: PaddingStrategy) {

  import GenericVar._
  private[this] val stateRef: AtomicAny[State[A]] =
    AtomicAny.withPadding(
      initial match { case None => State.empty[A]; case Some(a) => State(a) },
      ps
    )

  protected def makeCancelable(f: Id => Unit, id: Id): CancelToken
  protected def emptyCancelable: CancelToken

  protected final def unsafePut(a: A, await: Either[Nothing, Unit] => Unit): CancelToken = {
    stateRef.get match {
      case current @ WaitForTake(value, listeners) =>
        val id = new Id
        val newMap = listeners.updated(id, (a, await))
        val update = WaitForTake(value, newMap)

        if (stateRef.compareAndSet(current, update)) {
          makeCancelable(putCancel, id)
        } else {
          unsafePut(a, await) // retry
        }

      case current @ WaitForPut(reads, takes) =>
        var first: Either[Nothing, A] => Unit = null
        val update: State[A] =
          if (takes.isEmpty) State(a)
          else {
            val (x, rest) = takes.dequeue
            first = x
            if (rest.isEmpty) State.empty[A]
            else WaitForPut(LinkedMap.empty, rest)
          }

        if (stateRef.compareAndSet(current, update)) {
          val value = Right(a)
          // Satisfies all current `read` requests found
          streamAll(value, reads)
          // Satisfies the first `take` request found
          if (first ne null) first(value)
          // Signals completion of `put`
          await(Constants.eitherOfUnit)
          emptyCancelable
        } else {
          unsafePut(a, await) // retry
        }
    }
  }

  protected final def unsafeTryPut(a: A): Boolean = {
    stateRef.get match {
      case WaitForTake(_, _) => false

      case current @ WaitForPut(reads, takes) =>
        var first: Either[Nothing, A] => Unit = null
        val update: State[A] =
          if (takes.isEmpty) State(a)
          else {
            val (x, rest) = takes.dequeue
            first = x
            if (rest.isEmpty) State.empty[A]
            else WaitForPut(LinkedMap.empty, rest)
          }

        if (!stateRef.compareAndSet(current, update)) {
          unsafeTryPut(a) // retry
        } else {
          val value = Right(a)
          // Satisfies all current `read` requests found
          streamAll(value, reads)
          // Satisfies the first `take` request found
          if (first ne null) first(value)
          // Signals completion of `put`
          true
        }
    }
  }

  private val putCancel: (Id => Unit) = {
    @tailrec def loop(id: Id): Unit =
      stateRef.get match {
        case current @ WaitForTake(_, queue) =>
          val update = current.copy(queue = queue - id)
          if (!stateRef.compareAndSet(current, update)) {
            loop(id) // retry
          }
        case _ =>
          ()
      }
    loop
  }

  protected final def unsafeTake(await: Either[Nothing, A] => Unit): CancelToken = {
    stateRef.get match {
      case current @ WaitForTake(a, queue) =>
        val value = Right(a)
        if (queue.isEmpty) {
          if (stateRef.compareAndSet(current, State.empty)) {
            await(value)
            emptyCancelable
          } else {
            unsafeTake(await) // retry
          }
        } else {
          val ((ax, notify), xs) = queue.dequeue
          if (stateRef.compareAndSet(current, WaitForTake(ax, xs))) {
            await(value)
            notify(Constants.eitherOfUnit)
            emptyCancelable
          } else {
            unsafeTake(await) // retry
          }
        }

      case current @ WaitForPut(reads, takes) =>
        val id = new Id
        val newQueue = takes.updated(id, await)
        if (stateRef.compareAndSet(current, WaitForPut(reads, newQueue)))
          makeCancelable(takeCancel, id)
        else {
          unsafeTake(await) // retry
        }
    }
  }

  protected final def unsafeTryTake(): Option[A] = {
    val current: State[A] = stateRef.get
    current match {
      case WaitForTake(value, queue) =>
        if (queue.isEmpty) {
          if (stateRef.compareAndSet(current, State.empty))
            // Signals completion of `take`
            Some(value)
          else {
            unsafeTryTake() // retry
          }
        } else {
          val ((ax, notify), xs) = queue.dequeue
          val update = WaitForTake(ax, xs)
          if (stateRef.compareAndSet(current, update)) {
            // Complete the `put` request waiting on a notification
            notify(Constants.eitherOfUnit)
            // Signals completion of `take`
            Some(value)
          } else {
            unsafeTryTake() // retry
          }
        }
      case WaitForPut(_, _) =>
        None
    }
  }

  private val takeCancel: (Id => Unit) = {
    @tailrec def loop(id: Id): Unit =
      stateRef.get match {
        case current @ WaitForPut(reads, takes) =>
          val newMap = takes - id
          val update: State[A] = WaitForPut(reads, newMap)
          if (!stateRef.compareAndSet(current, update))
            loop(id)
        case _ =>
      }
    loop
  }

  protected final def unsafeRead(await: Either[Nothing, A] => Unit): CancelToken = {
    val current: State[A] = stateRef.get
    current match {
      case WaitForTake(value, _) =>
        // A value is available, so complete `read` immediately without
        // changing the sate
        await(Right(value))
        emptyCancelable

      case WaitForPut(reads, takes) =>
        // No value available, enqueue the callback
        val id = new Id
        val newQueue = reads.updated(id, await)
        if (stateRef.compareAndSet(current, WaitForPut(newQueue, takes)))
          makeCancelable(readCancel, id)
        else
          unsafeRead(await) // retry
    }
  }

  protected final def unsafeTryRead(): Option[A] =
    stateRef.get match {
      case WaitForTake(value, _) =>
        Some(value)
      case _ =>
        None
    }

  protected final def unsafeIsEmpty(): Boolean =
    stateRef.get match {
      case WaitForTake(_, _) =>
        false
      case _ =>
        true
    }

  @tailrec
  private val readCancel: (Id => Unit) = {
    def loop(id: Id): Unit =
      stateRef.get match {
        case current @ WaitForPut(reads, takes) =>
          val newMap = reads - id
          val update: State[A] = WaitForPut(newMap, takes)
          if (!stateRef.compareAndSet(current, update)) {
            loop(id) // retry
          }
        case _ =>
      }
    loop
  }

  // For streaming a value to a whole `reads` collection
  private final def streamAll(value: Either[Nothing, A], listeners: LinkedMap[Id, Either[Nothing, A] => Unit]): Unit = {

    val cursor = listeners.values.iterator
    while (cursor.hasNext) cursor.next().apply(value)
  }
}

private[monix] object GenericVar {

  /** Used with `LinkedMap` to identify callbacks that need to be cancelled. */
  private[monix] final class Id extends Serializable

  /** ADT modelling the internal state. */
  private sealed trait State[A]

  /** Private [[State]] builders.*/
  private object State {
    private[this] val ref = WaitForPut[Any](LinkedMap.empty, LinkedMap.empty)
    def apply[A](a: A): State[A] = WaitForTake(a, LinkedMap.empty)
    /** `Empty` state, reusing the same instance. */
    def empty[A]: State[A] = ref.asInstanceOf[State[A]]
  }

  /** `AsyncVar` state signaling it has `take` callbacks
    * registered and we are waiting for one or multiple
    * `put` operations.
    */
  private final case class WaitForPut[A](
    reads: LinkedMap[Id, Either[Nothing, A] => Unit],
    takes: LinkedMap[Id, Either[Nothing, A] => Unit])
    extends State[A]

  /** `AsyncVar` state signaling it has one or more values enqueued,
    * to be signaled on the next `take`.
    */
  private final case class WaitForTake[A](value: A, queue: LinkedMap[Id, (A, Either[Nothing, Unit] => Unit)])
    extends State[A]
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy