Merge branch 'stable-3.8' into stable-3.9

* stable-3.8:
  Roll back batch ref updates on uncaught throwables
Change-Id: I1ec214b0e4e28ba058f243b0bbf5e9b4017ed825
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 f461a9a..1e45951 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 {}
 }