dev.profunktor.redis4cats.runner.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2018-2021 ProfunKtor
*
* 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 dev.profunktor.redis4cats
import java.util.UUID
import scala.annotation.nowarn
import scala.concurrent.duration._
import cats.effect.kernel._
import cats.effect.kernel.implicits._
import cats.syntax.all._
import dev.profunktor.redis4cats.effect.Log
import dev.profunktor.redis4cats.hlist._
import cats.Applicative
object Runner {
type CancelFibers[F[_]] = Throwable => F[Unit]
case class Ops[F[_]](
name: String,
mainCmd: F[Unit],
onComplete: CancelFibers[F] => F[Unit],
onError: F[Unit],
afterCompletion: F[Unit],
mkError: () => Throwable
)
def apply[F[_]: Async: Log]: RunnerPartiallyApplied[F] =
new RunnerPartiallyApplied[F]
}
private[redis4cats] class RunnerPartiallyApplied[F[_]: Async: Log] {
def filterExec[T <: HList](ops: Runner.Ops[F])(commands: T)(implicit w: WitnessFilter[T]): F[w.S] = {
import w._
exec[T](ops)(commands).map(_.filterUnit)
}
def exec[T <: HList](ops: Runner.Ops[F])(commands: T)(implicit w: Witness[T]): F[w.R] =
(Deferred[F, Either[Throwable, w.R]], Sync[F].delay(UUID.randomUUID)).tupled.flatMap {
case (promise, uuid) =>
def cancelFibers[A](fibs: HList)(err: Throwable): F[Unit] =
joinOrCancel(fibs, HNil)(false) >> promise.complete(err.asLeft).ensure(promiseAlreadyCompleted)(identity).void
def onErrorOrCancelation(fibs: HList): F[Unit] =
cancelFibers(fibs)(ops.mkError()).guarantee(ops.onError)
(Deferred[F, Unit], Ref.of[F, Int](0)).tupled
.flatMap {
case (gate, counter) =>
// wait for commands to be scheduled
val synchronizer: F[Unit] =
counter.modify {
case n if n === (commands.size - 1) =>
n + 1 -> gate.complete(()).ensure(promiseAlreadyCompleted)(identity).void
case n => n + 1 -> Applicative[F].unit
}.flatten
Log[F].debug(s"${ops.name} started - ID: $uuid") >>
(ops.mainCmd >> runner(synchronizer, commands, HNil))
.bracketCase(_ => gate.get) {
case (fibs, Outcome.Succeeded(_)) =>
for {
_ <- Log[F].debug(s"${ops.name} completed - ID: $uuid")
_ <- ops.onComplete(cancelFibers(fibs))
r <- joinOrCancel(fibs, HNil)(true)
// Casting here is fine since we have a `Witness` that proves this true
_ <- promise.complete(r.asInstanceOf[w.R].asRight).ensure(promiseAlreadyCompleted)(identity)
} yield ()
case (fibs, Outcome.Errored(e)) =>
Log[F].error(s"${ops.name} failed: ${e.getMessage} - ID: $uuid") >> onErrorOrCancelation(fibs)
case (fibs, Outcome.Canceled()) =>
Log[F].error(s"${ops.name} canceled - ID: $uuid") >> onErrorOrCancelation(fibs)
}
.guarantee(ops.afterCompletion) >> promise.get.rethrow.timeout(3.seconds)
}
}
// Forks every command in order
@nowarn("cat=other-match-analysis")
private def runner[H <: HList, G <: HList](f: F[Unit], ys: H, res: G): F[HList] =
ys match {
case HNil => res.pure[F].widen
case HCons((h: F[_] @unchecked), t) => (h, f).parTupled.map(_._1).start.flatMap(fb => runner(f, t, fb :: res))
}
// Joins or cancel fibers correspondent to previous executed commands
private def joinOrCancel[H <: HList, G <: HList](ys: H, res: G)(isJoin: Boolean): F[HList] =
ys match {
case HNil => Applicative[F].pure(res)
case HCons((h: Fiber[F, Throwable, Any] @unchecked), t) if isJoin =>
h.joinWithNever.flatMap(x => joinOrCancel(t, x :: res)(isJoin))
case HCons((h: Fiber[F, Throwable, Any] @unchecked), t) =>
h.cancel.flatMap(x => joinOrCancel(t, x :: res)(isJoin))
case HCons(h, t) =>
Log[F].error(s"Unexpected result: ${h.toString}") >> joinOrCancel(t, res)(isJoin)
}
private val promiseAlreadyCompleted = new AssertionError("Promise already completed")
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy