Skip to content

Instantly share code, notes, and snippets.

@corenti13711539
Last active July 23, 2019 21:11
Show Gist options
  • Save corenti13711539/df7482dc7bdce3a1e371fad02c9a7791 to your computer and use it in GitHub Desktop.
Save corenti13711539/df7482dc7bdce3a1e371fad02c9a7791 to your computer and use it in GitHub Desktop.
object Cached {
def create[F[_]: Concurrent, A](fa: F[A], ttl: FiniteDuration)(implicit t: Timer[F]): F[Cached[F, A]] = {
sealed trait State
case class Value(v: A, ts: Long) extends State
case class Updating(d: Deferred[F, Either[Throwable, A]]) extends State
case object NoValue extends State
def currentTimestamp = t.clock.monotonic(ttl.unit)
Ref.of[F, State](NoValue).map { state =>
new Cached[F, A] {
def get: F[A] =
(for {
newValue <- Deferred[F, Either[Throwable, A]]
now <- currentTimestamp
res <- state.modify {
case st@Value(v, ts) if (ts + ttl.length) < now => st -> v.pure[F]
case st@Updating(inFlightValue) => st -> inFlightValue.get.rethrow
case NoValue | Value(_, _) => Updating(newValue) -> fetch(newValue).rethrow
}
} yield res).flatten
def fetch(d: Deferred[F, Either[Throwable, A]]) = {
for {
r <- fa.attempt
now <- currentTimestamp
_ <- state.set {
r match {
case Left(_) => NoValue
case Right(v) => Value(v, now)
}
}
_ <- d.complete(r)
} yield r
}.guaranteeCase {
case ExitCase.Completed => ().pure[F]
case ExitCase.Error(_) => ().pure[F]
case ExitCase.Canceled => state.modify {
case st @ Value(v, _) => st -> d.complete(v.asRight).attempt.void
case NoValue | Updating(_) =>
val appropriateError = new Exception("Couldn't retrieve")
NoValue -> d.complete(appropriateError.asLeft).attempt.void
}.flatten
}
def expire: F[Unit] = state.update {
case Value(_, _) => NoValue
case NoValue => NoValue
case st @ Updating(_) => st
}
}
}
}
}
@tyoras
Copy link

tyoras commented Jul 23, 2019

Great piece of code @corenti13711539 !
I'd like to suggest a small change for the expiration check: revert the guard clause from if (ts + ttl.length) < now to if now < (ts + ttl.length) on L17, it would allow to check that now is before the expiration time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment