Prevent action-cache duplicate suppression

Exceptions that are reused between download futures must not be added to
the suppression of themselves.

Fixes #9362

Closes #9376.

PiperOrigin-RevId: 270658205
diff --git a/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java b/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java
index 95f61b5..59d6f5a 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java
@@ -354,13 +354,13 @@
       } catch (IOException e) {
         if (downloadException == null) {
           downloadException = e;
-        } else {
+        } else if (e != downloadException) {
           downloadException.addSuppressed(e);
         }
       } catch (InterruptedException e) {
         if (interruptedException == null) {
           interruptedException = e;
-        } else {
+        } else if (e != interruptedException) {
           interruptedException.addSuppressed(e);
         }
       }
diff --git a/src/test/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCacheTests.java b/src/test/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCacheTests.java
index 141a4dd..d8bc54a 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCacheTests.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCacheTests.java
@@ -717,6 +717,64 @@
   }
 
   @Test
+  public void downloadWithDuplicateIOErrorsDoesNotSuppress() throws Exception {
+    Path stdout = fs.getPath("/execroot/stdout");
+    Path stderr = fs.getPath("/execroot/stderr");
+
+    DefaultRemoteActionCache cache = newTestCache();
+    Digest digest1 = cache.addContents("file1");
+    IOException reusedException = new IOException("reused io exception");
+    Digest digest2 = cache.addException("file2", reusedException);
+    Digest digest3 = cache.addException("file3", reusedException);
+
+    ActionResult result =
+        ActionResult.newBuilder()
+            .setExitCode(0)
+            .addOutputFiles(OutputFile.newBuilder().setPath("file1").setDigest(digest1))
+            .addOutputFiles(OutputFile.newBuilder().setPath("file2").setDigest(digest2))
+            .addOutputFiles(OutputFile.newBuilder().setPath("file3").setDigest(digest3))
+            .build();
+    IOException e =
+        assertThrows(
+            IOException.class,
+            () ->
+                cache.download(
+                    result, execRoot, new FileOutErr(stdout, stderr), outputFilesLocker));
+
+    assertThat(e.getSuppressed()).isEmpty();
+    assertThat(Throwables.getRootCause(e)).hasMessageThat().isEqualTo("reused io exception");
+  }
+
+  @Test
+  public void downloadWithDuplicateInterruptionsDoesNotSuppress() throws Exception {
+    Path stdout = fs.getPath("/execroot/stdout");
+    Path stderr = fs.getPath("/execroot/stderr");
+
+    DefaultRemoteActionCache cache = newTestCache();
+    Digest digest1 = cache.addContents("file1");
+    InterruptedException reusedInterruption = new InterruptedException("reused interruption");
+    Digest digest2 = cache.addException("file2", reusedInterruption);
+    Digest digest3 = cache.addException("file3", reusedInterruption);
+
+    ActionResult result =
+        ActionResult.newBuilder()
+            .setExitCode(0)
+            .addOutputFiles(OutputFile.newBuilder().setPath("file1").setDigest(digest1))
+            .addOutputFiles(OutputFile.newBuilder().setPath("file2").setDigest(digest2))
+            .addOutputFiles(OutputFile.newBuilder().setPath("file3").setDigest(digest3))
+            .build();
+    InterruptedException e =
+        assertThrows(
+            InterruptedException.class,
+            () ->
+                cache.download(
+                    result, execRoot, new FileOutErr(stdout, stderr), outputFilesLocker));
+
+    assertThat(e.getSuppressed()).isEmpty();
+    assertThat(Throwables.getRootCause(e)).hasMessageThat().isEqualTo("reused interruption");
+  }
+
+  @Test
   public void testDownloadWithStdoutStderrOnSuccess() throws Exception {
     // Tests that fetching stdout/stderr as a digest works and that OutErr is still
     // writable afterwards.