Што шлях Scala для ажыццяўлення паўторнага выканання выкліку, як гэты?

Яшчэ пачатковец у Scala, і я ў цяперашні час шукае спосаб рэалізацыі наступнага кода на ім:

@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);
        }
    };

}

Што было б лепшым спосабам рэалізаваць той жа funcionality, што RetryableService прылады, але ў Scala?

Гэта ў асноўным выклікае <моцны> выклік метад N раз, калі ўсе з іх не выключэнне затым падымаецца, калі ім удасца гэта ідзе далей. Гэта адзін не вяртае нічога, але тады ў мяне ёсць іншая версія, якая дазваляе вяртаць значэнне (так, у мяне ёсць два класа ў Java), і я мяркую, што я мог бы зрабіць з дапамогай аднаго класа/функцыі ў Scala.

Любыя ідэі?

<Моцны> Змяніць

Бягучая рэалізацыя ў Java выглядае наступным чынам:

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;

}
41

13 адказы

Рэкурсія + <Страйк> Першых функцыі класа па-імя параметр == дзіўны.

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

Выкарыстанне, як гэта:

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

Edit: slight variation inspired by @themel's answer. One fewer line of code :-)

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

Edit Again: The recursion bothered me in that it added several calls to the stack trace. For some reason, the compiler couldn't optimize tail recursion in the catch handler. Tail recursion not in the catch handler, though, optimizes just fine :-)

@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)
  }
}

Edit yet again: Apparently I'm going to make it a hobby to keep coming back and adding alternatives to this answer. Here's a tail-recursive version that's a bit more straightforward than using Option, but using return to short-circuit a function isn't 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. As is my hobby, I revisit this answer occasionally. Scala 2.10 as introduced Try, which provides a clean way of implementing retry in a tail-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
  }
}
139
дададзена
Чаму scala.util.control.Exception.catching (classOf [E1], classOf [E2 & ZWNJ;]) тыпу Выгада [Nothing] ? напрыклад <Код> Exception.catching (classOf [PSQLException]) становіцца тып : Злавіць [Nothing] . Ці з'яўляецца ут павінен быць такім чынам? Я не ведаю, чаму яны дадалі тып-параметрызацыі да яе, калі яна заўсёды будзе Nothing у любым выпадку ...? : /
дададзена аўтар kornfridge, крыніца
Гэта прыгажосць.
дададзена аўтар Maurício Linhares, крыніца
Ці ёсць спосаб, каб зрабіць tailrec працуе з recoverWith так што я толькі павінна паўтарыць на пэўны выключэнні?
дададзена аўтар Minh Thai, крыніца
На самай справе, па імі параметраў, хоць я разумею што можа быць нешта няёмкае адбываецца пры праходжанні па імі параметру рэкурсіўна.
дададзена аўтар Daniel C. Sobral, крыніца
<Код> @ annotation.tailrec Абарону паўтарыць [T] (п: Int) (п: => T): Паспрабуйце [T] = {Паспрабуйце {Fn} {запалку выпадак х: Поспех => х выпадку памылкі [T] ( нефатального (_)), калі п> 1 => паўторных спробаў (п - 1) (п) выпадку F => F}}
дададзена аўтар piotr, крыніца
@ MaurícioLinhares Дзякуючы :-D
дададзена аўтар leedm777, крыніца
@IshankGulati Змена рэгістра паўторных спробаў у сказе злавіць, каб паказаць выключэння вы хочаце паўтарыць на. Як выпадак util.Failure (е: RuntimeException), калі п> 1 => паўторных спробаў (п - 1) (п) у Scala 2.10. Напрыклад,
дададзена аўтар leedm777, крыніца
@fricadelle Я думаю, проста выклік Thread.sleep (waitMillis) перад паўторыце (п - 1) (п) будзе рабіць трук.
дададзена аўтар leedm777, крыніца
Патрэбы нефатального чэк
дададзена аўтар smartnut007, крыніца
Я прачытаў усе вашыя рашэнні, і вы маглі сапраўды заўважыць, як кожны паляпшае папярэдні. Таму Scala з'яўляецца каханне. Гэта тое, што робіць вялікі Scala. Прыгожая. Я адаптуючы апошнюю і дадаючы нарастальны таймаўт паміж кожнай паўтарыць спробу. Я буду старацца, каб захаваць прыгажосць як мага больш.
дададзена аўтар Rafael Saraiva, крыніца
Я проста паказаць, што часам вам трэба будзе паўтарыць толькі на некаторых выключэннях, а непасрэдна кідаць іншых. Вы можаце выкарыстоўваць scala.util.control.Exception.catching (classOf [E1], classOf [E2 & ZWNJ;]). WithTry (п) супадаюць ...
дададзена аўтар Cristian Vrabie, крыніца
Калі ў вас ёсць звычка браць @ Dave зірнуць на scala.util.Try аб'екта! Вялікія адказы!
дададзена аўтар tysonjh, крыніца
Як наконт трохі меншых ліній :) @ annotation.tailrec Абарону паўтарыць [T] (п: Int) (п: => T): Паспрабуйце [T] = Паспрабуйце (п) супадаюць {выпадак х: util.Success [T] => х выпадак, _, калі п> 1 => паўторных спробаў (п - 1) (п) выпадку F => F}
дададзена аўтар Gaurav Abbi, крыніца
Ці можа гэта быць зменена, каб паўтарыць толькі за некаторыя выключэння?
дададзена аўтар Ishank Gulati, крыніца
@ Leedm777, але затым паўтарыце функцыя будзе спецыфічным для канкрэтнага запыту. Я думаў аб праходжанні спісу vararg выключэнняў ў метадзе.
дададзена аўтар Ishank Gulati, крыніца
Як гэта можа быць палепшана, каб дадаць час чакання паміж кожнай паўтарыць спробу?
дададзена аўтар fricadelle, крыніца
адно пытанне тут. Як змяніць яго, калі мая функцыя вяртае будучыню?
дададзена аўтар asdasdsdf, крыніца

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]

