From 9503542dd14ef52f975d6e431811552b5d5f30b7 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 29 Dec 2024 03:00:29 -0500 Subject: [PATCH 1/2] fix: Retry to retry non-IOException **Problem** Retry(...) only handles IOExceptions. **Solution** This fixes it to handle NonFatal. --- build.sbt | 3 ++- io/src/main/scala/sbt/internal/io/Retry.scala | 6 +++--- .../test/scala/sbt/internal/io/RetrySpec.scala | 17 ++++++++++++----- project/Dependencies.scala | 1 + project/build.properties | 2 +- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/build.sbt b/build.sbt index ae2cf436..6a492d3a 100644 --- a/build.sbt +++ b/build.sbt @@ -80,8 +80,9 @@ val io = (project in file("io")) commonSettings, name := "IO", libraryDependencies ++= { - Vector(scalaCompiler.value % Test, scalaCheck % Test, scalatest % Test) + Vector(scalaCompiler.value % Test, scalaVerify % Test, scalaCheck % Test, scalatest % Test) } ++ Vector(swovalFiles), + testFrameworks += new TestFramework("verify.runner.Framework"), Test / fork := System.getProperty("sbt.test.fork", "false") == "true", Test / testForkedParallel := true, Compile / generateContrabands / sourceManaged := baseDirectory.value / "src" / "main" / "contraband-scala", diff --git a/io/src/main/scala/sbt/internal/io/Retry.scala b/io/src/main/scala/sbt/internal/io/Retry.scala index ebfc0668..2d660655 100644 --- a/io/src/main/scala/sbt/internal/io/Retry.scala +++ b/io/src/main/scala/sbt/internal/io/Retry.scala @@ -34,19 +34,19 @@ private[sbt] object Retry { excludedExceptions: Class[? <: IOException]*, ): T = { require(limit >= 1, "limit must be 1 or higher: was: " + limit) - def filter(e: Exception): Boolean = excludedExceptions match { + def filter(e: Throwable): Boolean = excludedExceptions match { case s if s.nonEmpty => !excludedExceptions.exists(_.isAssignableFrom(e.getClass)) case _ => true } var attempt = 1 - var firstException: IOException = null + var firstException: Throwable = null while (attempt <= limit) { try { return f } catch { - case e: IOException if filter(e) => + case NonFatal(e) if filter(e) => if (firstException == null) firstException = e // https://github.com/sbt/io/issues/295 // On Windows, we're seeing java.nio.file.AccessDeniedException with sleep(0). diff --git a/io/src/test/scala/sbt/internal/io/RetrySpec.scala b/io/src/test/scala/sbt/internal/io/RetrySpec.scala index 298f3fe4..8cbdcde9 100644 --- a/io/src/test/scala/sbt/internal/io/RetrySpec.scala +++ b/io/src/test/scala/sbt/internal/io/RetrySpec.scala @@ -14,11 +14,9 @@ package sbt.internal.io import java.io.IOException import java.util.concurrent.atomic.AtomicInteger -import org.scalatest.flatspec.AnyFlatSpec - -final class RetrySpec extends AnyFlatSpec { +object RetrySpec extends verify.BasicTestSuite { private val noExcluded: List[Class[? <: IOException]] = List[Class[? <: IOException]]() - "retry" should "throw first exception after number of failures" in { + test("retry should throw first exception after number of failures") { val i = new AtomicInteger() def throww(): Any = throw new IOException(i.incrementAndGet().toString) try { @@ -31,7 +29,7 @@ final class RetrySpec extends AnyFlatSpec { } } - "retry" should "throw recover" in { + test("retry should throw recover") { for (recoveryStep <- (1 to 14)) { val i = new AtomicInteger() val value = Retry( @@ -46,4 +44,13 @@ final class RetrySpec extends AnyFlatSpec { assert(value == "recover") } } + + test("retry should recover from non-IO exceptions") { + val i = new AtomicInteger() + def throww(): Any = + if (i.incrementAndGet() == 5) 0 + else ??? + Retry(throww()) + () + } } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 7ea746b7..a47e7f48 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -11,6 +11,7 @@ object Dependencies { "org.scala-lang" % "scala-compiler" % v } + val scalaVerify = "com.eed3si9n.verify" %% "verify" % "1.0.0" val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.18.1" val scalatest = "org.scalatest" %% "scalatest" % "3.2.19" val swovalFiles = "com.swoval" % "file-tree-views" % "2.1.12" diff --git a/project/build.properties b/project/build.properties index abbbce5d..73df629a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.8 +sbt.version=1.10.7 From e4da4c5e86e799d6041ba80a464e4a536e368b52 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 1 Jan 2025 02:58:32 -0500 Subject: [PATCH 2/2] Expand excludedExceptions to throwables --- build.sbt | 1 + io/src/main/scala/sbt/internal/io/Retry.scala | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 6a492d3a..6f802e3f 100644 --- a/build.sbt +++ b/build.sbt @@ -107,6 +107,7 @@ val io = (project in file("io")) "1.7.0", "1.8.0", "1.9.0", + "1.10.0", ) map (version => organization.value %% moduleName.value % version) }), mimaBinaryIssueFilters ++= Seq( diff --git a/io/src/main/scala/sbt/internal/io/Retry.scala b/io/src/main/scala/sbt/internal/io/Retry.scala index 2d660655..f3f72620 100644 --- a/io/src/main/scala/sbt/internal/io/Retry.scala +++ b/io/src/main/scala/sbt/internal/io/Retry.scala @@ -20,18 +20,18 @@ private[sbt] object Retry { try System.getProperty("sbt.io.retry.limit", defaultLimit.toString).toInt catch { case NonFatal(_) => defaultLimit } } - private[sbt] def apply[@specialized T](f: => T, excludedExceptions: Class[? <: IOException]*): T = + private[sbt] def apply[@specialized T](f: => T, excludedExceptions: Class[? <: Throwable]*): T = apply(f, limit, excludedExceptions: _*) private[sbt] def apply[@specialized T]( f: => T, limit: Int, - excludedExceptions: Class[? <: IOException]*, + excludedExceptions: Class[? <: Throwable]*, ): T = apply(f, limit, 100, excludedExceptions: _*) private[sbt] def apply[@specialized T]( f: => T, limit: Int, sleepInMillis: Long, - excludedExceptions: Class[? <: IOException]*, + excludedExceptions: Class[? <: Throwable]*, ): T = { require(limit >= 1, "limit must be 1 or higher: was: " + limit) def filter(e: Throwable): Boolean = excludedExceptions match {