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

io.chrisdavenport.rediculous.concurrent.RedisCyclicBarrier.scala Maven / Gradle / Ivy

package io.chrisdavenport.rediculous.concurrent

import cats._
import cats.syntax.all._
import cats.effect._
import cats.effect.syntax.all._
import io.circe._
import io.circe.syntax._
import io.chrisdavenport.rediculous._
import io.chrisdavenport.rediculous.RedisCtx.syntax.all._
import scala.concurrent.duration._
import cats.effect.std.UUIDGen
import java.util.UUID

/**
 * A synchronization abstraction that allows a set of fibers
 * to wait until they all reach a certain point.
 *
 * A cyclic barrier is initialized with a positive integer capacity n and
 * a fiber waits by calling [[await]], at which point it is semantically
 * blocked until a total of n fibers are blocked on the same cyclic barrier.
 *
 * At this point all the fibers are unblocked and the cyclic barrier is reset,
 * allowing it to be used again.
 */
trait CyclicBarrier[F[_]]{ self => 
  /**
   * Possibly semantically block until the cyclic barrier is full
   */
  def await: F[Unit]

  def mapK[G[_]](f: F ~> G): CyclicBarrier[G] =
    new CyclicBarrier[G] {
      def await: G[Unit] = f(self.await)
    }
}

object RedisCyclicBarrier {

  def create[F[_]: Async: UUIDGen](
    redisConnection: RedisConnection[F],
    key: String,
    capacity: Int,
    acquireTimeout: FiniteDuration,
    lockTimeout: FiniteDuration,
    pollingInterval: FiniteDuration,
    deferredLifetime: FiniteDuration, // Should be a long time
    setOpts: RedisCommands.SetOpts
  ): CyclicBarrier[F] = 
    new RedisCyclicBarrier[F](redisConnection, key, capacity, acquireTimeout, lockTimeout, pollingInterval, deferredLifetime, setOpts)


  case class State(awaiting: Int, epoch: Long, currentDeferredLocation: String)


  private class RedisCyclicBarrier[F[_]: Async: UUIDGen](
    redisConnection: RedisConnection[F],
    key: String,
    capacity: Int,
    acquireTimeout: FiniteDuration,
    lockTimeout: FiniteDuration,
    pollingInterval: FiniteDuration,
    deferredLifetime: FiniteDuration,
    setOpts: RedisCommands.SetOpts
  ) extends CyclicBarrier[F]{
    val ref = RedisRef.optionJsonRef[F, State](RedisRef.lockedOptionRef(redisConnection, key, acquireTimeout, lockTimeout, setOpts))

    def keyLocation(uuid: UUID): String = key ++ ":lock:" ++ uuid.toString()

    def deferredAtLocation(location: String): Deferred[F, String] = RedisDeferred.fromKey(
      redisConnection,
      location,
      pollingInterval, 
      deferredLifetime
    )

    def await: F[Unit] = UUIDGen[F].randomUUID.flatMap{ gate => 
      ref.modify{
        case Some(State(awaiting, epoch, location)) => 
          val awaitingNow = awaiting - 1
          if (awaitingNow == 0)
            Some(State(capacity, epoch + 1, keyLocation(gate))) -> deferredAtLocation(location).complete(key).void
          else {
            val newState = State(awaitingNow, epoch, location)
            // reincrement count if this await gets canceled,
            // but only if the barrier hasn't reset in the meantime
            val cleanup = ref.update { 
              case Some(s) =>
                if (s.epoch == epoch) Some(s.copy(awaiting = s.awaiting + 1))
                else Some(s)
              case None => None // Shouldn't end up here
            }

            Some(newState) -> deferredAtLocation(location).get.void.guaranteeCase{
              case Outcome.Canceled() => cleanup
              case _ => Applicative[F].unit
            }
          }
        case None =>
          (State(capacity, 0, keyLocation(gate)).some, await)
      }.flatten
    }
  }




  implicit val encoder: Encoder[State] = Encoder.instance[State]{
    case State(latches, epoch, location) => Json.obj(
        "latches" -> latches.asJson,
        "epoch" -> epoch.asJson,
        "location" -> location.asJson
    )
  }

  implicit val decoder: Decoder[State] = new Decoder[State]{ 
    def apply(h: HCursor): Decoder.Result[State] =  {
      for {
        latches <- h.downField("latches").as[Int]
        epoch <- h.downField("epoch").as[Long]
        location <- h.downField("location").as[String]
      } yield State(latches, epoch, location)
    }
  }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy