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

io.github.jchapuis.leases4s.HeldLease.scala Maven / Gradle / Ivy

The newest version!
package io.github.jchapuis.leases4s

import cats.effect.Concurrent
import cats.effect.kernel.Outcome
import cats.syntax.applicative.*
import cats.syntax.applicativeError.*

trait HeldLease[F[_]] extends Lease[F] {
  implicit def F: Concurrent[F]

  /** Runs the given action guarded by the lease: this implements a distributed critical section.
    *   - if the lease is still held after completion of the action, the outcome is `Outcome.Succeeded`
    *   - if the lease is already expired or expires in the meantime, the action is cancelled and the outcome is
    *     `Outcome.Canceled`
    *   - if the action fails, the outcome is `Outcome.Errored`
    *
    * @note
    *   The lease is auto-renewed in the background, so in nominal conditions there is no reason for it to expire during
    *   the action. However, in case of loss of connectivity to the lease issuer or some other issue preventing renewal
    *   of the lease, it may expire before the action completes. In such cases, the action is cancelled and the outcome
    *   is `Outcome.Canceled`.
    * @note
    *   This does not per-se ensure transactional semantics, as cancellation is best effort. In other words, if we fail
    *   to renew the lease and it gets revoked while running the action, cancellation or rollback must run before the
    *   lease expires. Otherwise, it is possible that another node acquires the lease upon expiry and enters the
    *   critical section while we are still trying to exit. If strict transactional semantics are required, the action
    *   should use some additional form of strong consistency like version control or idempotent operations.
    * @param fa
    *   action to run while holding the lease
    * @tparam A
    *   action result type
    * @return
    *   outcome of the action
    */
  def guard[A](fa: F[A]): F[Outcome[F, Throwable, A]] =
    fs2.Stream
      .eval(fa)
      .map(Option(_))
      .mergeHaltBoth(expired.map(_ => None))
      .map {
        case Some(a) => Outcome.succeeded[F, Throwable, A](a.pure)
        case None    => Outcome.canceled[F, Throwable, A]
      }
      .compile
      .lastOrError
      .handleError(Outcome.errored[F, Throwable, A])
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy