Skip to content

Commit ccad726

Browse files
committed
Retry downloads to mitigate transient 503 and 504 errors
1 parent 701ee87 commit ccad726

1 file changed

Lines changed: 38 additions & 5 deletions

File tree

sbt-protofetch/src/main/scala/com/coralogix/sbtprotofetch/ProtofetchBinary.scala

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ package com.coralogix.sbtprotofetch
1919
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
2020
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream
2121
import sbt.util.Logger
22-
import sbt.{File, URL}
22+
import sbt.File
2323

2424
import java.io.{BufferedInputStream, IOException, InputStream}
25-
import java.net.URI
25+
import java.net.{HttpURLConnection, URI, URL}
26+
import java.nio.charset.StandardCharsets
2627
import java.nio.file.attribute.{BasicFileAttributes, PosixFilePermission}
2728
import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor}
2829
import java.util
30+
import scala.annotation.tailrec
31+
import scala.util.Random
2932

3033
object ProtofetchBinary {
3134

@@ -66,9 +69,7 @@ object ProtofetchBinary {
6669
delete(targetPath)
6770
}
6871

69-
val stream = url.openStream()
70-
try extract(logger, stream, targetPath)
71-
finally stream.close()
72+
downloadRetrying(url, extract(logger, _, targetPath))
7273

7374
val binary = system.platform match {
7475
case Platform.Linux | Platform.Mac => "protofetch"
@@ -78,6 +79,37 @@ object ProtofetchBinary {
7879
targetPath.resolve("bin").resolve(binary).toFile
7980
}
8081

82+
// Downloading release assets from GitHub often fails with 503 or 504,
83+
// so we want to try a few times before giving up.
84+
// The retry behavior is rather arbitrary, we will make up to 5 attempts
85+
// with a random delay up to 5s between attempts.
86+
// @tailrec
87+
private def downloadRetrying[T](
88+
url: URL,
89+
block: InputStream => T,
90+
retries: Int = 5
91+
): T = {
92+
val connection = url.openConnection()
93+
try {
94+
val stream = connection.getInputStream
95+
try block(stream)
96+
finally stream.close()
97+
} catch {
98+
case e: IOException =>
99+
connection match {
100+
case httpConnection: HttpURLConnection =>
101+
val code = httpConnection.getResponseCode
102+
if (code >= 500 && code < 600 && retries > 0) {
103+
Thread.sleep(Random.nextInt(5000))
104+
downloadRetrying(url, block, retries - 1)
105+
} else {
106+
throw e
107+
}
108+
case _ => throw e
109+
}
110+
}
111+
}
112+
81113
private def delete(path: Path): Unit = {
82114
Files.walkFileTree(
83115
path,
@@ -138,4 +170,5 @@ object ProtofetchBinary {
138170

139171
result
140172
}
173+
141174
}

0 commit comments

Comments
 (0)