All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sparklinedata.druid.RetryUtils.scala Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.sparklinedata.druid

import java.util.concurrent.{Callable, TimeUnit}

import com.google.common.base.Throwables
import org.apache.spark.Logging

import scala.reflect._

object RetryUtils extends Logging {

  var DEFAULT_RETRY_COUNT: Int = 10
  var DEFAULT_RETRY_SLEEP: Long = TimeUnit.SECONDS.toMillis(30)

  def retryUntil(callable: Callable[Boolean],
                 expectedValue: Boolean,
                 delayInMillis: Long,
                 retryCount: Int,
                 taskMessage: String) : Unit = {
    try {
      var currentTry: Int = 0
      while (callable.call != expectedValue) {
        if (currentTry > retryCount) {
          throw new IllegalStateException(
            s"Max number of retries[$retryCount] exceeded for Task[$taskMessage]. Failing."
          )
        }
        logInfo(s"Attempt[$currentTry]: " +
          s"Task $taskMessage still not complete. Next retry in $delayInMillis ms")
        Thread.sleep(delayInMillis)
        currentTry += 1
      }
    } catch {
      case e: Exception => {
        throw Throwables.propagate(e)
      }
    }
  }

  def ifException[E <: Exception : ClassTag] = (e: Exception) =>
    classTag[E].runtimeClass.isAssignableFrom(e.getClass)


  def backoff(start : Int, cap : Int) : Stream[Int] = {
    def next(current : Int) : Stream[Int] = {
      Stream.cons(current, next(Math.min(current *2, cap)))
    }
    next(start)
  }

  def execWithBackOff[X](taskMessage : String, f : Int => Option[X])(
    numTries : Int = Int.MaxValue, start : Int = 200, cap : Int = 5000) : X = {
    val b = backoff(start, cap).iterator
    var tries = 0
    while(tries < numTries) {
      val nextBackOff = b.next()
      f(nextBackOff) match {
        case Some(x) => return x
        case _ => {
          Thread.sleep(b.next)
          tries += 1
        }
      }
    }
    throw new IllegalStateException(
      s"Max number of retries[$numTries] exceeded for Task[$taskMessage]. Failing."
    )
  }

  def retryOnErrors[X](isTransients: (Exception => Boolean)*)(
    taskMessage: String,
    x: => X,
    numTries: Int = Int.MaxValue, start: Int = 200, cap: Int = 5000
  ): X = {
    execWithBackOff(taskMessage, { nextBackOff =>
      try Some(x) catch {
        case e: Exception if isTransients.find(_ (e)).isDefined =>
          logWarning(s"Transient error in $taskMessage, retrying after $nextBackOff ms")
          None
      }
    })(numTries, start, cap)

  }

  def retryOnError(isTransient: Exception => Boolean) = new {
    def apply[X](taskMessage: String,
                 x: => X)(
                 numTries: Int = Int.MaxValue, start: Int = 200, cap: Int = 5000) =
      retryOnErrors(isTransient)(taskMessage, x, numTries, start, cap)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy