Bu kabi qayta qo'ng'iroqni amalga oshirish uchun Scala yo'li qanday?

Hali ham Scala'da yangi odam va men quyidagi kodni amalga oshirish uchun yo'l izlayapman:

@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 amalga oshiradigan bir xil funktsionalni amalga oshirishning eng yaxshi yo'li Scala'da nima bo'ladi?

Ularning barchasi muvaffaqiyatsiz bo'lsa, istisno ko'tarilsa, asosan, N marta chaqiriq usulini chaqiradi. Bu narsa hech narsa qaytara olmaydi, lekin keyin yana bir versiyam bor, bu qiymat qaytarish imkonini beradi (Java'da ikkita sinf bor) va men Skala-da bitta sinf/funktsiya bilan ishlashim mumkinligiga ishonaman.

Har qanday fikr bormi?

EDIT -ni tanlang

Java da joriy dastur quyidagilar:

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 javoblar

Recursion + birinchi sinf funktsiyalari by-name parametrlari == ajoyib.

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

Foydalanish quyidagicha:

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
qo'shib qo'ydi
Catch [Nothing] nima uchun scala.util.control.Exception.catching (classOf [E1], classOf [E2 & zwnj;]) nima uchun? Masalan, Exception.catching (classOf [PSQLException]) kodi : Catch [Nothing] bo'ladi. Bu xuddi shu tarzda bo'lishi kerak edi? Nima uchun ular har doim Nothing bo'lsa ham, bunday parametrizatsiya qo'shib qo'yganligini bilmayman ...? : /
qo'shib qo'ydi muallif kornfridge, manba
Bu go'zallik.
qo'shib qo'ydi muallif Maurício Linhares, manba
Aslida, ism-sharif parametrlari, men tashvishlanayotgan bo'lsa-da, nominal parametrni o'z-o'zidan yurgizayotganda juda g'alati bir narsa bo'lishi mumkin.
qo'shib qo'ydi muallif Daniel C. Sobral, manba
@ annotation.tailrec def retry [T] (n: Int) (fn: => T): [T] = {sinash {fn} bilan urinish {case x: muvaffaqiyati [t] => N = 1) (fn) holatida f => f}}
qo'shib qo'ydi muallif piotr, manba
@ MauricioLinhares Tashakkur :-D
qo'shib qo'ydi muallif leedm777, manba
@IshankGulati Qayta urinish kerakli istisnolarni aniqlash uchun catch bobida qayta urinish holatini o'zgartiring. Scala 2.10 misolida n> 1 => qayta urinish (n - 1) (fn) ) bo'lsa, case util.Failure (e: RuntimeException) kabi.
qo'shib qo'ydi muallif leedm777, manba
@fricadelle Menimcha qayta urinishdan oldin (n - 1) (fn) ) oldin Thread.sleep (waitMillis) deb chaqiraman deb o'ylayman.
qo'shib qo'ydi muallif leedm777, manba
NonFatal tekshiruvga muhtoj
qo'shib qo'ydi muallif smartnut007, manba
Men sizning barcha echimlarni o'qib chiqdim va siz har birining avvalgi holatini qanday yaxshilashini bilib olishingiz mumkin. Shuning uchun Skala - bu sevgi. Bu shkalani ajoyib qiladi. Yaxshi. Men oxirgi birini moslashtiraman va har bir qayta urinishlar orasida kuchayib borayotgan takrorlashlarni qo'shaman. Men go'zallikni iloji boricha ko'proq ushlab turishga harakat qilaman.
qo'shib qo'ydi muallif Rafael Saraiva, manba
Faqat ba'zan siz boshqalarni to'g'ridan-to'g'ri tashlab ketayotib, ayrim istisnolardan qaytadan urinishni istaysiz. scala.util.control.Exception.catching (classOf [E1], classOf [E2 & zwnj;] dan foydalanishingiz mumkin ... withTry (fn) match ...
qo'shib qo'ydi muallif Cristian Vrabie, manba
Agar hali ham odat tusiga ega bo'lsangiz @dave scala.util.Try ob'ektiga e'tibor bering! Katta javoblar!
qo'shib qo'ydi muallif tysonjh, manba
(Fn: => T): [T] = sinab ko'ring (fn) mos keladigan {case x: util.Success [T] => x hol bo'lsa, agar n> 1 => qayta urin (n - 1) (fn) ish f => f}
qo'shib qo'ydi muallif Gaurav Abbi, manba
Bu faqat ayrim istisnolardan qaytadan urinish uchun o'zgartirilishi mumkinmi?
qo'shib qo'ydi muallif Ishank Gulati, manba
@ leedm777 lekin qayta urinish funktsiyasi ma'lum bir talabga xos bo'ladi. Men usulda vararg ro'yxatidan istisnolardan o'tishni o'yladim.
qo'shib qo'ydi muallif Ishank Gulati, manba
Har bir qayta urinish o'rtasida kutish vaqtini qo'shish uchun qanday qilib takomillashtirilishi mumkin?
qo'shib qo'ydi muallif fricadelle, manba
Bu erda bitta savol. Funktsiya kelajakka qaytadi, agar uni qanday o'zgartirish mumkin?
qo'shib qo'ydi muallif asdasdsdf, manba

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]

Agar Task [T] kodi berilgan bo'lsa, yangi kodni yaratishingiz mumkin. Bu Task [T] > kechikishlar parametrini tanlang. masalan:

