web-dev-qa-db-ja.com

このような再試行可能な呼び出しを実装する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);
        }
    };

}

RetryableServiceが実装するのと同じ機能をScalaで実装する最良の方法は何でしょうか?

基本的にcallメソッドをN回呼び出します。すべて失敗した場合は例外が発生し、成功した場合は次に進みます。これは何も返しませんが、値を返すことができる別のバージョンがあります(したがって、Javaに2つのクラスがあります)。

何か案は?

[〜#〜] edit [〜#〜]

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;

}
49

再帰+ 一流の関数 名前によるパラメーター==素晴らしい。

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@ themel の答えに触発されたわずかなバリエーション。 1行少ないコード:-)

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

もう一度編集:再帰はスタックトレースにいくつかの呼び出しを追加するという点で私を悩ませました。何らかの理由で、コンパイラーはキャッチハンドラーで末尾再帰を最適化できませんでした。ただし、catchハンドラーではない末尾再帰は最適化されます:-)

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

もう一度編集する:どうやら、この答えに戻って、代替案を追加し続けることを趣味にするつもりです。末尾再帰バージョンはOptionを使用するよりも少し簡単ですが、returnを使用して関数を短絡させるのは慣用的な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。私の趣味であるように、私は時々この答えを再訪します。 Scala 2.10導入時 Try 。これは、末尾再帰方式で再試行を実装するクリーンな方法を提供します。

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

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]を作成できます。再試行間の遅延はdelaysパラメーターによって定義されます。例えば。:

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

可能な実装の1つを次に示します。

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は、本文が正常に処理された場合はSome[T]を返し、本文が例外のみをスローした場合はNoneも返します。


更新

最後の例外を解決したい場合は、非常に似たアプローチをとることができますが、Eitherの代わりに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でラップしてからスローすることもできます。 (簡単にするために、最後の例外をスローします)

6
tenshi

これをお勧めします-

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

おそらくより慣用的なScalaに改良することができますが、私は読者がとにかく標準ライブラリ全体を心から知ることを必要とするワンライナーの大ファンではありません。

4
themel

scala.util.control.Exception を使用して、アイデアを機能的なスタイルで表現できます。

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

ご覧のとおり、ここでは末尾再帰を使用できます。

このアプローチには、キャッチコンテナーをパラメーター化できるという追加の利点があります。したがって、例外の特定のサブセットのみを再試行したり、ファイナライザーを追加したりできます。したがって、retryの最終バージョンは次のようになります:

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

これにより、次のような複雑なことができます。

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

retry と呼ばれる、これを支援できる既存のライブラリがあります。また、 guava-retrying と呼ばれるJavaライブラリもあります。

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

私は受け入れられた解決策が好きですが、例外がNonFatalであることを確認することをお勧めします。

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

私は以前の回答を適応させて、どの例外で再試行するかをフィルタリングできるようにしました:

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

  }

次の2つの方法で呼び出すことができます。

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
Doug Donohoe

再試行する例外を制御する場合は、_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)部分です。nullを返したい場合は、代わりにイテレータfill内で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(<console>:20)
    ...

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

いいね!

1
Rex Kerr

このプロジェクトは、さまざまな再試行メカニズムにニースの実装を提供するようです 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

再試行可能な一時停止のある再利用可能なオブジェクト/メソッド:

Retry(3, 2 seconds) { /* some 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

このソリューションは、コンパイラーによって何らかの理由で再帰を末尾に最適化されていません(誰が理由を知っていますか?)が、まれな再試行の場合にはオプションになります。

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

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