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.
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.
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. |
|
Is there some plan to release this? I would love to have also too |
|
The current implementation has some flaws. I will experiment with different API encoding this month. |
|
In https://github.com/biochimia/scala-retry, I've toyed with an approach where the retry policy ( In that approach, the error conditions on which to retry are managed separately in a The actual implementation of the retry loop will be specific to the monad. In the project above, I added only an implementation for I don't intend to derail the current PR, but wanted to explore a bit the interface possibilities. (Is there a better place for this discussion outside the current PR?) |
The PR is influenced by #3135.
In my opinion,
Retrymust carry the error type. That way, we can implement retry functionality on top of theHandlefrom thecats-mtl.I also decided to keep the
name(perhaps the 'description' fits better?) around. That way,toStringprovides 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
TimeoutExceptionA 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?