-
Notifications
You must be signed in to change notification settings - Fork 529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Retry
functionality
#4109
base: series/3.x
Are you sure you want to change the base?
Conversation
de14d85
to
c7abc4e
Compare
private def doRetry[F[_], A, E]( | ||
retry: Retry[F, E], | ||
onRetry: Option[(Status, E, Decision) => F[Unit]] | ||
)(fa: F[A])(implicit F: GenTemporal[F, E]): F[A] = { | ||
def onError(status: Status, error: E): F[Either[Status, A]] = | ||
retry | ||
.decide(status, error) | ||
.flatTap(decision => onRetry.traverse_(_(status, error, decision))) | ||
.flatMap { | ||
case retry: Decision.Retry => | ||
F.delayBy(F.pure(Left(status.withRetry(retry.delay))), retry.delay) | ||
case _: Decision.GiveUp => | ||
F.raiseError(error) | ||
} | ||
|
||
F.tailRecM(Status.initial) { status => | ||
fa.redeemWith(error => onError(status, error), success => F.pure(Right(success))) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The evaluation of the retry strategy.
3cbaeff
to
8af8301
Compare
"retry only on matching errors" in ticked { implicit ticker => | ||
type F[A] = EitherT[IO, Errors, A] | ||
|
||
val maxRetries = 2 | ||
val delay = 1.second | ||
|
||
val error = new Error1 | ||
val policy = Retry | ||
.constantDelay[F, Errors](delay) | ||
.withMaxRetries(maxRetries) | ||
.withErrorMatcher(Retry.ErrorMatcher[F, Errors].only[Error1]) | ||
|
||
val expected: List[RetryAttempt] = List( | ||
(Status(0, Duration.Zero), Decision.retry(delay), error), | ||
(Status(1, 1.second), Decision.retry(delay), error), | ||
(Status(2, 2.seconds), Decision.giveUp, error) | ||
) | ||
|
||
val io: F[Unit] = Handle[F, Errors].raise[Errors, Unit](error) | ||
|
||
val run = | ||
for { | ||
ref <- IO.ref(List.empty[RetryAttempt]) | ||
result <- mtlRetry[F, Errors, Unit]( | ||
io, | ||
policy, | ||
(s, e: Errors, d) => EitherT.liftF(ref.update(_ :+ (s, d, e))) | ||
).value | ||
attempts <- ref.get | ||
} yield (result, attempts) | ||
|
||
run must completeAs((Left(error), expected)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retry works with MTL too.
e60f16c
to
7f25d62
Compare
I'd really love for this to land 🚀 - but I see there's been no comments for 4 months - @iRevive is this waiting on feedback or approvals, or is discussion ongoing somewhere off Github? |
I'm still waiting for the feedback. |
The PR is influenced by #3135.
In my opinion,
Retry
must carry the error type. That way, we can implement retry functionality on top of theHandle
from thecats-mtl
.I also decided to keep the
name
(perhaps the 'description' fits better?) around. That way,toString
provides enough details to understand the retry strategy:Usage
Retry on all errors
Retry on some errors (e.g.
TimeoutException
)Retry on all errors except the
TimeoutException
A few points to discuss:
withMaxDelay
,withCappedDelay
,withMaxCumulativeDelay
. Even though I provided the documentation with examples, these three methods are confusing. Can we find better names?