Улічваючы Задача [T] , вы можаце стварыць новы Task [T] , які будзе паўтараць пэўную колькасць раз, калі затрымка паміж спробамі вызначаецца <код > затрымкі параметр /. напрыклад.:

// 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
дададзена

Вось адна з магчымых рэалізацый:

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

Вы можаце выкарыстоўваць яго як гэта:

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

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


абнаўленне

Калі вы хочаце, каб пэндзліка да апошняга выключэння, то вы можаце прыняць вельмі падобны падыход, але выкарыстаць Альбо замест 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
    }
}

Акрамя таго, як вы можаце бачыць, у рэшце рэшт, замест таго, каб толькі апошняе выключэнне, у мяне ёсць усё. Такім чынам, вы можаце таксама абгарнуць іх у нейкі AggregatingException , калі вы хочаце, і затым кінуць яго. (Для прастаты, я проста кінуць апошняе выключэнне)

5
дададзена
Дзякуючы @thenshi :)
дададзена аўтар Maurício Linhares, крыніца
Калі ён не ўвесь час выключэнне сусло бурбалка, я буду дадаваць поўнае ажыццяўленне ў гэтым пытанні.
дададзена аўтар Maurício Linhares, крыніца
Звярніце ўвагу, што гэта, верагодна, не тое, што ОП мае намер Re: пабочныя эфекты - паўторы (3) {Println ( «Foo»)} надрукуе тры радкі.
дададзена аўтар themel, крыніца
Спрабавалі Ці вы? Гэта не для мяне ў Scala 2.8.1.
дададзена аўтар themel, крыніца
@tenshi: Так, я магу прайграць - працуе ў 2.9.1, але не ў 2.8.1. Я не чакаў, што.
дададзена аўтар themel, крыніца
@ MaurícioLinhares: Я абнавіў свой адказ
дададзена аўтар tenshi, крыніца
@themel: З майго рэалізацыі , паўтарыць (3) {Println ( "Foo")} будзе друкаваць толькі адзін раз
дададзена аўтар tenshi, крыніца
@themel: Я выкарыстоўваю 2.9.1.final і друкуе Foo толькі адзін раз
дададзена аўтар tenshi, крыніца
Вялікі адказ - трэба даведацца пра .view() і .toStream() і, нарэшце, зразумець, чаму Варыянт быўшы фактычна з'яўляецца Traverable/Iterable з'яўляецца гэтак магутным.
дададзена аўтар Doug Donohoe, крыніца

