From 07e3815261a478a68b0c377e24c179d4e9124457 Mon Sep 17 00:00:00 2001 From: izharahmd Date: Tue, 29 Sep 2020 19:10:42 +0530 Subject: [PATCH] retry publish on all 5XX errors --- .../internal/librarymanagement/IvyUtil.scala | 8 ++- .../librarymanagement/IvyUtilSpec.scala | 66 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 ivy/src/test/scala/sbt/internal/librarymanagement/IvyUtilSpec.scala diff --git a/ivy/src/main/scala/sbt/internal/librarymanagement/IvyUtil.scala b/ivy/src/main/scala/sbt/internal/librarymanagement/IvyUtil.scala index 181e49d89..c3437b595 100644 --- a/ivy/src/main/scala/sbt/internal/librarymanagement/IvyUtil.scala +++ b/ivy/src/main/scala/sbt/internal/librarymanagement/IvyUtil.scala @@ -31,8 +31,14 @@ private[sbt] object IvyUtil { } } + /** + * Currently transient network errors are defined as: + * - a network timeout + * - all server errors (response code 5xx) + * - rate limiting (response code 429) + */ object TransientNetworkException { - private val _r = """.*HTTP response code: (503|429).*""".r + private val _r = """.*HTTP response code: (5\d{2}|408|429).*""".r @inline private def check(s: String): Boolean = { if (s == null) return false diff --git a/ivy/src/test/scala/sbt/internal/librarymanagement/IvyUtilSpec.scala b/ivy/src/test/scala/sbt/internal/librarymanagement/IvyUtilSpec.scala new file mode 100644 index 000000000..6924d739a --- /dev/null +++ b/ivy/src/test/scala/sbt/internal/librarymanagement/IvyUtilSpec.scala @@ -0,0 +1,66 @@ +package sbt.internal.librarymanagement + +import java.io.IOException + +import org.scalatest.FunSuite +import sbt.internal.librarymanagement.IvyUtil._ + +class IvyUtilSpec extends FunSuite { + test("503 should be a TransientNetworkException") { + val statusCode503Exception = + new IOException("Server returned HTTP response code: 503 for URL:") + assert(TransientNetworkException(statusCode503Exception)) + } + + test("500 should be a TransientNetworkException") { + val statusCode500Exception = + new IOException("Server returned HTTP response code: 500 for URL:") + assert(TransientNetworkException(statusCode500Exception)) + } + + test("408 should be a TransientNetworkException") { + val statusCode408Exception = + new IOException("Server returned HTTP response code: 408 for URL:") + assert(TransientNetworkException(statusCode408Exception)) + } + + test("429 should be a TransientNetworkException") { + val statusCode429Exception = + new IOException(" Server returned HTTP response code: 429 for URL:") + assert(TransientNetworkException(statusCode429Exception)) + } + + test("404 should not be a TransientNetworkException") { + val statusCode404Exception = + new IOException("Server returned HTTP response code: 404 for URL:") + assert(!TransientNetworkException(statusCode404Exception)) + } + + test("IllegalArgumentException should not be a TransientNetworkException") { + val illegalArgumentException = new IllegalArgumentException() + assert(!TransientNetworkException(illegalArgumentException)) + } + + test("it should retry for 3 attempts") { + var i = 0 + def f: Int = { + i += 1 + if (i < 3) throw new RuntimeException() else i + } + // exception predicate retries on all exceptions for this test + val result = retryWithBackoff(f, _ => true, maxAttempts = 3) + assert(result == 3) + } + + test("it should fail after maxAttempts") { + var i = 0 + def f: Int = { + i += 1 + throw new RuntimeException() + } + intercept[RuntimeException] { + retryWithBackoff(f, _ => true, maxAttempts = 3) + } + assert(i == 3) + } +}