// 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
qo'shib qo'ydi

Mana bir amalga oshirish mumkin:

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

Buni quyidagi kabi foydalanishingiz mumkin:

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

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


Yangilash

Agar istisno qilsangiz, juda o'xshash yondashuvni olishingiz mumkin, lekin Option o'rniga yoki dan foydalanishingiz mumkin:

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

Bundan tashqari, siz ko'rib turganingizdek, oxirida, faqat oxirgi istisnoga ega bo'lish o'rniga, ularning barchasi bor. Shunday qilib, agar kerak bo'lsa, ularni AggregatingException qatoriga joylashtiring. (soddalik uchun, men faqat so'nggi istisno qilaman)

5
qo'shib qo'ydi
Rahmat @thenshi :)
qo'shib qo'ydi muallif Maurício Linhares, manba
Agar istisno doimo buzilmasa, men bu savolga to'liq amalni qo'shaman.
qo'shib qo'ydi muallif Maurício Linhares, manba
Shuni e'tiborga olish kerakki, bu OPning niyatini o'zgartirmaydi: yon ta'sir - qayta urinish (3) {println ("foo")} uch qatorni chop etadi.
qo'shib qo'ydi muallif themel, manba
Siz harakat qildingizmi? Men uchun Scala 2.8.1da emas.
qo'shib qo'ydi muallif themel, manba
@enshi: To'g'ri, men ko'paytira olaman - 2.9.1-da ishlaydi, lekin 2.8.1 da emas. Men buni kutmagan edim.
qo'shib qo'ydi muallif themel, manba
@ MauricioLinhares: Men javobimni yangiladim
qo'shib qo'ydi muallif tenshi, manba
@themel: retry ilovasini ishlatish bilan qayta urinish (3) {println ("foo")} faqat bir marta chop qilinadi
qo'shib qo'ydi muallif tenshi, manba
@themel: Men 2.9.1.final dan foydalanaman va faqat foo ni bosib chiqaradi
qo'shib qo'ydi muallif tenshi, manba
Katta javob - .view() va .toStream() ni o'rganish kerak va nihoyat, Variantning nima uchun samarali o'tkazilishi mumkinligini tushunish kerak.
qo'shib qo'ydi muallif Doug Donohoe, manba

Bunga " qayta urinish deb nomlangan mavjud bo'lgan kutubxona ham mavjud va Java-kutubxonasi ham mavjud. Guava-qayta urinish .

qayta urinish dan foydalanishning ba'zi bir misollar:

// 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
qo'shib qo'ydi

Men buni taklif qilaman -

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
} 

U shunday qiladi:

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

Ehtimol, bu yanada noan'anaviy Scala bo'lishi mumkin, ammo men hamisha kutubxona kutubxonasini bilib olishni talab qiladigan bir laynerlarning katta muxlisi emasman.

3
qo'shib qo'ydi
Bu ham ajoyib echim.
qo'shib qo'ydi muallif Maurício Linhares, manba

Men qabul qilingan echimni yaxshi ko'raman, lekin istisno tekshiruvini noto'g'ri deb bilishni taklif qilaman:

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

Boshqaruv oqimi istisnosini qayta urishni istamaysiz va odatda qidiruvni to'xtatish uchun emas ...

2
qo'shib qo'ydi
NonFatal mos keladigan satrni qilmagunimcha, kompilyatsiya qilinmaydi: case Xato (e) agar n> 1 && NonFatal (e) => qayta urinish (n - 1) (fn) .
qo'shib qo'ydi muallif Taylor R, manba

Qaysi istisnolaringizni tekshirishni istasangiz, scala.util.control.Exception da usullarni qo'llashingiz mumkin:

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

(Yozilganidek, u null-da qayta ko'rib chiqadi, ya'ni Option (t) qismidir. Agar null qaytarib berilishini xohlasangiz, some (t) Buning o'rniga.)

Keling buni sinab ko'raylik

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

U ishlaydi?

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)
    ...

Yaxshi ko'rinadi!

1
qo'shib qo'ydi

Qanday istisnolardan foydalanishni sinab ko'rish uchun oldingi javobni moslashtirdim:

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

  }

Siz ikki yo'l bilan qo'ng'iroq qilishingiz mumkin:

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

yoki qisman funktsiyalar bilan (shuningdek, qaytib qiymat haqida qayg'urmayotgan versiyani ham ko'rsatib turibdi)

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

   retry(4, pf)
   {
      boom.doit()
   }
1
qo'shib qo'ydi
//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
qo'shib qo'ydi

Qaytarilishi mumkin bo'lgan ob'ekt/usul, urinishlar orasidagi pauza bilan:

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

Kod:

object Retry {
  def apply[A](times: Int, pause: Duration)(Kod: ⇒ 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
qo'shib qo'ydi

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
qo'shib qo'ydi

Ushbu yechim kompilyator tomonidan biron bir sababga ko'ra (masalan, kim biladi) takrorlash uchun optimallashtirilmagan bo'lsa-da, ammo noyob amaliyotlar mavjud bo'lsa, bu variantni tanlash mumkin:

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

Foydalanish:

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

Javob oxirida. Bu yerda o'qish to'xtating


Natijada ko'ringan versiya:

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

Foydalanish:

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

Orqaga qaytish funksiyasi bilan versiya

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

Foydalanish:

// 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
qo'shib qo'ydi