Існуе якая існуе бібліятэка, якая можа дапамагчы з гэтым, называецца паўторных спробаў , і ёсць бібліятэка Java таксама называецца гуавы- паўтарыць спробу.

Вось некаторыя прыклады выкарыстання паўторных спробаў :

// 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
дададзена

Вы можаце выказаць ідэю ў функцыянальным стылі, выкарыстоўваючы

Я б выказаць здагадку, што гэта -

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
} 

Гэта робіць:

scala> retry(3) { println("foo"); }
foo

scala> retry(4) { throw new RuntimeException("nope"); }
java.lang.RuntimeException: nope
        at $anonfun$1.apply(:7)
        at $anonfun$1.apply(:7)
        at .retry(:11)
        at .(:7)
        at .()
        at RequestResult$.(:9)
        at RequestResult$.()
        at RequestResult$scala_repl_result()
        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

Гэта, верагодна, можа быць палепшана, каб быць больш ідыёматычны Scala, але я не вялікі прыхільнік жартаў, якія патрабуюць чытача, каб ведаць усю стандартную бібліятэку на памяць у любым выпадку.

3
дададзена
Гэта выдатнае рашэнне таксама.
дададзена аўтар Maurício Linhares, крыніца

Мне падабаецца прынятае рашэнне, але прапанаваць праверка выключэння складае нефатальных:

// 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
  }
}

Вы не хочаце, каб паўтарыць спробу выключэння патоку кіравання, і, як правіла, не перапыняе плынь ...

2
дададзена
<Код> нефатального матч радок не кампілюецца, калі я не раблю: выпадку памылкі (е), калі п> 1 && нефатального (е) => паўтарыць (п - 1) (п) .
дададзена аўтар Taylor R, крыніца

Калі вы хочаце кантраляваць, якія выключэння вы паўтарыць, вы можаце выкарыстоўваць метады ў 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

(Як напісана, ён будзе таксама паўтарыць на нуль, гэта Option (т) частку Калі вы хочаце обнуляет быць вернутыя, выкарыстоўвайце Некаторыя з іх (т) ўнутры залівання итератора. замест гэтага.)

Давайце паспрабуем гэта з

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

Гэта працуе?

scala> ioretry(4) { ix.get }
res0: Int = 5

scala> ix.n = 3

scala> ioretry(2) { ix.get }
java.io.IOException
    at IoEx.get(:20)
    ...

scala> ioretry(4) { throw new Exception }
java.lang.Exception
    at $anonfun$1.apply(:21)
    ...

Выглядае добра!

1
дададзена

Я скончыў тым, адаптуючы папярэдні адказ дазволіць фільтраванне, якія выключэння паўтарыць на:

  /**
   * 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
    }

  }

Вы можаце выклікаць двума спосабамі:

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

або з частковымі функцыямі (таксама паказвае версію, дзе не клапоціцца аб вяртаецца значэння)

    def pf: PartialFunction[Throwable, Boolean] =
    {
      case x: SomeOtherException => true
      case _ => false
    }

   retry(4, pf)
   {
      boom.doit()
   }
1
дададзена
//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
дададзена

Паўторна які выкарыстоўваецца аб'ект/метад з паўзай паміж спробамі:

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

код:

object Retry {
  def apply[A](times: Int, pause: Duration)(код: ⇒ 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
дададзена

This project seems to provide some nice 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
дададзена

Гэта рашэнне не аптымізавана кампілятарам хвост рэкурсіі па нейкай прычыне (хто ведае, чаму?), Але ў выпадку рэдкіх спробаў будзе варыянт:

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

выкарыстанне:

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

<Моцны> Канец адказу. Спыніце чытанне тут


Версія з вынікам як Try:

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

выкарыстанне:

// 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()
}

Версія з функцыяй якая вяртае Try

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

выкарыстанне:

// 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
дададзена