Merge "Fix racy rebuilding when the winning writer fails"
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
index 30c6d10..8244628 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
@@ -239,25 +239,32 @@
return change;
}
});
- if (!migration.failChangeWrites()) {
- manager.execute();
- } else {
- // Don't even attempt to execute if read-only, it would fail anyway. But
- // do throw an exception to the caller so they know to use the staged
- // results instead of reading from the repo.
- throw new OrmException(NoteDbUpdateManager.CHANGES_READ_ONLY);
- }
- } catch (AbortUpdateException e) {
- // Drop this rebuild; another thread completed it. It's ok to not execute
- // the update in this case, since the object referenced in the Result was
- // flushed to the repo by whatever thread won the race.
} catch (ConflictingUpdateException e) {
// Rethrow as an OrmException so the caller knows to use staged results.
// Strictly speaking they are not completely up to date, but result we
// send to the caller is the same as if this rebuild had executed before
// the other thread.
throw new OrmException(e.getMessage());
+ } catch (AbortUpdateException e) {
+ if (NoteDbChangeState.parse(changeId, newNoteDbState).isUpToDate(
+ manager.getChangeRepo().cmds.getRepoRefCache(),
+ manager.getAllUsersRepo().cmds.getRepoRefCache())) {
+ // If the state in ReviewDb matches NoteDb at this point, it means
+ // another thread successfully completed this rebuild. It's ok to not
+ // execute the update in this case, since the object referenced in the
+ // Result was flushed to the repo by whatever thread won the race.
+ return r;
+ }
+ // If the state doesn't match, that means another thread attempted this
+ // rebuild, but failed. Fall through and try to update the ref again.
}
+ if (migration.failChangeWrites()) {
+ // Don't even attempt to execute if read-only, it would fail anyway. But
+ // do throw an exception to the caller so they know to use the staged
+ // results instead of reading from the repo.
+ throw new OrmException(NoteDbUpdateManager.CHANGES_READ_ONLY);
+ }
+ manager.execute();
return r;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
index d960bda..4a7a781 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
@@ -204,6 +204,19 @@
return id.get().equals(draftIds.get(accountId));
}
+ boolean isUpToDate(RefCache changeRepoRefs, RefCache draftsRepoRefs)
+ throws IOException {
+ if (!isChangeUpToDate(changeRepoRefs)) {
+ return false;
+ }
+ for (Account.Id accountId : draftIds.keySet()) {
+ if (!areDraftsUpToDate(draftsRepoRefs, accountId)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@VisibleForTesting
Change.Id getChangeId() {
return changeId;