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

io.delta.sharing.client.util.RetryUtils.scala Maven / Gradle / Ivy

/*
 * Copyright (2021) The Delta Lake Project Authors.
 *
 * Licensed 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 io.delta.sharing.client.util

import java.io.{InterruptedIOException, IOException}

import org.apache.spark.internal.Logging

import io.delta.sharing.spark.MissingEndStreamActionException


private[sharing] object RetryUtils extends Logging {

  // Expose it for testing
  @volatile var sleeper: Long => Unit = (sleepMs: Long) => Thread.sleep(sleepMs)

  def runWithExponentialBackoff[T](
      numRetries: Int,
      maxDurationMillis: Long = Long.MaxValue)(func: => T): T = {
    var times = 0
    var sleepMs = 100
    val startTime = System.currentTimeMillis()
    while (true) {
      times += 1
      val retryStartTime = System.currentTimeMillis()
      try {
        return func
      } catch {
        case e: Exception =>
          val totalDuration = System.currentTimeMillis() - startTime
          val retryDuration = System.currentTimeMillis() - retryStartTime
          logError(
            "Error during retry attempt " + times + ", retryDuration=" + retryDuration +
            ", totalDuration=" + totalDuration + " : " + e.getMessage,
            e
          )
          if (shouldRetry(e) && times <= numRetries && totalDuration <= maxDurationMillis) {
            logWarning(s"Sleeping $sleepMs ms to retry")
            sleeper(sleepMs)
            sleepMs *= 2
          } else {
            logError(s"Not retrying delta sharing rpc on error: ${e.getMessage}", e)
            throw e
          }
      }
    }
    throw new IllegalStateException("Should not happen")
  }

  def shouldRetry(t: Throwable): Boolean = {
    t match {
      case e: UnexpectedHttpStatus =>
        if (e.statusCode == 429) { // Too Many Requests
          true
        } else if (e.statusCode >= 500 && e.statusCode < 600) { // Internal Error
          true
        } else {
          false
        }
      case _: MissingEndStreamActionException => true
      case _: java.net.SocketTimeoutException => true
      // do not retry on ConnectionClosedException because it can be caused by invalid json returned
      // from the delta sharing server.
      case _: org.apache.http.ConnectionClosedException => false
      case _: InterruptedException => false
      case _: InterruptedIOException => false
      case _: IOException => true
      case _ => false
    }
  }
}

private[sharing] class UnexpectedHttpStatus(message: String, val statusCode: Int)
  extends IllegalStateException(message)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy