[2.x] Retry on HTTP 5xx during dependency resolution (#8903)

This commit is contained in:
Michał Pawlik 2026-03-14 07:24:20 +01:00 committed by Eugene Yokota
parent 1bd5a5e409
commit 29d2653bf6
2 changed files with 51 additions and 6 deletions

View File

@ -144,12 +144,7 @@ object ResolutionRun {
resolveTask.io.attempt
.flatMap {
case Left(e: ResolutionError) =>
val hasConnectionTimeouts = e.errors.exists {
case err: CantDownloadModule =>
err.perRepositoryErrors.exists(_.contains("Connection timed out"))
case _ => false
}
if (hasConnectionTimeouts)
if (isTransientResolutionError(e))
if (attempt + 1 >= maxAttempts) {
log.error(s"Failed, maximum iterations ($maxAttempts) reached")
Task.point(Left(e))
@ -280,4 +275,16 @@ object ResolutionRun {
}
private lazy val retryScheduler = ThreadUtil.fixedScheduledThreadPool(1)
private[internal] def isTransientResolutionError(e: ResolutionError): Boolean =
e.errors.exists {
case err: CantDownloadModule => isTimeout(err) || isServerError(err)
case _ => false
}
private def isTimeout(err: CantDownloadModule): Boolean =
err.perRepositoryErrors.exists(_.contains("Connection timed out"))
private def isServerError(err: CantDownloadModule): Boolean =
err.perRepositoryErrors.exists(_.contains("Server returned HTTP response code: 5"))
}

View File

@ -0,0 +1,38 @@
package lmcoursier.internal
import coursier.core.{ Module, ModuleName, Organization, Resolution }
import coursier.error.ResolutionError.CantDownloadModule
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
class ResolutionRunSpec extends AnyFunSuite with Matchers:
private def cantDownload(errors: String*): CantDownloadModule =
new CantDownloadModule(
Resolution(),
Module(Organization("org"), ModuleName("mod"), Map.empty),
"1.0",
errors.toSeq
)
test("503 is a transient resolution error"):
val err = cantDownload(
"Server returned HTTP response code: 503 for URL: https://repo.example.com/org/mod/1.0/mod-1.0.pom"
)
ResolutionRun.isTransientResolutionError(err) shouldBe true
test("500 is a transient resolution error"):
val err = cantDownload(
"Server returned HTTP response code: 500 for URL: https://repo.example.com/org/mod/1.0/mod-1.0.pom"
)
ResolutionRun.isTransientResolutionError(err) shouldBe true
test("connection timeout is a transient resolution error"):
val err = cantDownload("Connection timed out")
ResolutionRun.isTransientResolutionError(err) shouldBe true
test("404 is not a transient resolution error"):
val err = cantDownload(
"Server returned HTTP response code: 404 for URL: https://repo.example.com/org/mod/1.0/mod-1.0.pom"
)
ResolutionRun.isTransientResolutionError(err) shouldBe false