wake-up-neo.com

Wie kann Scala so einen wiederholbaren Anruf wie diesen implementieren?

Immer noch der Neuling in Scala und ich suche jetzt nach einer Möglichkeit, den folgenden Code darauf zu implementieren:

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

}

Was wäre der beste Weg, um dieselbe Funktionalität zu implementieren, die RetryableService in Scala implementiert?

Es ruft die call -Methode im Wesentlichen N-mal auf. Wenn alle fehlschlagen, wird die Exception ausgelöst, wenn sie erfolgreich ist, wird sie fortgesetzt. Dieser gibt nichts zurück, aber dann habe ich eine andere Version, die einen Wert zurückgibt (also habe ich zwei Klassen in Java) und ich glaube, ich könnte eine einzige Klasse/Funktion in Scala verwenden.

Irgendwelche Ideen?

EDIT

Die aktuelle Implementierung in Java ist wie folgt:

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;

}
47

Rekursion + erstklassige Funktionen by-name-parameter == ehrfürchtig.

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

Die Nutzung ist wie folgt:

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

Edit: leichte Abweichung, die sich an der Antwort von @themel orientiert. Eine Codezeile weniger :-)

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

Edit Again: Die Rekursion hat mich gestört, dass sie dem Stack-Trace mehrere Aufrufe hinzugefügt hat. Aus irgendeinem Grund konnte der Compiler die Tail-Rekursion im Catch-Handler nicht optimieren. Tail-Rekursion nicht im Catchhandler, optimiert aber ganz gut :-)

@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 noch einmal: Offensichtlich werde ich es zu einem Hobby machen, immer wieder zurückzukommen und Alternativen zu dieser Antwort hinzuzufügen. Hier ist eine rekursive Version, die etwas unkomplizierter ist als die Verwendung von Option, aber die Verwendung von return zum Kurzschließen einer Funktion ist keine idiomatische 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. Wie mein Hobby, gehe ich gelegentlich auf diese Antwort zurück. Scala 2.10 wird als Try eingeführt. Dies ist eine saubere Möglichkeit, Wiederholungen rekursiv auszuführen.

// 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 util.Failure(_) if n > 1 => retry(n - 1)(fn)
    case fn => fn
  }
}
155
leedm777

Es gibt eine Methode 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]

Mit einem Task[T] können Sie einen neuen Task[T] erstellen, der eine bestimmte Anzahl von Versuchen wiederholt wird, wobei die Verzögerung zwischen den Neuversuchen durch den Parameter delays definiert wird. z.B.:

// 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
Gary Coady

Hier ist eine mögliche Implementierung:

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

Sie können es so verwenden:

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

retry gibt auch Some[T] zurück, wenn body erfolgreich verarbeitet wurde, und None, wenn body nur Ausnahmen ausgelöst hat.


Aktualisieren

Wenn Sie die letzte Ausnahme bombeln möchten, können Sie einen sehr ähnlichen Ansatz verwenden, jedoch Either anstelle von Option verwenden:

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

Wie Sie sehen können, habe ich am Ende, anstatt nur die letzte Ausnahme zu haben, alle. Sie können sie auch in eine AggregatingException einwickeln, wenn Sie möchten, und sie dann werfen. (Der Einfachheit halber werfe ich nur die letzte Ausnahme)

5
tenshi

Ich würde das vorschlagen - 

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
} 

Es tut: 

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

Es kann wahrscheinlich verbessert werden, um eine idiomatischere Scala zu sein, aber ich bin kein großer Fan von Einzeilern, bei denen der Leser die gesamte Standardbibliothek sowieso auswendig kennen sollte.

4
themel

Sie können die Idee in einem funktionalen Stil ausdrücken, indem Sie scala.util.control.Exception verwenden:

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

Wie wir sehen können, kann hier die Rekursion des Endes verwendet werden.

Dieser Ansatz bietet Ihnen den zusätzlichen Vorteil, dass Sie den catch-Container parametrisieren können, sodass Sie nur eine bestimmte Teilmenge von Ausnahmen erneut versuchen, Finalizer hinzufügen können. Die endgültige Version von retry könnte also wie folgt aussehen:

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

Damit können Sie komplexe Dinge tun wie:

retry(Exception.allCatch andFinally { print("Finished.") }, 3) {
  // your scode
}
3
Petr Pudlák

Es gibt eine vorhandene Bibliothek, die dazu beitragen kann, genannt retry , und es gibt auch eine Java-Bibliothek namens guava-retrying .

Hier sind einige Beispiele für die Verwendung von 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
Hosam Aly

Ich mag die akzeptierte Lösung, schlage jedoch vor, dass die Ausnahme als nicht fatal eingestuft wird:

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

Sie möchten keine Regelflussausnahme wiederholen, und in der Regel nicht für Thread-Interrupts.

2
srnm

Am Ende habe ich eine vorherige Antwort angepasst, um zu filtern, auf welchen Ausnahmen es einen erneuten Versuch gibt:

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

  }

Sie können auf zwei Arten anrufen:

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

oder mit Teilfunktionen (auch Version anzeigen, bei der der Rückgabewert nicht wichtig ist)

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

   retry(4, pf)
   {
      boom.doit()
   }
1
Doug Donohoe

Wenn Sie steuern möchten, welche Ausnahmen Sie erneut versuchen, können Sie Methoden in scala.util.control.Exception verwenden:

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

(Wie geschrieben, wird es auch bei null wiederholt; dies ist der Teil Option(t). Wenn Sie möchten, dass Nullen zurückgegeben werden, verwenden Sie stattdessen Some(t) in der Iteratorfüllung.)

Lass uns das mit ausprobieren

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

Funktioniert es?

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

Sieht gut aus!

1
Rex Kerr

Dieses Projekt scheint einige Nice-Implementierungen für verschiedene Wiederholungsmechanismen zu bieten 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
hipjim

Ein wiederverwendbares Objekt/eine Methode mit einer Pause zwischen den Versuchen:

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
Devis Lucato

Diese Lösung wird vom Compiler aus irgendeinem Grund nicht für die Rekursion optimiert (wer weiß warum?), Aber bei seltenen Wiederholungen wäre eine Option:

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

Verwendungszweck:

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

Ende der Antwort. Hör auf zu lesen hier


Version mit Ergebnis als Versuch:

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

Verwendungszweck:

// 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 mit einer Funktion, die Try zurückgibt

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

Verwendungszweck:

// 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
Sergii Pogodin
//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
Santhosh Sath