Roll back batch ref updates on uncaught throwables

Prior to this change, batch ref updates were only rolled back when
exceptions were caught.

Attempt to roll back local ref updates for uncaught exceptions too.

Bug: Issue 297890487
Change-Id: I4b5754a4306593e341fd0b9c0a260bc03a8fee7b
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java
index 02ae01c..241e733 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidator.java
@@ -157,14 +157,21 @@
     try (CloseableSet<AutoCloseable> locks = new CloseableSet<>()) {
       final List<RefPair> finalRefsToUpdate = compareAndGetLatestLocalRefs(refsToUpdate, locks);
       delegateUpdate.invoke();
+      boolean sharedDbUpdateSucceeded = false;
       try {
         updateSharedRefDb(batchRefUpdate.getCommands().stream(), finalRefsToUpdate);
+        sharedDbUpdateSucceeded = true;
       } catch (Exception e) {
-        List<ReceiveCommand> receiveCommands = batchRefUpdate.getCommands();
         logger.atWarning().withCause(e).log(
-            "Batch ref-update failing because of failure during the global refdb update. Set all commands Result to LOCK_FAILURE [%d]",
-            receiveCommands.size());
-        rollback(delegateUpdateRollback, finalRefsToUpdate, receiveCommands);
+            "Batch ref-update failed because of failure during the global refdb update.");
+      } finally {
+        if (!sharedDbUpdateSucceeded) {
+          List<ReceiveCommand> receiveCommands = batchRefUpdate.getCommands();
+          logger.atWarning().log(
+              "Batch ref-update failed, set all commands Result to LOCK_FAILURE [%d]",
+              commands.size());
+          rollback(delegateUpdateRollback, finalRefsToUpdate, receiveCommands);
+        }
       }
     } catch (OutOfSyncException e) {
       List<ReceiveCommand> receiveCommands = batchRefUpdate.getCommands();
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidatorTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidatorTest.java
index 2c4a1ea..e5d3409 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidatorTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/BatchRefUpdateValidatorTest.java
@@ -15,11 +15,13 @@
 package com.gerritforge.gerrit.globalrefdb.validation;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 import static java.util.Collections.singletonList;
 import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -180,6 +182,35 @@
   }
 
   @Test
+  public void shouldRollbackWhenSharedRefUpdateCompareAndPutThrowsUncaughtThrowable()
+      throws Exception {
+    String REF_NAME = "refs/changes/01/1/meta";
+    BatchRefUpdate batchRefUpdate =
+        newBatchUpdate(singletonList(new ReceiveCommand(A, B, REF_NAME, UPDATE)));
+    BatchRefUpdateValidator batchRefUpdateValidator =
+        getRefValidatorForEnforcement(A_TEST_PROJECT_NAME, tmpRefEnforcement);
+
+    doReturn(SharedRefEnforcement.EnforcePolicy.REQUIRED)
+        .when(batchRefUpdateValidator.refEnforcement)
+        .getPolicy(A_TEST_PROJECT_NAME, REF_NAME);
+    doReturn(true).when(sharedRefDatabase).isUpToDate(any(), any());
+
+    doThrow(TestError.class).when(sharedRefDatabase).compareAndPut(any(), any(), any());
+
+    assertThrows(
+        TestError.class,
+        () ->
+            batchRefUpdateValidator.executeBatchUpdateWithValidation(
+                batchRefUpdate, () -> execute(batchRefUpdate), rollbackFunction));
+
+    verify(rollbackFunction).invoke(any());
+    List<ReceiveCommand> commands = batchRefUpdate.getCommands();
+    assertThat(commands.size()).isEqualTo(1);
+    commands.forEach(
+        (command) -> assertThat(command.getResult()).isEqualTo(ReceiveCommand.Result.LOCK_FAILURE));
+  }
+
+  @Test
   public void shouldRollbackRefUpdateWhenRefDbIsNotUpdated() throws Exception {
     String REF_NAME = "refs/changes/01/1/meta";
     BatchRefUpdate batchRefUpdate =
@@ -266,4 +297,6 @@
   public String testBranch() {
     return "branch_" + nameRule.getMethodName();
   }
+
+  private static class TestError extends Error {}
 }