Подтвердить что ты не робот

Что такое 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;

}
4b9b3361

Ответ 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
  }
}