Что такое 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?
В основном он вызывает метод вызов 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;
}
Ответы
Ответ 1
Рекурсия + функции первого класса параметры by-name == awesome.
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. Однако рекурсия хвоста не в обработчике уловов, но оптимизирует только штраф: -)
@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 x: util.Success[T] => x
case _ if n > 1 => retry(n - 1)(fn)
case fn => fn
}
}
Ответ 2
В 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
Ответ 3
Вот одна из возможных реализаций:
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
, если тело только бросает исключения.
Update
Если вы хотите перевернуть последнее исключение, вы можете использовать очень похожий подход, но используйте 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
, если хотите, а затем выбросить. (для простоты я просто бросаю последнее исключение)
Ответ 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, но я не являюсь большим поклонником однострочных, которые требуют, чтобы читатель знал всю стандартную библиотеку в любом случае.
Ответ 5
Вы можете выразить идею в функциональном стиле, используя 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
}
Ответ 6
Существует существующая библиотека, которая может помочь с этим, retry, а также есть библиотека Java, называемая guava-retrying.
Вот несколько примеров использования 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 }
Ответ 7
Мне нравится принятое решение, но предлагаю проверить исключение: 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
}
}
Вы не хотите повторять исключение потока управления и обычно не для прерываний потоков...
Ответ 8
Если вы хотите контролировать, какие исключения вы повторите, вы можете использовать методы в 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)
. Если вы хотите, чтобы возвращались nulls, используйте 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)
...
Выглядит хорошо!
Ответ 9
Я закончил тем, что адаптировал предыдущий ответ, чтобы разрешить фильтрацию на каких исключениях повторить попытку:
/**
* 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()
}
Ответ 10
Этот проект, как представляется, обеспечивает некоторые хорошие реализации для разных механизмов повтора
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)
}
Ответ 11
//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)
}}
}
Ответ 12
Это решение не оптимизировано компилятором для хвостовой рекурсии по какой-либо причине (кто знает почему?), но в случае редких повторов будет вариант:
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()
}
Конец ответа. Остановить чтение здесь
Версия с результатом в виде Try:
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()
}
Ответ 13
Повторно используемый объект/метод с паузой между попытками:
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
}
}