diff options
author | Shawn Pearce <sop@google.com> | 2014-03-07 14:44:16 -0800 |
---|---|---|
committer | Shawn Pearce <sop@google.com> | 2014-03-07 15:32:25 -0800 |
commit | 577738dbdf137c82b896fde32746a51be7beb274 (patch) | |
tree | 65540554aebea15ebca5218febe9c8158ea16999 | |
parent | 97a9e182fee026df9d5fed331d900f7f967e86cc (diff) |
Fix race condition between MergeOp and MergeabilityChecker
MergeabilityChecker is a GitReferenceUpdatedListener, which gets
called after the reference updates but before the change status
is written to the database. To reduce latency during merge the
rescan operation is sent to a background thread pool.
It is possible for the background thread to scan the database
and observe the recently merged change with status at SUBMITTED,
as the scan can run before the status is changed to MERGED.
If the MergeOp thread writes the status to the database before
Mergeable finishes the check, then Mergeable will replace the
status with SUBMITTED, making the change appear to be unsubmitted.
One part of a fix is to move the reference update notification
after writing the change status to the database. This will have
the least surprising semantics for plugins and any other code
wired into the listener. Commits visible in the reference will
now show as merged if the listener queries for them.
Change-Id: Ib8d034c3f2eb79716b5ebc1ed426a0e8dbb0861a
-rw-r--r-- | gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java | 126 |
1 files changed, 65 insertions, 61 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java index 4568d1a556..0919aa0f67 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java @@ -249,11 +249,14 @@ public class MergeOp { } final SubmitStrategy strategy = createStrategy(submitType); preMerge(strategy, toMerge.get(submitType)); - updateBranch(strategy, branchUpdate); + RefUpdate update = updateBranch(strategy, branchUpdate); reopen = true; updateChangeStatus(toSubmit.get(submitType)); updateSubscriptions(toSubmit.get(submitType)); + if (update != null) { + fireRefUpdated(update); + } for (final Iterator<CodeReviewCommit> it = potentiallyStillSubmittable.iterator(); it.hasNext();) { @@ -580,77 +583,78 @@ public class MergeOp { return r.type; } - private void updateBranch(final SubmitStrategy strategy, + private RefUpdate updateBranch(final SubmitStrategy strategy, final RefUpdate branchUpdate) throws MergeException { - if ((branchTip == null && mergeTip == null) || branchTip == mergeTip) { + if (branchTip == mergeTip || mergeTip == null) { // nothing to do - return; + return null; } - if (mergeTip != null && (branchTip == null || branchTip != mergeTip)) { - if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) { - try { - ProjectConfig cfg = - new ProjectConfig(destProject.getProject().getNameKey()); - cfg.load(repo, mergeTip); - } catch (Exception e) { - throw new MergeException("Submit would store invalid" - + " project configuration " + mergeTip.name() + " for " - + destProject.getProject().getName(), e); - } - } - - branchUpdate.setRefLogIdent(refLogIdent); - branchUpdate.setForceUpdate(false); - branchUpdate.setNewObjectId(mergeTip); - branchUpdate.setRefLogMessage("merged", true); + if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) { try { - switch (branchUpdate.update(rw)) { - case NEW: - case FAST_FORWARD: - if (branchUpdate.getResult() == RefUpdate.Result.FAST_FORWARD) { - tagCache.updateFastForward(destBranch.getParentKey(), - branchUpdate.getName(), - branchUpdate.getOldObjectId(), - mergeTip); - } + ProjectConfig cfg = + new ProjectConfig(destProject.getProject().getNameKey()); + cfg.load(repo, mergeTip); + } catch (Exception e) { + throw new MergeException("Submit would store invalid" + + " project configuration " + mergeTip.name() + " for " + + destProject.getProject().getName(), e); + } + } - if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) { - projectCache.evict(destProject.getProject()); - destProject = projectCache.get(destProject.getProject().getNameKey()); - repoManager.setProjectDescription( - destProject.getProject().getNameKey(), - destProject.getProject().getDescription()); - } + branchUpdate.setRefLogIdent(refLogIdent); + branchUpdate.setForceUpdate(false); + branchUpdate.setNewObjectId(mergeTip); + branchUpdate.setRefLogMessage("merged", true); + try { + switch (branchUpdate.update(rw)) { + case NEW: + case FAST_FORWARD: + if (branchUpdate.getResult() == RefUpdate.Result.FAST_FORWARD) { + tagCache.updateFastForward(destBranch.getParentKey(), + branchUpdate.getName(), + branchUpdate.getOldObjectId(), + mergeTip); + } - gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate); + if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) { + projectCache.evict(destProject.getProject()); + destProject = projectCache.get(destProject.getProject().getNameKey()); + repoManager.setProjectDescription( + destProject.getProject().getNameKey(), + destProject.getProject().getDescription()); + } - Account account = null; - PatchSetApproval submitter = approvalsUtil.getSubmitter( - db, mergeTip.notes(), mergeTip.getPatchsetId()); - if (submitter != null) { - account = accountCache.get(submitter.getAccountId()).getAccount(); - } - hooks.doRefUpdatedHook(destBranch, branchUpdate, account); - break; + return branchUpdate; - case LOCK_FAILURE: - String msg; - if (strategy.retryOnLockFailure()) { - mergeQueue.recheckAfter(destBranch, LOCK_FAILURE_RETRY_DELAY, - MILLISECONDS); - msg = "will retry"; - } else { - msg = "will not retry"; - } - throw new IOException(branchUpdate.getResult().name() + ", " + msg); - default: - throw new IOException(branchUpdate.getResult().name()); - } - } catch (IOException e) { - throw new MergeException("Cannot update " + branchUpdate.getName(), e); + case LOCK_FAILURE: + String msg; + if (strategy.retryOnLockFailure()) { + mergeQueue.recheckAfter(destBranch, LOCK_FAILURE_RETRY_DELAY, + MILLISECONDS); + msg = "will retry"; + } else { + msg = "will not retry"; + } + throw new IOException(branchUpdate.getResult().name() + ", " + msg); + default: + throw new IOException(branchUpdate.getResult().name()); } + } catch (IOException e) { + throw new MergeException("Cannot update " + branchUpdate.getName(), e); + } + } + + private void fireRefUpdated(RefUpdate branchUpdate) { + gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate); + + Account account = null; + PatchSetApproval submitter = approvalsUtil.getSubmitter( + db, mergeTip.notes(), mergeTip.getPatchsetId()); + if (submitter != null) { + account = accountCache.get(submitter.getAccountId()).getAccount(); } + hooks.doRefUpdatedHook(destBranch, branchUpdate, account); } private void updateChangeStatus(final List<Change> submitted) { |