仍然是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);
}
};
}
在Scala中实现RetryableService实现的相同功能的最佳方法是什么?
它基本上调用调用方法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;
}
答案 0 :(得分:158)
递归+ 第一类函数按名称参数==真棒。
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
}
修改:受@themel答案启发的轻微变化。少一行代码: - )
def retry[T](n: Int)(fn: => T): T = {
try {
fn
} catch {
case e if n > 1 =>
retry(n - 1)(fn)
}
}
再次编辑:递归困扰我,因为它添加了几次对堆栈跟踪的调用。由于某种原因,编译器无法在catch处理程序中优化尾递归。但是,不在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更新。作为我的爱好,我偶尔会重温这个答案。 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
}
}
答案 1 :(得分:7)
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
参数定义。 e.g:
// 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
答案 2 :(得分: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
也会返回Some[T]
,如果正在抛出异常,则None
也会返回。{/ p>
如果您想要填补上一个例外,那么您可以采用非常类似的方法,但使用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
中,然后将其抛出。 (为简单起见,我只抛出最后一个例外)
答案 3 :(得分:4)
我建议这个 -
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 :(得分:3)
您可以使用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);
}
正如我们所看到的,尾递归可以在这里使用。
这种方法为您提供了参数化catch容器的额外好处,因此您只能重试某个异常子集,添加终结器等。因此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
}
答案 5 :(得分:3)
现有的库可以提供帮助,称为retry,并且还有一个Java库,名为guava-retrying。
以下是使用retry的一些示例:
#include <math.h>
#include <inttypes.h>
#include <stdio.h>
#define safe_abs(n) _Generic((n), \
signed char: abs(n), short: abs(n), int: abs(n), long: labs(n), long long: llabs(n))
int main(void)
{
int8_t n1 = -123;
printf("n1 = %" PRId8 "\n", safe_abs(n1));
int16_t n2 = -1234;
printf("n2 = %" PRId16 "\n", safe_abs(n2));
int32_t n3 = -123456;
printf("n3 = %" PRId32 "\n", safe_abs(n3));
int64_t n4 = -12345678910;
printf("n4 = %" PRId64 "\n", safe_abs(n4));
return 0;
}
答案 6 :(得分:2)
我喜欢接受的解决方案,但建议检查异常是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
}
}
您不想重试控制流异常,通常不会重置线程中断......
答案 7 :(得分:1)
如果您想控制您重试的异常,可以使用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(<console>:20)
...
scala> ioretry(4) { throw new Exception }
java.lang.Exception
at $anonfun$1.apply(<console>:21)
...
看起来不错!
答案 8 :(得分: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()
}
答案 9 :(得分:0)
这个项目似乎为不同的重试机制提供了一些很好的实现 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)
}
答案 10 :(得分:0)
//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)
}}
}
答案 11 :(得分: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()
}
答案 12 :(得分:0)
尝试之间暂停的可重用对象/方法:
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
}
}
答案 13 :(得分:0)
打印输出略有改进尝试使用x的N
// Returning T, throwing the exception on failure
@annotation.tailrec
final def retry[T](n: Int, name: String ="", attemptCount:Int = 1)(fn: => T): T = {
logger.info(s"retry count: attempt $attemptCount of $n ....... function: $name")
try {
val result = fn
logger.info(s"Succeeded: attempt $attemptCount of $n ....... function: $name")
result
} catch {
case e: Throwable =>
if (n < attemptCount) { Thread.sleep(5000 * attemptCount); retry(n, name, attemptCount+1)(fn) }
else throw e
}
}