From fa0f85955240fb8567e721ab734b8fc4b5351df9 Mon Sep 17 00:00:00 2001 From: izharahmd Date: Sun, 13 Sep 2020 18:22:48 +0530 Subject: [PATCH] retry with backoff while publishing --- .../librarymanagement/LMSysProp.scala | 2 + .../librarymanagement/IvyActions.scala | 11 ++++- .../internal/librarymanagement/IvyUtil.scala | 44 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/sbt/internal/librarymanagement/LMSysProp.scala b/core/src/main/scala/sbt/internal/librarymanagement/LMSysProp.scala index d2a28d3eb..58ac20824 100644 --- a/core/src/main/scala/sbt/internal/librarymanagement/LMSysProp.scala +++ b/core/src/main/scala/sbt/internal/librarymanagement/LMSysProp.scala @@ -60,4 +60,6 @@ object LMSysProp { } lazy val useGigahorse: Boolean = getOrFalse("sbt.gigahorse") + lazy val maxPublishAttempts: Int = java.lang.Integer.getInteger("sbt.repository.publish.attempts", 3) + } diff --git a/ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index ffa2f733e..09e8b1e6b 100644 --- a/ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -25,6 +25,7 @@ import sbt.librarymanagement.{ ModuleDescriptorConfiguration => InlineConfigurat import syntax._ import InternalDefaults._ import UpdateClassifiersUtil._ +import sbt.internal.librarymanagement.IvyUtil.TransientNetworkException object IvyActions { @@ -497,8 +498,14 @@ object IvyActions { checkFilesPresent(artifacts) try { resolver.beginPublishTransaction(module.getModuleRevisionId(), overwrite); - for ((artifact, file) <- artifacts) - resolver.publish(artifact, file, overwrite) + artifacts.foreach { + case (artifact, file) => + IvyUtil.retryWithBackoff( + resolver.publish(artifact, file, overwrite), + TransientNetworkException.apply, + maxAttempts = LMSysProp.maxPublishAttempts + ) + } resolver.commitPublishTransaction() } catch { case e: Throwable => diff --git a/ivy/src/main/scala/sbt/internal/librarymanagement/IvyUtil.scala b/ivy/src/main/scala/sbt/internal/librarymanagement/IvyUtil.scala index 6c4622d90..181e49d89 100644 --- a/ivy/src/main/scala/sbt/internal/librarymanagement/IvyUtil.scala +++ b/ivy/src/main/scala/sbt/internal/librarymanagement/IvyUtil.scala @@ -1,6 +1,50 @@ package sbt.internal.librarymanagement +import java.io.IOException +import java.net.{ SocketException, SocketTimeoutException } + +import scala.annotation.tailrec +import scala.util.{ Failure, Success, Try } + private[sbt] object IvyUtil { def separate[A, B](l: Seq[Either[A, B]]): (Seq[A], Seq[B]) = (l.flatMap(_.left.toOption), l.flatMap(_.right.toOption)) + + @tailrec + final def retryWithBackoff[T]( + f: => T, + predicate: Throwable => Boolean, + maxAttempts: Int, + retry: Int = 0 + ): T = { + // Using Try helps in catching NonFatal exceptions only + Try { + f + } match { + case Success(value) => value + case Failure(e) if predicate(e) && retry < (maxAttempts - 1) => + // max 8s backoff + val backoff = math.min(math.pow(2d, retry.toDouble).toLong * 1000L, 8000L) + Thread.sleep(backoff) + retryWithBackoff(f, predicate, maxAttempts, retry + 1) + case Failure(e) => throw e + } + } + + object TransientNetworkException { + private val _r = """.*HTTP response code: (503|429).*""".r + + @inline private def check(s: String): Boolean = { + if (s == null) return false + + _r.pattern.matcher(s).matches() + } + + def apply(t: Throwable): Boolean = t match { + case _: SocketException | _: SocketTimeoutException => true + case e: IOException if check(e.getMessage) => true + case _ => false + } + } + }