Merge "Treat NoteDb LOCK_FAILURE as HTTP 409"
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 9504564..b7bcafa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -38,6 +38,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.InMemoryInserter;
 import com.google.gerrit.server.git.InsertedObject;
+import com.google.gerrit.server.git.LockFailureException;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.update.ChainedReceiveCommands;
 import com.google.gwtorm.server.OrmConcurrencyException;
@@ -476,11 +477,18 @@
     or.cmds.addTo(bru);
     bru.setAllowNonFastForwards(true);
     bru.execute(or.rw, NullProgressMonitor.INSTANCE);
+
+    boolean lockFailure = false;
     for (ReceiveCommand cmd : bru.getCommands()) {
-      if (cmd.getResult() != ReceiveCommand.Result.OK) {
+      if (cmd.getResult() == ReceiveCommand.Result.LOCK_FAILURE) {
+        lockFailure = true;
+      } else if (cmd.getResult() != ReceiveCommand.Result.OK) {
         throw new IOException("Update failed: " + bru);
       }
     }
+    if (lockFailure) {
+      throw new LockFailureException("Update failed with one or more lock failures: " + bru);
+    }
   }
 
   private void addCommands() throws OrmException, IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java
index d05f32e..37a25af 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java
@@ -49,6 +49,7 @@
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.InsertedObject;
+import com.google.gerrit.server.git.LockFailureException;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
@@ -560,7 +561,8 @@
     }
   }
 
-  private void executeNoteDbUpdates(List<ChangeTask> tasks) throws IOException {
+  private void executeNoteDbUpdates(List<ChangeTask> tasks)
+      throws ResourceConflictException, IOException {
     // Aggregate together all NoteDb ref updates from the ops we executed,
     // possibly in parallel. Each task had its own NoteDbUpdateManager instance
     // with its own thread-local copy of the repo(s), but each of those was just
@@ -623,18 +625,21 @@
       }
     } catch (IOException e) {
       if (tasks.stream().allMatch(t -> t.storage == PrimaryStorage.REVIEW_DB)) {
-        // Ignore all errors trying to update NoteDb at this point. We've
-        // already written the NoteDbChangeStates to ReviewDb, which means
-        // if any state is out of date it will be rebuilt the next time it
-        // is needed.
+        // Ignore all errors trying to update NoteDb at this point. We've already written the
+        // NoteDbChangeStates to ReviewDb, which means if any state is out of date it will be
+        // rebuilt the next time it is needed.
+        //
         // Always log even without RequestId.
         log.debug("Ignoring NoteDb update error after ReviewDb write", e);
-      } else {
-        // We can't prove it's safe to ignore the error, either because some
-        // change had NOTE_DB primary, or a task failed before determining the
-        // primary storage.
-        throw e;
+
+        // Otherwise, we can't prove it's safe to ignore the error, either because some change had
+        // NOTE_DB primary, or a task failed before determining the primary storage.
+      } else if (e instanceof LockFailureException) {
+        // LOCK_FAILURE is a special case indicating there was a conflicting write to a meta ref,
+        // although it happened too late for us to produce anything but a generic error message.
+        throw new ResourceConflictException("Updating change failed due to conflicting write", e);
       }
+      throw e;
     }
   }