HttpDownloader: Support scaling of timputs

Currently, the HttpDownloader hard-codes the values for timeouts. However,
depending on network situation, different values might be appropriate.
So support scaling all timeouts by a configurable factor (defaulting
to 1.0).

Change-Id: I84dc64e92d83f4f129a29a5f79874a6d662f1f0b
PiperOrigin-RevId: 245374111
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
index 4c3126d..3114cfd 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -277,6 +277,8 @@
         httpDownloader.setDistdir(ImmutableList.<Path>of());
       }
 
+      httpDownloader.setTimeoutScaling((float) repoOptions.httpTimeoutScaling);
+
       if (repoOptions.repositoryOverrides != null) {
         ImmutableMap.Builder<RepositoryName, PathFragment> builder = ImmutableMap.builder();
         for (RepositoryOverride override : repoOptions.repositoryOverrides) {
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java
index 8e46218..b7e1fa4 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java
@@ -70,6 +70,14 @@
   public List<PathFragment> experimentalDistdir;
 
   @Option(
+      name = "http_timeout_scaling",
+      defaultValue = "1.0",
+      documentationCategory = OptionDocumentationCategory.BAZEL_CLIENT_OPTIONS,
+      effectTags = {OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION},
+      help = "Scale all timeouts related to http downloads by the given factor")
+  public double httpTimeoutScaling;
+
+  @Option(
     name = "override_repository",
     defaultValue = "null",
     allowMultiple = true,
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnector.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnector.java
index 9ec4592..be29680 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnector.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnector.java
@@ -63,13 +63,28 @@
   private final EventHandler eventHandler;
   private final ProxyHelper proxyHelper;
   private final Sleeper sleeper;
+  private final float timeoutScaling;
 
   HttpConnector(
-      Locale locale, EventHandler eventHandler, ProxyHelper proxyHelper, Sleeper sleeper) {
+      Locale locale,
+      EventHandler eventHandler,
+      ProxyHelper proxyHelper,
+      Sleeper sleeper,
+      float timeoutScaling) {
     this.locale = locale;
     this.eventHandler = eventHandler;
     this.proxyHelper = proxyHelper;
     this.sleeper = sleeper;
+    this.timeoutScaling = timeoutScaling;
+  }
+
+  HttpConnector(
+      Locale locale, EventHandler eventHandler, ProxyHelper proxyHelper, Sleeper sleeper) {
+    this(locale, eventHandler, proxyHelper, sleeper, 1.0f);
+  }
+
+  private int scale(int unscaled) {
+    return Math.round(unscaled * timeoutScaling);
   }
 
   URLConnection connect(
@@ -85,7 +100,7 @@
     List<Throwable> suppressions = new ArrayList<>();
     int retries = 0;
     int redirects = 0;
-    int connectTimeout = MIN_CONNECT_TIMEOUT_MS;
+    int connectTimeout = scale(MIN_CONNECT_TIMEOUT_MS);
     while (true) {
       HttpURLConnection connection = null;
       try {
@@ -105,7 +120,7 @@
         }
         connection.setConnectTimeout(connectTimeout);
         // The read timeout is always large because it stays in effect after this method.
-        connection.setReadTimeout(READ_TIMEOUT_MS);
+        connection.setReadTimeout(scale(READ_TIMEOUT_MS));
         // Java tries to abstract HTTP error responses for us. We don't want that. So we're going
         // to try and undo any IOException that doesn't appear to be a legitimate I/O exception.
         int code;
@@ -184,7 +199,7 @@
         int timeout = IntMath.pow(2, retries) * MIN_RETRY_DELAY_MS;
         if (e instanceof SocketTimeoutException) {
           eventHandler.handle(Event.progress("Timeout connecting to " + url));
-          connectTimeout = Math.min(connectTimeout * 2, MAX_CONNECT_TIMEOUT_MS);
+          connectTimeout = Math.min(connectTimeout * 2, scale(MAX_CONNECT_TIMEOUT_MS));
           // If we got connect timeout, we're already doing exponential backoff, so no point
           // in sleeping too.
           timeout = 1;
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java
index e0fd9b3..010249a 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java
@@ -54,6 +54,7 @@
 
   protected final RepositoryCache repositoryCache;
   private List<Path> distdir = ImmutableList.of();
+  private float timeoutScaling = 1.0f;
 
   public HttpDownloader(RepositoryCache repositoryCache) {
     this.repositoryCache = repositoryCache;
@@ -63,6 +64,10 @@
     this.distdir = ImmutableList.copyOf(distdir);
   }
 
+  public void setTimeoutScaling(float timeoutScaling) {
+    this.timeoutScaling = timeoutScaling;
+  }
+
   /**
    * Downloads file to disk and returns path.
    *
@@ -181,7 +186,8 @@
     Sleeper sleeper = new JavaSleeper();
     Locale locale = Locale.getDefault();
     ProxyHelper proxyHelper = new ProxyHelper(clientEnv);
-    HttpConnector connector = new HttpConnector(locale, eventHandler, proxyHelper, sleeper);
+    HttpConnector connector =
+        new HttpConnector(locale, eventHandler, proxyHelper, sleeper, timeoutScaling);
     ProgressInputStream.Factory progressInputStreamFactory =
         new ProgressInputStream.Factory(locale, clock, eventHandler);
     HttpStream.Factory httpStreamFactory = new HttpStream.Factory(progressInputStreamFactory);