まだ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;
}
再帰+ 一流の関数 名前によるパラメーター==素晴らしい。
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
}
}
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
可能な実装の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
でラップしてからスローすることもできます。 (簡単にするために、最後の例外をスローします)
これをお勧めします-
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に改良することができますが、私は読者がとにかく標準ライブラリ全体を心から知ることを必要とするワンライナーの大ファンではありません。
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
}
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 }
私は受け入れられた解決策が好きですが、例外が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
}
}
制御フローの例外を再試行したくはありません。通常はスレッド割り込みのために...
私は以前の回答を適応させて、どの例外で再試行するかをフィルタリングできるようにしました:
/**
* 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()
}
再試行する例外を制御する場合は、_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)
...
_
いいね!
このプロジェクトは、さまざまな再試行メカニズムにニースの実装を提供するようです 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)
}
再試行可能な一時停止のある再利用可能なオブジェクト/メソッド:
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
}
}
このソリューションは、コンパイラーによって何らかの理由で再帰を末尾に最適化されていません(誰が理由を知っていますか?)が、まれな再試行の場合にはオプションになります。
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()
}
//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)
}}
}