Бұл сияқты қайта қоңырау шалуды жүзеге асырудың 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);
        }
    };

}

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 жауаптар

Рекурсия + бірінші сыныптың функциялары by-name параметрлері == керемет.

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
қосылды
Catch [Nothing] түріндегі scala.util.control.Exception.catching (classOf [E1], classOf [E2 & zwnj;]) дегеніміз не? Мысалы: Exception.catching (classOf [PSQLException]) типі : Catch [Nothing] болады. Бұл солай бола ма? Мен әрқашан Nothing болса да, егер олар оған түр-параметрлеуді қосқанын білмеймін ...? : /
қосылды автор kornfridge, көзі
Бұл сұлулық.
қосылды автор Maurício Linhares, көзі
Шын мәнінде, атау параметрлері, мені алаңдатып отырса да, аталмыш параметрді рекурсивті түрде өткізгенде ыңғайсыз жағдай болуы мүмкін.
қосылды автор Daniel C. Sobral, көзі
@ annotation.tailrec def retry [T] (n: Int) (fn: => T): [T] = {тырысып көріңіз {fn} сәйкестік {case x: Success [T] => x case Failure F = f}} -> n = 1 (n)
қосылды автор piotr, көзі
@ MauricioLinhares рахмет :-D
қосылды автор leedm777, көзі
@IshankGulati Қайта әрекет етуді қалайтын ерекше жағдайды анықтау үшін, ұстап беру ережесінде қайталайтын әрекетті өзгертіңіз. Scala 2.10 мысалында n> 1 => retry (n - 1) (fn) ) болса, case util.Failure (e: RuntimeException) сияқты.
қосылды автор leedm777, көзі
@fricadelle Мен қайталау (n - 1) (fn) ) алдында «кодты> Thread.sleep (waitMillis) деп шақырамын.
қосылды автор leedm777, көзі
Фронтальды тексеру қажет
қосылды автор smartnut007, көзі
Мен сіздердің барлық шешімдеріңізді оқып шығып, олардың әрқайсысының қалай жақсарғанын байқай аласыз. Міне, сондықтан Scala - махаббат. Бұл керемет етеді. Әдемі. Мен әрқайсысымен қайталанады арасындағы соңғы уақытты ұлғайта отырып, мен бейімделемін. Мен сұлулықты мүмкіндігінше ұстауға тырысамын.
қосылды автор Rafael Saraiva, көзі
Мен жай ғана басқаларға тікелей лақтырып тастағанда, кейбіреулерін қоспағанда, қайталап көргіңіз келетінін айтайын. scala.util.control.Exception.catching (classOf [E1], classOf [E2 & zwnj;]) пайдалана аласыз ... withTry (fn) сәйкестігі ...
қосылды автор Cristian Vrabie, көзі
Егер сіз әлі де әдеті бар болсаңыз @dave scala.util.Try нысанына назар аударыңыз! Үлкен жауаптар!
қосылды автор tysonjh, көзі
(Fn: => T): Try [T] = Try (fn) сәйкестігін {case x: util.Success [T] => x жағдай, егер n> 1 => қайталаңыз (n - 1) (fn) case f => f}
қосылды автор Gaurav Abbi, көзі
Бұл тек белгілі бір ерекшеліктер үшін қайталап көру үшін өзгертілуі мүмкін бе?
қосылды автор Ishank Gulati, көзі
@ leedm777, бірақ содан кейін қайталау функциясы белгілі бір сұранымға тән болады. Мен әдіс бойынша варарг тізімін алып тастауды ойладым.
қосылды автор 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]

Task [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, көзі
Мүмкін, бұл ОС-ны қайта қарауды қаламайды: жанама әсерлер - retry (3) {println («foo»)} үш жолды шығарады.
қосылды автор themel, көзі
Сіз тырысты ба? Бұл Мен үшін Scala 2.8.1-де емес.
қосылды автор themel, көзі
@тэнси: Оң, мен ойнату аламын - жұмыс 2.9.1, бірақ 2.8.1. Мен мұны күтпедім.
қосылды автор themel, көзі
@ Маурисио Линхарес: ​​Мен жауапты жаңарттым
қосылды автор tenshi, көзі
@themel: retry қолданбасымен қайталау (3) {println («foo»)} тек бір рет басып шығарылады
қосылды автор tenshi, көзі
@themel: Мен 2.9.1.final пайдаланамын және ол тек foo басып шығарады
қосылды автор tenshi, көзі
Үлкен жауап - .view() және .toStream() туралы білу керек және ақыр соңында, Опция неге тиімді болып табылады?
қосылды автор Doug Donohoe, көзі

Осыған көмектесетін бар кітапхана бар, ол қайталау деп аталады және Java кітапханасы да бар, ол Guava-қайталау .

Қайталау пайдалану мысалдарын келтіріңіз:

// 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
қосылды
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
қосылды
NonFatal сәйкестігі сызығы мен істемейінше компиляцияланбайды: case Әрекет (e) егер n> 1 && NonFatal (e) => Қайталау (n - 1) (fn) </>.
қосылды автор 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

(Жазылғандай, ол сондай-ақ null-де әрекет етеді, ол Option (t) бөлімі) Егер сіз бос орындардың қайтарылуын қаласаңыз, иератордың ішіндегі Some (t) орнына.)

Мұны көрейік

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

Жауаптың соңы. Мұнда оқуды тоқтатыңыз


Сынақ ретінде нәтижесі бар нұсқасы:

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

Қайтару функциясы бар нұсқа

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
қосылды