What's the Scala way to implement a retry-able call like this one?

前端 未结 14 1699
甜味超标
甜味超标 2020-11-29 16:08

Still the newbie in Scala and I\'m now looking for a way to implement the following code on it:

@Override
public void store(InputStream source, String destin         


        
相关标签:
14条回答
  • 2020-11-29 16:44

    A reusable object/method with a pause between attempts:

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

    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 讨论(0)
  • 2020-11-29 16:45

    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]
    

    Given a Task[T], you can create a new Task[T] which will retry a certain number of times, where the delay between retries is defined by the delays parameter. e.g.:

    // 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
    
    0 讨论(0)
  • 2020-11-29 16:46
    //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 讨论(0)
  • 2020-11-29 16:48

    I ended up adapting a previous answer to allow filtering on which exceptions to retry on:

      /**
       * 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 (e.g., 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 version where don't care about return value)

        def pf: PartialFunction[Throwable, Boolean] =
        {
          case x: SomeOtherException => true
          case _ => false
        }
    
       retry(4, pf)
       {
          boom.doit()
       }
    
    0 讨论(0)
  • 2020-11-29 16:52

    I'd 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
    } 
    

    It 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 one-liners that require the reader to know the entire standard library by heart anyways.

    0 讨论(0)
  • 2020-11-29 16:53

    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 like this:

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

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


    Update

    If you want to bobble up last exception, then you can take 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, at the end, instead of having only last exception, I have them all. So you can also wrap them in some AggregatingException if you want and then throw it. (for simplicity, I just throw last exception)

    0 讨论(0)
提交回复
热议问题