0

Monix observables have the apis .onErrorRestartIf(f: Throwable => Boolean) and .onErrorRestart(times: Int). How to specify the maximum amount of times it should retry doing .onErrorRestartIf?

JVS
  • 521
  • 6
  • 18

2 Answers2

3

You can build your own loop, based on onErrorHandleWith:

def retryLimited[A](fa: Observable[A], maxRetries: Int)
  (p: Throwable => Boolean): Observable[A] = {

  // If we have no retries left, return the source untouched
  if (maxRetries <= 0) fa else
    fa.onErrorHandleWith { err =>
      // If predicate holds, do recursive call
      if (p(err)) 
        retryLimited(fa, maxRetries - 1)(p)
      else 
        Observable.raiseError(err)
    }
}

If you don't like simple functions (I do), you can always expose some extension methods as an alternative:

implicit class ObservableExtensions[A](val self: Observable[A]) 
  extends AnyVal {

  def onErrorRetryLimited(maxRetries: Int)
    (p: Throwable => Boolean): Observable[A] = 
    retryLimited(self, maxRetries)(p)
}

Note the answer by @JVS is OK in spirit, but can be problematic because it keeps shared mutable state, which isn't OK for cold observables. So notice what happens if you do something like this:

val source = Observable.suspend { 
  if (Random.nextInt() % 10 != 0)
    Observable.raiseError(new RuntimeException("dummy"))
  else
    Observable(1, 2, 3)
} 

val listT = source
  .onErrorRestartIf(limitedRetries(AtomicInt(maxRetries), shouldRestart))
  .toListL

listT.runAsync // OK
listT.runAsync // Ooops, shared state, we might not have retries left

Be wary of mutable shared state in Observable's operators. You can work like this of course, but you have to be aware of the danger within :-)

Alexandru Nedelcu
  • 8,061
  • 2
  • 34
  • 39
0

Warning: This uses shared mutable state and can be incorrect for cold observables. See Alexandru's answer.

Define a function to do it:

def limitedRetries(maxRetries: AtomicInt, shouldRetryOnException: Throwable => Boolean): Throwable => Boolean = 
  ex => maxRetries.decrementAndGet() > 0 && shouldRetryOnException(ex)

And use this function in onErrorRestartIf

.onErrorRestartIf(limitedRetries(AtomicInt(maxRetries), shouldRestart))

FYI, used monix AtomicInt here...

import monix.execution.atomic.AtomicInt
JVS
  • 521
  • 6
  • 18