What is a Scala way to implement a callback like this?

New to Scala, and now I'm looking for a way to implement the following code on it:

@Override public void store(InputStream source, String destination, long size) { ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(size); final PutObjectRequest request = new PutObjectRequest( this.configuration.getBucket(), destination, source, metadata); new RetryableService(3) { @Override public void call() throws Exception { getClient().putObject(request); } }; } 

What would be the best way to implement the same functionality as RetryableService, but in Scala?

Basically, he calls the method a call N times, if all of them fail, then an exception occurs, if they succeed, he passes. This does not return anything, but then I have another version that allows me to return a value (so I have two classes in Java), and I believe that I can do with one class / function in Scala.

Any ideas?

EDIT

The current implementation in java is as follows:

 public abstract class RetryableService { private static final JobsLogger log = JobsLogger .getLogger(RetryableService.class); private int times; public RetryableService() { this(3); } public RetryableService(int times) { this.times = times; this.run(); } private void run() { RuntimeException lastExceptionParent = null; int x = 0; for (; x < this.times; x++) { try { this.call(); lastExceptionParent = null; break; } catch (Exception e) { lastExceptionParent = new RuntimeException(e); log.errorWithoutNotice( e, "Try %d caused exception %s", x, e.getMessage() ); try { Thread.sleep( 5000 ); } catch (InterruptedException e1) { log.errorWithoutNotice( e1, "Sleep inside try %d caused exception %s", x, e1.getMessage() ); } } } try { this.ensure(); } catch (Exception e) { log.error(e, "Failed while ensure inside RetryableService"); } if ( lastExceptionParent != null ) { throw new IllegalStateException( String.format( "Failed on try %d of %s", x, this ), lastExceptionParent); } } public void ensure() throws Exception { // blank implementation } public abstract void call() throws Exception; } 
+44
java design coding-style scala functional-programming
Oct 28 '11 at 2:45 a.m.
source share
13 answers

Recursion + first-class functions by-name == awesome parameters.

 def retry[T](n: Int)(fn: => T): T = { try { fn } catch { case e => if (n > 1) retry(n - 1)(fn) else throw e } } 

Usage looks like this:

 retry(3) { // insert code that may fail here } 

Edit : A little variation inspired by @themel's answer. Another line of code :-)

 def retry[T](n: Int)(fn: => T): T = { try { fn } catch { case e if n > 1 => retry(n - 1)(fn) } } 

Change again . The recursion bothered me by adding a few stack trace calls. For some reason, the compiler was unable to optimize tail recursion in the catch handler. However, tail recursion is not in the catch handler, but it only optimizes the penalty :-)

 @annotation.tailrec def retry[T](n: Int)(fn: => T): T = { val r = try { Some(fn) } catch { case e: Exception if n > 1 => None } r match { case Some(x) => x case None => retry(n - 1)(fn) } } 

Change again . Apparently, I'm going to make this a hobby in order to keep coming back and adding alternatives to this answer. Here's a tail recursive version, simpler than using Option , but using return to short-circuit the function is not an idiomatic Scala.

 @annotation.tailrec def retry[T](n: Int)(fn: => T): T = { try { return fn } catch { case e if n > 1 => // ignore } retry(n - 1)(fn) } 

Scala 2.10 update . Like my hobby, I periodically review this answer. Scala 2.10 as introduced by Try , which provides a clean way to implement repetition in a recursive way.

 // Returning T, throwing the exception on failure @annotation.tailrec def retry[T](n: Int)(fn: => T): T = { util.Try { fn } match { case util.Success(x) => x case _ if n > 1 => retry(n - 1)(fn) case util.Failure(e) => throw e } } // Returning a Try[T] wrapper @annotation.tailrec def retry[T](n: Int)(fn: => T): util.Try[T] = { util.Try { fn } match { case x: util.Success[T] => x case _ if n > 1 => retry(n - 1)(fn) case fn => fn } } 
+146
Oct 28 '11 at 3:32 a.m.
source share

There is a method in scalaz.concurrent.Task[T] : http://docs.typelevel.org/api/scalaz/nightly/#scalaz.concurrent.Task

 def retry(delays: Seq[Duration], p: (Throwable) ⇒ Boolean = _.isInstanceOf[Exception]): Task[T] 

When setting Task[T] you can create a new Task[T] , which will repeat a certain number of times, where the delay between retries is determined by the delays parameter. eg:.

 // Task.delay will lazily execute the supplied function when run val myTask: Task[String] = Task.delay(???) // Retry four times if myTask throws java.lang.Exception when run val retryTask: Task[String] = myTask.retry(Seq(20.millis, 50.millis, 100.millis, 5.seconds)) // Run the Task on the current thread to get the result val result: String = retryTask.run 
+6
Feb 15 '15 at 10:27
source share

Here is one possible implementation:

 def retry[T](times: Int)(fn: => T) = (1 to times).view flatMap (n => try Some(fn) catch {case e: Exception => None}) headOption 

You can use it as follows:

 retry(3) { getClient.putObject(request) } 

retry also returns Some[T] if the body was successfully processed, and None if the body only throws exceptions.




Update

If you want to flip the last exception, you can use a very similar approach, but use Either instead of Option :

 def retry[T](times: Int)(fn: => T) = { val tries = (1 to times).toStream map (n => try Left(fn) catch {case e: Exception => Right(e)}) tries find (_ isLeft) match { case Some(Left(result)) => result case _ => throw tries.reverse.head.right.get } } 

Also, as you can see, in the end, instead of having only the last exception, I have everything. Therefore, you can also wrap them in some AggregatingException if you want, and then throw them. (for simplicity, I just throw the last exception)

+5
Oct 28 '11 at 3:01 a.m.
source share

I would suggest this -

 def retry[T](n: Int)(code: => T) : T = { var res : Option[T] = None var left = n while(!res.isDefined) { left = left - 1 try { res = Some(code) } catch { case t: Throwable if left > 0 => } } res.get } 

He does:

 scala> retry(3) { println("foo"); } foo scala> retry(4) { throw new RuntimeException("nope"); } java.lang.RuntimeException: nope at $anonfun$1.apply(<console>:7) at $anonfun$1.apply(<console>:7) at .retry(<console>:11) at .<init>(<console>:7) at .<clinit>(<console>) at RequestResult$.<init>(<console>:9) at RequestResult$.<clinit>(<console>) at RequestResult$scala_repl_result(<console>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$17.apply(Interpreter.scala:988) at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$17.apply(Interpreter.... scala> var i = 0 ; i: Int = 0 scala> retry(3) { i = i + 1; if(i < 3) throw new RuntimeException("meh");} scala> i res3: Int = 3 

It can probably be improved to be more idiomatic Scala, but I am not a big fan of single-line, which requires the reader to know the entire standard library anyway.

+3
Oct 28 '11 at 3:35 a.m.
source share

You can express the idea in a functional style using scala.util.control.Exception :

 @annotation.tailrec def retry[T](n: Int)(fn: => T): T = Exception.allCatch.either(fn) match { case Right(v) => v; case Left(e) if (n <= 1) => throw e; case _ => retry(n - 1)(fn); } 

As we can see, tail recursion can be used here.

This approach gives you the added benefit that you can parameterize a catch container, so that you can only repeat a specific subset of the exceptions, add finalizers, etc. So the final version of retry might look like this:

 /** Retry on any exception, no finalizers. */ def retry[T](n: Int)(fn: => T): T = retry(Exception.allCatch[T], n)(fn); /** Parametrized retry. */ @annotation.tailrec def retry[T](theCatch: Exception.Catch[T], n: Int)(fn: => T): T = theCatch.either(fn) match { case Right(v) => v; case Left(e) if (n <= 1) => throw e; case _ => retry(theCatch, n - 1)(fn); } 

With this, you can do complex things like:

 retry(Exception.allCatch andFinally { print("Finished.") }, 3) { // your scode } 
+3
Jan 16 '13 at
source share

There is an existing library that can help with this, retry , and there is also a Java library called guava-retrying .

Here are some examples of using retry :

 // retry 4 times val future = retry.Directly(4) { () => doSomething } // retry 3 times pausing 30 seconds in between attempts val future = retry.Pause(3, 30.seconds) { () => doSomething } // retry 4 times with a delay of 1 second which will be multipled // by 2 on every attempt val future = retry.Backoff(4, 1.second) { () => doSomething } 
+3
Aug 14 '15 at 10:41
source share

I like the decision made, but I suggest checking the exception: NonFatal:

 // Returning T, throwing the exception on failure @annotation.tailrec def retry[T](n: Int)(fn: => T): T = { Try { fn } match { case Success(x) => x case _ if n > 1 && NonFatal(e) => retry(n - 1)(fn) case Failure(e) => throw e } } 

You do not want to repeat the control flow exception, and usually not for thread interrupts ...

+2
Mar 13 '15 at 3:35
source share

If you want to control which exceptions you throw, you can use the methods in scala.util.control.Exception :

 import java.io._ import scala.util.control.Exception._ def ioretry[T](n: Int)(t: => T) = ( Iterator.fill(n){ failing[T](classOf[IOException]){ Option(t) } } ++ Iterator(Some(t)) ).dropWhile(_.isEmpty).next.get 

(As written, it will also repeat to null, and part of Option(t) . If you want nulls to be returned, use Some(t) inside the iterator.)

Try this with

 class IoEx(var n: Int) { def get = if (n>0) { n -= 1; throw new IOException } else 5 } val ix = new IoEx(3) 

Does he work?

 scala> ioretry(4) { ix.get } res0: Int = 5 scala> ix.n = 3 scala> ioretry(2) { ix.get } java.io.IOException at IoEx.get(<console>:20) ... scala> ioretry(4) { throw new Exception } java.lang.Exception at $anonfun$1.apply(<console>:21) ... 

Looks good!

+1
Oct 28 '11 at 18:32
source share

I ended up adapting the previous answer to allow filtering on which exceptions to try again:

  /** * Attempt 'fn' up to 'attempts' times, retrying only if 'forExceptions' returns true for retry-able exceptions. */ def retry[T](attempts: Int, forExceptions: (Throwable) => Boolean)(fn: => T): T = { // toStream creates a lazily evaluated list, which we map to a try/catch block resulting in an Either val tries = (1 to attempts).toStream map { n => try Left(fn) catch { case e if forExceptions(e) => Right(e) } } // find the first 'Either' where left is defined and return that, or if not found, return last // exception thrown (stored as 'right'). The cool thing is that because of lazy evaluation, 'fn' is only // evaluated until it success (eg, until Left is found) tries find (_ isLeft) match { case Some(Left(result)) => result case _ => throw tries.reverse.head.right.get } } 

You can call in two ways:

 val result = retry(4, _.isInstanceOf[SomeBadException]) { boom.doit() } 

or with partial functions (also showing a version where they don't care about the return value)

  def pf: PartialFunction[Throwable, Boolean] = { case x: SomeOtherException => true case _ => false } retry(4, pf) { boom.doit() } 
+1
Mar 08 2018-12-12T00:
source share

This project seems to provide some good implementations for different retry mechanisms https://github.com/hipjim/scala-retry

 // define the retry strategy implicit val retryStrategy = RetryStrategy.fixedBackOff(retryDuration = 1.seconds, maxAttempts = 2) // pattern match the result val r = Retry(1 / 1) match { case Success(x) => x case Failure(t) => log("I got 99 problems but you won't be one", t) } 
0
Jul 05 '14 at 10:30
source share
 //Here is one using Play framework def retry[T](times:Int)(block: => Future[T])(implicit ctx: ExecutionContext):Future[T] = { type V = Either[Throwable,T] val i:Iterator[Future[Option[V]]] = Iterator.continually(block.map(t => Right(t)).recover { case e => Left(e) }.map(t => Some(t))) def _retry:Iteratee[V,V] = { def step(ctr:Int)(i:Input[V]):Iteratee[V,V] = i match { case Input.El(e) if (e.isRight) => Done(e,Input.EOF) case _ if (ctr < times) => Cont[V,V](i => step(ctr + 1)(i)) case Input.El(e) => Done(e,Input.EOF) } Cont[V,V](i => step(0)(i)) } Enumerator.generateM(i.next).run(_retry).flatMap { _ match { case Right(t) => future(t) case Left(e) => Future.failed(e) }} } 
0
Dec 28 '14 at 2:04 on
source share

This solution is not optimized by the compiler for tail recursion for any reason (who knows why?), But in the case of rare repetitions there will be an option:

 def retry[T](n: Int)(f: => T): T = { Try { f } recover { case _ if n > 1 => retry(n - 1)(f) } get } 

Using:

 val words: String = retry(3) { whatDoesTheFoxSay() } 

The end of the answer. Stop reading here




Version with the result in the form of Try:

 def reTry[T](n: Int)(f: => T): Try[T] = { Try { f } recoverWith { case _ if n > 1 => reTry(n - 1)(f) } } 

Using:

 // previous usage section will be identical to: val words: String = reTry(3) { whatDoesTheFoxSay() } get // Try as a result: val words: Try[String] = reTry(3) { whatDoesTheFoxSay() } 

Version with function returning Try

 def retry[T](n: Int)(f: => Try[T]): Try[T] = { f recoverWith { case _ if n > 1 => reTry(n - 1)(f) } } 

Using:

 // the first usage section will be identical to: val words: String = retry(3) { Try(whatDoesTheFoxSay()) } get // if your function returns Try: def tryAskingFox(): Try = Failure(new IllegalStateException) val words: Try[String] = retry(3) { tryAskingFox() } 
0
Apr 12 '16 at 20:30
source share

Reusable object / method with a pause between attempts:

 Retry(3, 2 seconds) { /* some code */ } 

the code:

 object Retry { def apply[A](times: Int, pause: Duration)(code: ⇒ A): A = { var result: Option[A] = None var remaining = times while (remaining > 0) { remaining -= 1 try { result = Some(code) remaining = 0 } catch { case _ if remaining > 0 ⇒ Thread.sleep(pause.toMillis) } } result.get } } 
0
Oct 21 '16 at 6:11
source share



All Articles