Merge "Add fluent interface for retrying actions"
diff --git a/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 5396e1c..a36a923 100644
--- a/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -59,7 +59,6 @@
import com.google.gerrit.server.mail.send.DeleteKeySender;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryHelper.ActionType;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -207,10 +206,10 @@
AccountResource rsrc, List<PGPPublicKeyRing> keyRings, Collection<Fingerprint> toRemove)
throws RestApiException, PGPException, IOException {
try {
- retryHelper.execute(
- ActionType.ACCOUNT_UPDATE,
- () -> tryStoreKeys(rsrc, keyRings, toRemove),
- LockFailureException.class::isInstance);
+ retryHelper
+ .accountUpdate("storeGpgKeys", () -> tryStoreKeys(rsrc, keyRings, toRemove))
+ .retryOn(LockFailureException.class::isInstance)
+ .call();
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
Throwables.throwIfInstanceOf(e, RestApiException.class);
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index bd32c0b..b8ecb23 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -126,8 +126,9 @@
import com.google.gerrit.server.restapi.change.ChangesCollection;
import com.google.gerrit.server.restapi.project.ProjectsCollection;
import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryHelper.Action;
-import com.google.gerrit.server.update.RetryHelper.ActionType;
+import com.google.gerrit.server.update.RetryableAction;
+import com.google.gerrit.server.update.RetryableAction.Action;
+import com.google.gerrit.server.update.RetryableAction.ActionType;
import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.util.http.CacheHeaders;
@@ -814,27 +815,22 @@
ActionType actionType,
Action<T> action)
throws Exception {
+ RetryableAction<T> retryableAction = globals.retryHelper.action(actionType, caller, action);
AtomicReference<Optional<String>> traceId = new AtomicReference<>(Optional.empty());
- RetryHelper.Options.Builder retryOptionsBuilder = RetryHelper.options().caller(caller);
if (!traceContext.isTracing()) {
// enable automatic retry with tracing in case of non-recoverable failure
- retryOptionsBuilder =
- retryOptionsBuilder
- .retryWithTrace(t -> !(t instanceof RestApiException))
- .onAutoTrace(
- autoTraceId -> {
- traceId.set(Optional.of(autoTraceId));
+ retryableAction
+ .retryWithTrace(t -> !(t instanceof RestApiException))
+ .onAutoTrace(
+ autoTraceId -> {
+ traceId.set(Optional.of(autoTraceId));
- // Include details of the request into the trace.
- traceRequestData(req);
- });
+ // Include details of the request into the trace.
+ traceRequestData(req);
+ });
}
try {
- // ExceptionHookImpl controls on which exceptions we retry.
- // The passed in exceptionPredicate allows to define additional exceptions on which retry
- // should happen, but here we have none (hence pass in "t -> false" as exceptionPredicate).
- return globals.retryHelper.execute(
- actionType, action, retryOptionsBuilder.build(), t -> false);
+ return retryableAction.call();
} finally {
// If auto-tracing got triggered due to a non-recoverable failure, also trace the rest of
// this request. This means logging is forced for all further log statements and the logs are
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 1caee58..272389f 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -41,8 +41,7 @@
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryHelper.Action;
-import com.google.gerrit.server.update.RetryHelper.ActionType;
+import com.google.gerrit.server.update.RetryableAction.Action;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -421,8 +420,10 @@
private Optional<AccountState> executeAccountUpdate(Action<Optional<AccountState>> action)
throws IOException, ConfigInvalidException {
try {
- return retryHelper.execute(
- ActionType.ACCOUNT_UPDATE, action, LockFailureException.class::isInstance);
+ return retryHelper
+ .accountUpdate("updateAccount", action)
+ .retryOn(LockFailureException.class::isInstance)
+ .call();
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
Throwables.throwIfInstanceOf(e, IOException.class);
diff --git a/java/com/google/gerrit/server/account/Emails.java b/java/com/google/gerrit/server/account/Emails.java
index 76c22cf..78cc59f 100644
--- a/java/com/google/gerrit/server/account/Emails.java
+++ b/java/com/google/gerrit/server/account/Emails.java
@@ -29,8 +29,7 @@
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryHelper.Action;
-import com.google.gerrit.server.update.RetryHelper.ActionType;
+import com.google.gerrit.server.update.RetryableAction.Action;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -85,7 +84,9 @@
return accounts;
}
- return executeIndexQuery(() -> queryProvider.get().byPreferredEmail(email).stream())
+ return executeIndexQuery(
+ "queryAccountsByPreferredEmail",
+ () -> queryProvider.get().byPreferredEmail(email).stream())
.map(a -> a.account().id())
.collect(toImmutableSet());
}
@@ -105,6 +106,7 @@
Arrays.stream(emails).filter(e -> !result.containsKey(e)).collect(toImmutableList());
if (!emailsToBackfill.isEmpty()) {
executeIndexQuery(
+ "queryAccountsByPreferredEmails",
() -> queryProvider.get().byPreferredEmail(emailsToBackfill).entries().stream())
.forEach(e -> result.put(e.getKey(), e.getValue().account().id()));
}
@@ -139,10 +141,12 @@
return u;
}
- private <T> T executeIndexQuery(Action<T> action) {
+ private <T> T executeIndexQuery(String actionName, Action<T> action) {
try {
- return retryHelper.execute(
- ActionType.INDEX_QUERY, action, StorageException.class::isInstance);
+ return retryHelper
+ .indexQuery(actionName, action)
+ .retryOn(StorageException.class::isInstance)
+ .call();
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new StorageException(e);
diff --git a/java/com/google/gerrit/server/change/ChangeCleanupRunner.java b/java/com/google/gerrit/server/change/ChangeCleanupRunner.java
index 0299f10..6cf7a8f 100644
--- a/java/com/google/gerrit/server/change/ChangeCleanupRunner.java
+++ b/java/com/google/gerrit/server/change/ChangeCleanupRunner.java
@@ -79,11 +79,14 @@
// abandonInactiveOpenChanges skips failures instead of throwing, so retrying will never
// actually happen. For the purposes of this class that is fine: they'll get tried again the
// next time the scheduled task is run.
- retryHelper.execute(
- updateFactory -> {
- abandonUtil.abandonInactiveOpenChanges(updateFactory);
- return null;
- });
+ retryHelper
+ .changeUpdate(
+ "abandonInactiveOpenChanges",
+ updateFactory -> {
+ abandonUtil.abandonInactiveOpenChanges(updateFactory);
+ return null;
+ })
+ .call();
} catch (RestApiException | UpdateException e) {
logger.atSevere().withCause(e).log("Failed to cleanup changes.");
}
diff --git a/java/com/google/gerrit/server/change/ConsistencyChecker.java b/java/com/google/gerrit/server/change/ConsistencyChecker.java
index 513f5ca..0f03c88 100644
--- a/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -170,26 +170,29 @@
public Result check(ChangeNotes notes, @Nullable FixInput f) {
requireNonNull(notes);
try {
- return retryHelper.execute(
- buf -> {
- try {
- reset();
- this.updateFactory = buf;
- this.notes = notes;
- fix = f;
- checkImpl();
- return result();
- } finally {
- if (rw != null) {
- rw.getObjectReader().close();
- rw.close();
- oi.close();
- }
- if (repo != null) {
- repo.close();
- }
- }
- });
+ return retryHelper
+ .changeUpdate(
+ "checkChangeConsistency",
+ buf -> {
+ try {
+ reset();
+ this.updateFactory = buf;
+ this.notes = notes;
+ fix = f;
+ checkImpl();
+ return result();
+ } finally {
+ if (rw != null) {
+ rw.getObjectReader().close();
+ rw.close();
+ oi.close();
+ }
+ if (repo != null) {
+ repo.close();
+ }
+ }
+ })
+ .call();
} catch (RestApiException e) {
return logAndReturnOneProblem(e, notes, "Error checking change: " + e.getMessage());
} catch (UpdateException e) {
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 5155925..3791de8 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -167,8 +167,7 @@
import com.google.gerrit.server.update.RepoContext;
import com.google.gerrit.server.update.RepoOnlyOp;
import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryHelper.Action;
-import com.google.gerrit.server.update.RetryHelper.ActionType;
+import com.google.gerrit.server.update.RetryableAction.Action;
import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.LabelVote;
import com.google.gerrit.server.util.MagicBranch;
@@ -3258,122 +3257,130 @@
// TODO(dborowitz): Combine this BatchUpdate with the main one in
// handleRegularCommands
try {
- retryHelper.execute(
- updateFactory -> {
- try (BatchUpdate bu =
- updateFactory.create(projectState.getNameKey(), user, TimeUtil.nowTs());
- ObjectInserter ins = repo.newObjectInserter();
- ObjectReader reader = ins.newReader();
- RevWalk rw = new RevWalk(reader)) {
- bu.setRepository(repo, rw, ins);
- // TODO(dborowitz): Teach BatchUpdate to ignore missing changes.
+ retryHelper
+ .changeUpdate(
+ "autoCloseChanges",
+ updateFactory -> {
+ try (BatchUpdate bu =
+ updateFactory.create(projectState.getNameKey(), user, TimeUtil.nowTs());
+ ObjectInserter ins = repo.newObjectInserter();
+ ObjectReader reader = ins.newReader();
+ RevWalk rw = new RevWalk(reader)) {
+ bu.setRepository(repo, rw, ins);
+ // TODO(dborowitz): Teach BatchUpdate to ignore missing changes.
- RevCommit newTip = rw.parseCommit(cmd.getNewId());
- BranchNameKey branch = BranchNameKey.create(project.getNameKey(), refName);
+ RevCommit newTip = rw.parseCommit(cmd.getNewId());
+ BranchNameKey branch = BranchNameKey.create(project.getNameKey(), refName);
- rw.reset();
- rw.sort(RevSort.REVERSE);
- rw.markStart(newTip);
- if (!ObjectId.zeroId().equals(cmd.getOldId())) {
- rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
- }
+ rw.reset();
+ rw.sort(RevSort.REVERSE);
+ rw.markStart(newTip);
+ if (!ObjectId.zeroId().equals(cmd.getOldId())) {
+ rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
+ }
- Map<Change.Key, ChangeNotes> byKey = null;
- List<ReplaceRequest> replaceAndClose = new ArrayList<>();
+ Map<Change.Key, ChangeNotes> byKey = null;
+ List<ReplaceRequest> replaceAndClose = new ArrayList<>();
- int existingPatchSets = 0;
- int newPatchSets = 0;
- SubmissionId submissionId = null;
- COMMIT:
- for (RevCommit c; (c = rw.next()) != null; ) {
- rw.parseBody(c);
+ int existingPatchSets = 0;
+ int newPatchSets = 0;
+ SubmissionId submissionId = null;
+ COMMIT:
+ for (RevCommit c; (c = rw.next()) != null; ) {
+ rw.parseBody(c);
- for (Ref ref :
- receivePackRefCache.tipsFromObjectId(c.copy(), RefNames.REFS_CHANGES)) {
- PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
- Optional<ChangeNotes> notes = getChangeNotes(psId.changeId());
- if (notes.isPresent() && notes.get().getChange().getDest().equals(branch)) {
- if (submissionId == null) {
- submissionId = new SubmissionId(notes.get().getChange());
+ for (Ref ref :
+ receivePackRefCache.tipsFromObjectId(c.copy(), RefNames.REFS_CHANGES)) {
+ PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
+ Optional<ChangeNotes> notes = getChangeNotes(psId.changeId());
+ if (notes.isPresent() && notes.get().getChange().getDest().equals(branch)) {
+ if (submissionId == null) {
+ submissionId = new SubmissionId(notes.get().getChange());
+ }
+ existingPatchSets++;
+ bu.addOp(
+ notes.get().getChangeId(), setPrivateOpFactory.create(false, null));
+ bu.addOp(
+ psId.changeId(),
+ mergedByPushOpFactory.create(
+ requestScopePropagator,
+ psId,
+ submissionId,
+ refName,
+ newTip.getId().getName()));
+ continue COMMIT;
+ }
}
- existingPatchSets++;
- bu.addOp(notes.get().getChangeId(), setPrivateOpFactory.create(false, null));
+
+ for (String changeId : c.getFooterLines(FooterConstants.CHANGE_ID)) {
+ if (byKey == null) {
+ byKey =
+ executeIndexQuery(
+ "queryOpenChangesByKeyByBranch",
+ () -> openChangesByKeyByBranch(branch));
+ }
+
+ ChangeNotes onto = byKey.get(Change.key(changeId.trim()));
+ if (onto != null) {
+ newPatchSets++;
+ // Hold onto this until we're done with the walk, as the call to
+ // req.validate below calls isMergedInto which resets the walk.
+ ReplaceRequest req =
+ new ReplaceRequest(onto.getChangeId(), c, cmd, false);
+ req.notes = onto;
+ replaceAndClose.add(req);
+ continue COMMIT;
+ }
+ }
+ }
+
+ for (ReplaceRequest req : replaceAndClose) {
+ Change.Id id = req.notes.getChangeId();
+ if (!req.validateNewPatchSetForAutoClose()) {
+ logger.atFine().log("Not closing %s because validation failed", id);
+ continue;
+ }
+ if (submissionId == null) {
+ submissionId = new SubmissionId(req.notes.getChange());
+ }
+ req.addOps(bu, null);
+ bu.addOp(id, setPrivateOpFactory.create(false, null));
bu.addOp(
- psId.changeId(),
- mergedByPushOpFactory.create(
- requestScopePropagator,
- psId,
- submissionId,
- refName,
- newTip.getId().getName()));
- continue COMMIT;
- }
- }
-
- for (String changeId : c.getFooterLines(FooterConstants.CHANGE_ID)) {
- if (byKey == null) {
- byKey = executeIndexQuery(() -> openChangesByKeyByBranch(branch));
+ id,
+ mergedByPushOpFactory
+ .create(
+ requestScopePropagator,
+ req.psId,
+ submissionId,
+ refName,
+ newTip.getId().getName())
+ .setPatchSetProvider(req.replaceOp::getPatchSet));
+ bu.addOp(id, new ChangeProgressOp(progress));
+ ids.add(id);
}
- ChangeNotes onto = byKey.get(Change.key(changeId.trim()));
- if (onto != null) {
- newPatchSets++;
- // Hold onto this until we're done with the walk, as the call to
- // req.validate below calls isMergedInto which resets the walk.
- ReplaceRequest req = new ReplaceRequest(onto.getChangeId(), c, cmd, false);
- req.notes = onto;
- replaceAndClose.add(req);
- continue COMMIT;
- }
+ logger.atFine().log(
+ "Auto-closing %d changes with existing patch sets and %d with new patch sets",
+ existingPatchSets, newPatchSets);
+ bu.execute();
+ } catch (IOException | StorageException | PermissionBackendException e) {
+ logger.atSevere().withCause(e).log("Failed to auto-close changes");
+ return null;
}
- }
- for (ReplaceRequest req : replaceAndClose) {
- Change.Id id = req.notes.getChangeId();
- if (!req.validateNewPatchSetForAutoClose()) {
- logger.atFine().log("Not closing %s because validation failed", id);
- continue;
- }
- if (submissionId == null) {
- submissionId = new SubmissionId(req.notes.getChange());
- }
- req.addOps(bu, null);
- bu.addOp(id, setPrivateOpFactory.create(false, null));
- bu.addOp(
- id,
- mergedByPushOpFactory
- .create(
- requestScopePropagator,
- req.psId,
- submissionId,
- refName,
- newTip.getId().getName())
- .setPatchSetProvider(req.replaceOp::getPatchSet));
- bu.addOp(id, new ChangeProgressOp(progress));
- ids.add(id);
- }
+ // If we are here, we didn't throw UpdateException. Record the result.
+ // The ordering is indeterminate due to the HashSet; unfortunately, Change.Id
+ // doesn't
+ // fit into TreeSet.
+ ids.stream()
+ .forEach(id -> resultChangeIds.add(ResultChangeIds.Key.AUTOCLOSED, id));
- logger.atFine().log(
- "Auto-closing %d changes with existing patch sets and %d with new patch sets",
- existingPatchSets, newPatchSets);
- bu.execute();
- } catch (IOException | StorageException | PermissionBackendException e) {
- logger.atSevere().withCause(e).log("Failed to auto-close changes");
- return null;
- }
-
- // If we are here, we didn't throw UpdateException. Record the result.
- // The ordering is indeterminate due to the HashSet; unfortunately, Change.Id doesn't
- // fit into TreeSet.
- ids.stream().forEach(id -> resultChangeIds.add(ResultChangeIds.Key.AUTOCLOSED, id));
-
- return null;
- },
+ return null;
+ })
// Use a multiple of the default timeout to account for inner retries that may otherwise
// eat up the whole timeout so that no time is left to retry this outer action.
- RetryHelper.options()
- .timeout(retryHelper.getDefaultTimeout(ActionType.CHANGE_UPDATE).multipliedBy(5))
- .build());
+ .defaultTimeoutMultiplier(5)
+ .call();
} catch (RestApiException e) {
logger.atSevere().withCause(e).log("Can't insert patchset");
} catch (UpdateException e) {
@@ -3390,10 +3397,12 @@
}
}
- private <T> T executeIndexQuery(Action<T> action) {
+ private <T> T executeIndexQuery(String actionName, Action<T> action) {
try (TraceTimer traceTimer = newTimer("executeIndexQuery")) {
- return retryHelper.execute(
- ActionType.INDEX_QUERY, action, StorageException.class::isInstance);
+ return retryHelper
+ .indexQuery(actionName, action)
+ .retryOn(StorageException.class::isInstance)
+ .call();
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new StorageException(e);
diff --git a/java/com/google/gerrit/server/group/db/GroupsUpdate.java b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
index 7f1ba6a..5069331 100644
--- a/java/com/google/gerrit/server/group/db/GroupsUpdate.java
+++ b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
@@ -309,10 +309,10 @@
InternalGroupCreation groupCreation, InternalGroupUpdate groupUpdate)
throws IOException, ConfigInvalidException, DuplicateKeyException {
try {
- return retryHelper.execute(
- RetryHelper.ActionType.GROUP_UPDATE,
- () -> createGroupInNoteDb(groupCreation, groupUpdate),
- LockFailureException.class::isInstance);
+ return retryHelper
+ .groupUpdate("createGroup", () -> createGroupInNoteDb(groupCreation, groupUpdate))
+ .retryOn(LockFailureException.class::isInstance)
+ .call();
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
Throwables.throwIfInstanceOf(e, IOException.class);
@@ -349,10 +349,10 @@
AccountGroup.UUID groupUuid, InternalGroupUpdate groupUpdate)
throws IOException, ConfigInvalidException, DuplicateKeyException, NoSuchGroupException {
try {
- return retryHelper.execute(
- RetryHelper.ActionType.GROUP_UPDATE,
- () -> updateGroupInNoteDb(groupUuid, groupUpdate),
- LockFailureException.class::isInstance);
+ return retryHelper
+ .groupUpdate("updateGroup", () -> updateGroupInNoteDb(groupUuid, groupUpdate))
+ .retryOn(LockFailureException.class::isInstance)
+ .call();
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
Throwables.throwIfInstanceOf(e, IOException.class);
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index 158db1c..71d8c15 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -156,11 +156,14 @@
* @param message {@link MailMessage} to process
*/
public void process(MailMessage message) throws RestApiException, UpdateException {
- retryHelper.execute(
- buf -> {
- processImpl(buf, message);
- return null;
- });
+ retryHelper
+ .changeUpdate(
+ "processCommentsReceivedByEmail",
+ buf -> {
+ processImpl(buf, message);
+ return null;
+ })
+ .call();
}
private void processImpl(BatchUpdate.Factory buf, MailMessage message)
diff --git a/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java b/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
index f6aba34..a70f902 100644
--- a/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
@@ -52,7 +52,6 @@
import com.google.gerrit.server.query.change.ProjectPredicate;
import com.google.gerrit.server.query.change.RefPredicate;
import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryHelper.ActionType;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -264,16 +263,16 @@
try {
List<ChangeData> queryResult =
- retryHelper.execute(
- ActionType.INDEX_QUERY,
- () -> {
- // Execute the query.
- return changeQueryProvider
- .get()
- .setRequestedFields(ChangeField.CHANGE, ChangeField.PATCH_SET)
- .query(and(basePredicate, or(predicates)));
- },
- StorageException.class::isInstance);
+ retryHelper
+ .indexQuery(
+ "projectsConsistencyCheckerQueryChanges",
+ () ->
+ changeQueryProvider
+ .get()
+ .setRequestedFields(ChangeField.CHANGE, ChangeField.PATCH_SET)
+ .query(and(basePredicate, or(predicates))))
+ .retryOn(StorageException.class::isInstance)
+ .call();
// Result for this query that we want to return to the client.
List<ChangeInfo> autoCloseableChangesByBranch = new ArrayList<>();
@@ -282,32 +281,35 @@
// Skip changes that we have already processed, either by this query or by
// earlier queries.
if (seenChanges.add(autoCloseableChange.getId())) {
- retryHelper.execute(
- ActionType.CHANGE_UPDATE,
- () -> {
- // Auto-close by change
- if (changeIdToMergedSha1.containsKey(autoCloseableChange.change().getKey())) {
- autoCloseableChangesByBranch.add(
- changeJson(
- fix, changeIdToMergedSha1.get(autoCloseableChange.change().getKey()))
- .format(autoCloseableChange));
- return null;
- }
+ retryHelper
+ .changeUpdate(
+ "projectsConsistencyCheckerAutoCloseChanges",
+ () -> {
+ // Auto-close by change
+ if (changeIdToMergedSha1.containsKey(autoCloseableChange.change().getKey())) {
+ autoCloseableChangesByBranch.add(
+ changeJson(
+ fix,
+ changeIdToMergedSha1.get(autoCloseableChange.change().getKey()))
+ .format(autoCloseableChange));
+ return null;
+ }
- // Auto-close by commit
- for (ObjectId patchSetSha1 :
- autoCloseableChange.patchSets().stream()
- .map(PatchSet::commitId)
- .collect(toSet())) {
- if (mergedSha1s.contains(patchSetSha1)) {
- autoCloseableChangesByBranch.add(
- changeJson(fix, patchSetSha1).format(autoCloseableChange));
- break;
- }
- }
- return null;
- },
- StorageException.class::isInstance);
+ // Auto-close by commit
+ for (ObjectId patchSetSha1 :
+ autoCloseableChange.patchSets().stream()
+ .map(PatchSet::commitId)
+ .collect(toSet())) {
+ if (mergedSha1s.contains(patchSetSha1)) {
+ autoCloseableChangesByBranch.add(
+ changeJson(fix, patchSetSha1).format(autoCloseableChange));
+ break;
+ }
+ }
+ return null;
+ })
+ .retryOn(StorageException.class::isInstance)
+ .call();
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
index 7c88ab3..213fcd5 100644
--- a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
@@ -38,8 +38,7 @@
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.query.change.ProjectPredicate;
import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryHelper.Action;
-import com.google.gerrit.server.update.RetryHelper.ActionType;
+import com.google.gerrit.server.update.RetryableAction.Action;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -132,6 +131,7 @@
// cheaper than ref visibility filtering and reachability computation.
List<ChangeData> changes =
executeIndexQuery(
+ "queryChangesByProjectCommitWithLimit1",
() ->
queryProvider
.get()
@@ -151,7 +151,10 @@
Arrays.stream(commit.getParents())
.map(parent -> new CommitPredicate(parent.getId().getName()))
.collect(toImmutableList())));
- changes = executeIndexQuery(() -> queryProvider.get().enforceVisibility(true).query(pred));
+ changes =
+ executeIndexQuery(
+ "queryChangesByProjectCommit",
+ () -> queryProvider.get().enforceVisibility(true).query(pred));
Set<Ref> branchesForCommitParents = new HashSet<>(changes.size());
for (ChangeData cd : changes) {
@@ -175,10 +178,12 @@
return reachable.fromRefs(project, repo, commit, refs);
}
- private <T> T executeIndexQuery(Action<T> action) {
+ private <T> T executeIndexQuery(String actionName, Action<T> action) {
try {
- return retryHelper.execute(
- ActionType.INDEX_QUERY, action, StorageException.class::isInstance);
+ return retryHelper
+ .indexQuery(actionName, action)
+ .retryOn(StorageException.class::isInstance)
+ .call();
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new StorageException(e);
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index f4159dc..64c4578 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -77,7 +77,6 @@
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryHelper.ActionType;
import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.Inject;
@@ -483,41 +482,39 @@
}
RetryTracker retryTracker = new RetryTracker();
- retryHelper.execute(
- updateFactory -> {
- long attempt = retryTracker.lastAttemptNumber + 1;
- boolean isRetry = attempt > 1;
- if (isRetry) {
- logger.atFine().log("Retrying, attempt #%d; skipping merged changes", attempt);
- this.ts = TimeUtil.nowTs();
- openRepoManager();
- }
- this.commitStatus = new CommitStatus(cs, isRetry);
- if (checkSubmitRules) {
- logger.atFine().log("Checking submit rules and state");
- checkSubmitRulesAndState(cs, isRetry);
- } else {
- logger.atFine().log("Bypassing submit rules");
- bypassSubmitRules(cs, isRetry);
- }
- try {
- integrateIntoHistory(cs);
- } catch (IntegrationException e) {
- logger.atWarning().withCause(e).log("Error from integrateIntoHistory");
- throw new ResourceConflictException(e.getMessage(), e);
- }
- return null;
- },
- RetryHelper.options()
- .listener(retryTracker)
- // Up to the entire submit operation is retried, including possibly many projects.
- // Multiply the timeout by the number of projects we're actually attempting to
- // submit.
- .timeout(
- retryHelper
- .getDefaultTimeout(ActionType.CHANGE_UPDATE)
- .multipliedBy(cs.projects().size()))
- .build());
+ retryHelper
+ .changeUpdate(
+ "integrateIntoHistory",
+ updateFactory -> {
+ long attempt = retryTracker.lastAttemptNumber + 1;
+ boolean isRetry = attempt > 1;
+ if (isRetry) {
+ logger.atFine().log("Retrying, attempt #%d; skipping merged changes", attempt);
+ this.ts = TimeUtil.nowTs();
+ openRepoManager();
+ }
+ this.commitStatus = new CommitStatus(cs, isRetry);
+ if (checkSubmitRules) {
+ logger.atFine().log("Checking submit rules and state");
+ checkSubmitRulesAndState(cs, isRetry);
+ } else {
+ logger.atFine().log("Bypassing submit rules");
+ bypassSubmitRules(cs, isRetry);
+ }
+ try {
+ integrateIntoHistory(cs);
+ } catch (IntegrationException e) {
+ logger.atWarning().withCause(e).log("Error from integrateIntoHistory");
+ throw new ResourceConflictException(e.getMessage(), e);
+ }
+ return null;
+ })
+ .listener(retryTracker)
+ // Up to the entire submit operation is retried, including possibly many projects.
+ // Multiply the timeout by the number of projects we're actually attempting to
+ // submit.
+ .defaultTimeoutMultiplier(cs.projects().size())
+ .call();
if (projects > 1) {
topicMetrics.topicSubmissionsCompleted.increment();
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index f3636c9..65fd97e 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -28,12 +28,10 @@
import com.github.rholder.retry.WaitStrategy;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.metrics.Counter3;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
@@ -44,6 +42,9 @@
import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.plugincontext.PluginSetContext;
+import com.google.gerrit.server.update.RetryableAction.Action;
+import com.google.gerrit.server.update.RetryableAction.ActionType;
+import com.google.gerrit.server.update.RetryableChangeAction.ChangeAction;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.time.Duration;
@@ -59,26 +60,6 @@
public class RetryHelper {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- @FunctionalInterface
- public interface ChangeAction<T> {
- T call(BatchUpdate.Factory batchUpdateFactory) throws Exception;
- }
-
- @FunctionalInterface
- public interface Action<T> {
- T call() throws Exception;
- }
-
- public enum ActionType {
- ACCOUNT_UPDATE,
- CHANGE_UPDATE,
- GROUP_UPDATE,
- INDEX_QUERY,
- PLUGIN_UPDATE,
- REST_READ_REQUEST,
- REST_WRITE_REQUEST,
- }
-
/**
* Options for retrying a single operation.
*
@@ -194,10 +175,6 @@
return new AutoValue_RetryHelper_Options.Builder();
}
- private static Options defaults() {
- return options().build();
- }
-
private final Metrics metrics;
private final BatchUpdate.Factory updateFactory;
private final PluginSetContext<ExceptionHook> exceptionHooks;
@@ -253,48 +230,100 @@
this.retryWithTraceOnFailure = cfg.getBoolean("retry", "retryWithTraceOnFailure", false);
}
- public Duration getDefaultTimeout(ActionType actionType) {
+ /**
+ * Creates an action that is executed with retrying when called.
+ *
+ * @param actionType the type of the action, used as metric bucket
+ * @param actionName the name of the action, used as metric bucket
+ * @param action the action that should be executed
+ * @return the retryable action, callers need to call {@link RetryableAction#call()} to execute
+ * the action
+ */
+ public <T> RetryableAction<T> action(ActionType actionType, String actionName, Action<T> action) {
+ return new RetryableAction<>(this, actionType, actionName, action);
+ }
+
+ /**
+ * Creates an action for updating an account that is executed with retrying when called.
+ *
+ * @param actionName the name of the action, used as metric bucket
+ * @param action the action that should be executed
+ * @return the retryable action, callers need to call {@link RetryableAction#call()} to execute
+ * the action
+ */
+ public <T> RetryableAction<T> accountUpdate(String actionName, Action<T> action) {
+ return new RetryableAction<>(this, ActionType.ACCOUNT_UPDATE, actionName, action);
+ }
+
+ /**
+ * Creates an action for updating a change that is executed with retrying when called.
+ *
+ * @param actionName the name of the action, used as metric bucket
+ * @param action the action that should be executed
+ * @return the retryable action, callers need to call {@link RetryableAction#call()} to execute
+ * the action
+ */
+ public <T> RetryableAction<T> changeUpdate(String actionName, Action<T> action) {
+ return new RetryableAction<>(this, ActionType.CHANGE_UPDATE, actionName, action);
+ }
+
+ /**
+ * Creates an action for updating a change that is executed with retrying when called.
+ *
+ * <p>The change action gets a {@link BatchUpdate.Factory} provided that can be used to update the
+ * change.
+ *
+ * @param actionName the name of the action, used as metric bucket
+ * @param changeAction the action that should be executed
+ * @return the retryable action, callers need to call {@link RetryableChangeAction#call()} to
+ * execute the action
+ */
+ public <T> RetryableChangeAction<T> changeUpdate(
+ String actionName, ChangeAction<T> changeAction) {
+ return new RetryableChangeAction<>(this, updateFactory, actionName, changeAction);
+ }
+
+ /**
+ * Creates an action for updating a group that is executed with retrying when called.
+ *
+ * @param actionName the name of the action, used as metric bucket
+ * @param action the action that should be executed
+ * @return the retryable action, callers need to call {@link RetryableAction#call()} to execute
+ * the action
+ */
+ public <T> RetryableAction<T> groupUpdate(String actionName, Action<T> action) {
+ return new RetryableAction<>(this, ActionType.GROUP_UPDATE, actionName, action);
+ }
+
+ /**
+ * Creates an action for updating of plugin-specific data that is executed with retrying when
+ * called.
+ *
+ * @param actionName the name of the action, used as metric bucket
+ * @param action the action that should be executed
+ * @return the retryable action, callers need to call {@link RetryableAction#call()} to execute
+ * the action
+ */
+ public <T> RetryableAction<T> pluginUpdate(String actionName, Action<T> action) {
+ return new RetryableAction<>(this, ActionType.PLUGIN_UPDATE, actionName, action);
+ }
+
+ /**
+ * Creates an action for querying an index that is executed with retrying when called.
+ *
+ * @param actionName the name of the action, used as metric bucket
+ * @param action the action that should be executed
+ * @return the retryable action, callers need to call {@link RetryableAction#call()} to execute
+ * the action
+ */
+ public <T> RetryableAction<T> indexQuery(String actionName, Action<T> action) {
+ return new RetryableAction<>(this, ActionType.INDEX_QUERY, actionName, action);
+ }
+
+ Duration getDefaultTimeout(ActionType actionType) {
return defaultTimeouts.get(actionType);
}
- public <T> T execute(
- ActionType actionType, Action<T> action, Predicate<Throwable> exceptionPredicate)
- throws Exception {
- return execute(actionType, action, defaults(), exceptionPredicate);
- }
-
- public <T> T execute(
- ActionType actionType,
- Action<T> action,
- Options opts,
- Predicate<Throwable> exceptionPredicate)
- throws Exception {
- try {
- return executeWithAttemptAndTimeoutCount(actionType, action, opts, exceptionPredicate);
- } catch (Throwable t) {
- Throwables.throwIfUnchecked(t);
- Throwables.throwIfInstanceOf(t, Exception.class);
- throw new IllegalStateException(t);
- }
- }
-
- public <T> T execute(ChangeAction<T> changeAction) throws RestApiException, UpdateException {
- return execute(changeAction, defaults());
- }
-
- public <T> T execute(ChangeAction<T> changeAction, Options opts)
- throws RestApiException, UpdateException {
- try {
- return execute(
- ActionType.CHANGE_UPDATE, () -> changeAction.call(updateFactory), opts, t -> false);
- } catch (Throwable t) {
- Throwables.throwIfUnchecked(t);
- Throwables.throwIfInstanceOf(t, UpdateException.class);
- Throwables.throwIfInstanceOf(t, RestApiException.class);
- throw new UpdateException(t);
- }
- }
-
/**
* Executes an action and records the number of attempts and the timeout as metrics.
*
@@ -306,7 +335,7 @@
* @throws Throwable any error or exception that made the action fail, callers are expected to
* catch and inspect this Throwable to decide carefully whether it should be re-thrown
*/
- private <T> T executeWithAttemptAndTimeoutCount(
+ <T> T execute(
ActionType actionType,
Action<T> action,
Options opts,
diff --git a/java/com/google/gerrit/server/update/RetryableAction.java b/java/com/google/gerrit/server/update/RetryableAction.java
new file mode 100644
index 0000000..3ee079c
--- /dev/null
+++ b/java/com/google/gerrit/server/update/RetryableAction.java
@@ -0,0 +1,168 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.update;
+
+import static java.util.Objects.requireNonNull;
+
+import com.github.rholder.retry.RetryListener;
+import com.google.common.base.Throwables;
+import com.google.gerrit.server.ExceptionHook;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * An action that is executed with retrying.
+ *
+ * <p>Instances of this class are created via {@link RetryHelper} (see {@link
+ * RetryHelper#action(ActionType, String, Action)}, {@link RetryHelper#accountUpdate(String,
+ * Action)}, {@link RetryHelper#changeUpdate(String, Action)}, {@link
+ * RetryHelper#groupUpdate(String, Action)}, {@link RetryHelper#pluginUpdate(String, Action)},
+ * {@link RetryHelper#indexQuery(String, Action)}).
+ *
+ * <p>Which exceptions cause a retry is controlled by {@link ExceptionHook#shouldRetry(Throwable)}.
+ * In addition callers can specify additional exception that should cause a retry via {@link
+ * #retryOn(Predicate)}.
+ */
+public class RetryableAction<T> {
+ public enum ActionType {
+ ACCOUNT_UPDATE,
+ CHANGE_UPDATE,
+ GROUP_UPDATE,
+ INDEX_QUERY,
+ PLUGIN_UPDATE,
+ REST_READ_REQUEST,
+ REST_WRITE_REQUEST,
+ }
+
+ @FunctionalInterface
+ public interface Action<T> {
+ T call() throws Exception;
+ }
+
+ private final RetryHelper retryHelper;
+ private final ActionType actionType;
+ private final Action<T> action;
+ private final RetryHelper.Options.Builder options = RetryHelper.options();
+ private final List<Predicate<Throwable>> exceptionPredicates = new ArrayList<>();
+
+ RetryableAction(
+ RetryHelper retryHelper, ActionType actionType, String actionName, Action<T> action) {
+ this.retryHelper = requireNonNull(retryHelper, "retryHelper");
+ this.actionType = requireNonNull(actionType, "actionType");
+ this.action = requireNonNull(action, "action");
+ options.caller(requireNonNull(actionName, "actionName"));
+ }
+
+ /**
+ * Adds an additional condition that should trigger retries.
+ *
+ * <p>For some exceptions retrying is enabled globally (see {@link
+ * ExceptionHook#shouldRetry(Throwable)}). Conditions for those exceptions do not need to be
+ * specified here again.
+ *
+ * <p>This method can be invoked multiple times to add further conditions that should trigger
+ * retries.
+ *
+ * @param exceptionPredicate predicate that decides if the action should be retried for a given
+ * exception
+ * @return this instance to enable chaining of calls
+ */
+ public RetryableAction<T> retryOn(Predicate<Throwable> exceptionPredicate) {
+ exceptionPredicates.add(exceptionPredicate);
+ return this;
+ }
+
+ /**
+ * Sets a condition that should trigger auto-retry with tracing.
+ *
+ * <p>This condition is only relevant if an exception occurs that doesn't trigger (normal) retry.
+ *
+ * <p>Auto-retry with tracing automatically captures traces for unexpected exceptions so that they
+ * can be investigated.
+ *
+ * <p>Every call of this method overwrites any previously set condition for auto-retry with
+ * tracing.
+ *
+ * @param exceptionPredicate predicate that decides if the action should be retried with tracing
+ * for a given exception
+ * @return this instance to enable chaining of calls
+ */
+ public RetryableAction<T> retryWithTrace(Predicate<Throwable> exceptionPredicate) {
+ options.retryWithTrace(exceptionPredicate);
+ return this;
+ }
+
+ /**
+ * Sets a callback that is invoked when auto-retry with tracing is triggered.
+ *
+ * <p>Via the callback callers can find out with trace ID was used for the retry.
+ *
+ * <p>Every call of this method overwrites any previously set trace ID consumer.
+ *
+ * @param traceIdConsumer trace ID consumer
+ * @return this instance to enable chaining of calls
+ */
+ public RetryableAction<T> onAutoTrace(Consumer<String> traceIdConsumer) {
+ options.onAutoTrace(traceIdConsumer);
+ return this;
+ }
+
+ /**
+ * Sets a listener that is invoked when the action is retried.
+ *
+ * <p>Every call of this method overwrites any previously set listener.
+ *
+ * @param retryListener retry listener
+ * @return this instance to enable chaining of calls
+ */
+ public RetryableAction<T> listener(RetryListener retryListener) {
+ options.listener(retryListener);
+ return this;
+ }
+
+ /**
+ * Increases the default timeout by the given multiplier.
+ *
+ * <p>Every call of this method overwrites any previously set timeout.
+ *
+ * @param multiplier multiplier for the default timeout
+ * @return this instance to enable chaining of calls
+ */
+ public RetryableAction<T> defaultTimeoutMultiplier(int multiplier) {
+ options.timeout(retryHelper.getDefaultTimeout(actionType).multipliedBy(multiplier));
+ return this;
+ }
+
+ /**
+ * Executes this action with retry.
+ *
+ * @return the result of the action
+ */
+ public T call() throws Exception {
+ try {
+ return retryHelper.execute(
+ actionType,
+ action,
+ options.build(),
+ t -> exceptionPredicates.stream().anyMatch(p -> p.test(t)));
+ } catch (Throwable t) {
+ Throwables.throwIfUnchecked(t);
+ Throwables.throwIfInstanceOf(t, Exception.class);
+ throw new IllegalStateException(t);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/update/RetryableChangeAction.java b/java/com/google/gerrit/server/update/RetryableChangeAction.java
new file mode 100644
index 0000000..152db2c
--- /dev/null
+++ b/java/com/google/gerrit/server/update/RetryableChangeAction.java
@@ -0,0 +1,92 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.update;
+
+import com.github.rholder.retry.RetryListener;
+import com.google.common.base.Throwables;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * A change action that is executed with retrying.
+ *
+ * <p>Instances of this class are created via {@link RetryHelper#changeUpdate(String,
+ * ChangeAction)}.
+ *
+ * <p>In contrast to normal {@link RetryableAction.Action}s that are called via {@link
+ * RetryableAction} {@link ChangeAction}s get a {@link BatchUpdate.Factory} provided.
+ *
+ * <p>In addition when a change action is called any exception that is not an unchecked exception
+ * and neither {@link UpdateException} nor {@link RestApiException} get wrapped into an {@link
+ * UpdateException}.
+ */
+public class RetryableChangeAction<T> extends RetryableAction<T> {
+ @FunctionalInterface
+ public interface ChangeAction<T> {
+ T call(BatchUpdate.Factory batchUpdateFactory) throws Exception;
+ }
+
+ RetryableChangeAction(
+ RetryHelper retryHelper,
+ BatchUpdate.Factory updateFactory,
+ String actionName,
+ ChangeAction<T> changeAction) {
+ super(
+ retryHelper, ActionType.CHANGE_UPDATE, actionName, () -> changeAction.call(updateFactory));
+ }
+
+ @Override
+ public RetryableChangeAction<T> retryOn(Predicate<Throwable> exceptionPredicate) {
+ super.retryOn(exceptionPredicate);
+ return this;
+ }
+
+ @Override
+ public RetryableChangeAction<T> retryWithTrace(Predicate<Throwable> exceptionPredicate) {
+ super.retryWithTrace(exceptionPredicate);
+ return this;
+ }
+
+ @Override
+ public RetryableChangeAction<T> onAutoTrace(Consumer<String> traceIdConsumer) {
+ super.onAutoTrace(traceIdConsumer);
+ return this;
+ }
+
+ @Override
+ public RetryableChangeAction<T> listener(RetryListener retryListener) {
+ super.listener(retryListener);
+ return this;
+ }
+
+ @Override
+ public RetryableChangeAction<T> defaultTimeoutMultiplier(int multiplier) {
+ super.defaultTimeoutMultiplier(multiplier);
+ return this;
+ }
+
+ @Override
+ public T call() throws UpdateException, RestApiException {
+ try {
+ return super.call();
+ } catch (Throwable t) {
+ Throwables.throwIfUnchecked(t);
+ Throwables.throwIfInstanceOf(t, UpdateException.class);
+ Throwables.throwIfInstanceOf(t, RestApiException.class);
+ throw new UpdateException(t);
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
index c502c79..3c066a3 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
@@ -154,16 +154,20 @@
AtomicInteger afterUpdateReposCalledCount = new AtomicInteger();
String result =
- retryHelper.execute(
- batchUpdateFactory -> {
- try (BatchUpdate bu = newBatchUpdate(batchUpdateFactory)) {
- bu.addOp(
- id,
- new UpdateRefAndAddMessageOp(updateRepoCalledCount, updateChangeCalledCount));
- bu.execute(new ConcurrentWritingListener(afterUpdateReposCalledCount));
- }
- return "Done";
- });
+ retryHelper
+ .changeUpdate(
+ "testUpdateRefAndAddMessageOp",
+ batchUpdateFactory -> {
+ try (BatchUpdate bu = newBatchUpdate(batchUpdateFactory)) {
+ bu.addOp(
+ id,
+ new UpdateRefAndAddMessageOp(
+ updateRepoCalledCount, updateChangeCalledCount));
+ bu.execute(new ConcurrentWritingListener(afterUpdateReposCalledCount));
+ }
+ return "Done";
+ })
+ .call();
assertThat(result).isEqualTo("Done");
assertThat(updateRepoCalledCount.get()).isEqualTo(2);
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index f1a3622..eac4cd9 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit f1a36220e0ef31fb024de9ad589dfdfdf301c295
+Subproject commit eac4cd97cb5818ff471c64914fb4e342baf28c05