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

tofu.concurrent.impl.QVarSM.scala Maven / Gradle / Ivy

package tofu.concurrent.impl

import cats.Monad
import tofu.concurrent.QVar
import tofu.syntax.monadic._
import cats.data.{NonEmptyVector => NEV}
import scala.annotation.tailrec

abstract class QVarSM[F[_]: Monad, A, P] extends QVar[F, A] {
  import QVarSM._

  protected type S = State[A, P]
  protected def newPromise: F[P]
  protected def complete(x: A, promise: P): F[Unit]
  protected def await(p: P): F[A]
  protected def modifyF[X](f: S => (S, F[X])): F[X]
  protected def get: F[S]
  protected def set(s: S): F[Unit]
  protected def onCancel(fa: F[A], fc: => F[Unit]): F[A]

  override def isEmpty = get.map {
    case Contains(p, _) => p.isEmpty
    case _              => true
  }

  // we use manual queue of requests here with controllable offset
  // offset is an offset of a permanent request index in our pseudo-queue,
  // needed by the take operation, too keep track its position
  // every time put removes a head request it will increment the offset
  override def put(a: A): F[Unit] = modifyF {
    case Contains(v, off) => (Contains(a +: v, off), unit[F])
    case Await(reqs, off) => putIter(reqs.toVector, off, a, unit[F])
  }

  @tailrec private[this] def putIter(reqs: Vector[Request[P]], offset: Long, a: A, fa: F[Unit]): (S, F[Unit]) =
    reqs match {
      case Read(p) +: rest           => putIter(rest, offset + 1, a, fa *> complete(a, p))
      case Take(p) +: (next +: rest) => (Await(NEV(next, rest), offset + 1), fa *> complete(a, p))
      case Take(p) +: _              => (Contains(offset = offset + 1), fa *> complete(a, p))
      case empty @ _                 => (Contains(Vector(a), offset), fa)
    }

  override def read: F[A] = modifyF {
    case s @ Contains(a +: _, _)    => (s, a.pure[F])
    case s @ Contains(empty @ _, _) => (s, newPromise >>= retryRead)
    case s: Await[P]                => (s, await(s.reqs.head.promise))
  }

  // same as read but has promise to make other readers wait
  private[this] def retryRead(promise: P): F[A] = modifyF {
    case s @ Contains(a +: _, _) => (s, a.pure[F])
    case Contains(_, off)        => (Await(NEV.one(Read(promise)), off), await(promise))
    case s: Await[P]             => (s, await(s.reqs.head.promise))
  }

  // on take cancelation we will use our best effort
  // to convert corresponding request to Read,
  // so put will complete it and go forward
  override def take: F[A] = modifyF {
    case Contains(a +: rest, _)     => (Contains(rest), a.pure[F])
    case s @ Contains(empty @ _, _) => (s, newPromise >>= retryTake)
    case s @ Await(reqs, off)       =>
      reqs.last match {
        case Read(p) =>
          (Await(NEV.fromVectorUnsafe(reqs.init :+ Take(p)), off), awaitTake(p, off + reqs.length - 1))
        case Take(_) => (s, newPromise >>= retryTake)
      }
  }

  private[this] def awaitTake(promise: P, index: Long) = onCancel(await(promise), revertTake(index))

  private[this] def retryTake(promise: P): F[A] = modifyF {
    case Contains(a +: rest, _)      => (Contains(rest), a.pure[F])
    case Contains(empty @ _, offset) => (Await(NEV.one(Take(promise)), offset), awaitTake(promise, offset))
    case Await(reqs, off)            =>
      reqs.last match {
        case Read(p) => (Await(NEV.fromVectorUnsafe(reqs.init :+ Take(p)), off), awaitTake(p, off + reqs.length - 1))
        case Take(_) => (Await(reqs :+ Take(promise)), awaitTake(promise, off + reqs.length))
      }
  }

  private[this] def update(f: S => S): F[Unit] = modifyF(s => (f(s), unit[F]))

  private[this] def revertTake(index: Long): F[Unit] = update {
    case s @ Await(reqs, offset) if offset <= index || reqs.length + offset > index =>
      val k = (index - offset).toInt
      reqs.getUnsafe(k) match {
        case Take(p) => Await(reqs.updatedUnsafe(k, Read(p)), offset)
        case _       => s
      }
    case s                                                                          => s
  }
}

object QVarSM {
  def fromOption[A](opt: Option[A]): State[A, Nothing] = Contains(opt.toVector)

  sealed trait Request[+P] {
    def promise: P
  }
  final case class Read[+P](promise: P) extends Request[P]
  final case class Take[+P](promise: P) extends Request[P]

  private[QVarSM] val empty = Contains()

  sealed trait State[+A, +P]

  // there is some value
  case class Contains[+A](values: Vector[A] = Vector.empty, offset: Long = 0) extends State[A, Nothing]

  // there is no value and some readers and takers want the next value
  final case class Await[+P](reqs: NEV[Request[P]], offset: Long = 0) extends State[Nothing, P]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy