MergeOp: Move most of setMerged body into a transaction

All the DB operations in this method operate on a single commit, so
make it a transaction on this commit. This has the advantage in
googlesource.com's DB implementation of making the various updates
asynchronous and hopefully improving latency.

Allow various exceptions to propagate out of the inner method calls
and fail the transaction. This means we will not necessarily forge
ahead with the update in cases where we would have before, but there
were a fair number of ignored errors that could lead to inconsistent
state. This hopefully cuts down on the number of possible
inconsistencies.

Change-Id: Ie8a18185ba4b5d9cd936dd4963b68d0ae9579d5a
This commit is contained in:
Dave Borowitz
2013-03-19 16:37:02 -07:00
parent ad01fd9b8b
commit e71b1c8c78

View File

@@ -711,8 +711,6 @@ public class MergeOp {
} }
private void updateChangeStatus(final List<Change> submitted) { private void updateChangeStatus(final List<Change> submitted) {
List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
for (final Change c : submitted) { for (final Change c : submitted) {
final CodeReviewCommit commit = commits.get(c.getId()); final CodeReviewCommit commit = commits.get(c.getId());
final CommitMergeStatus s = commit != null ? commit.statusCode : null; final CommitMergeStatus s = commit != null ? commit.statusCode : null;
@@ -725,23 +723,19 @@ public class MergeOp {
final String txt = s.getMessage(); final String txt = s.getMessage();
try {
switch (s) { switch (s) {
case CLEAN_MERGE: { case CLEAN_MERGE:
setMerged(c, message(c, txt)); setMerged(c, message(c, txt));
merged.add(commit);
break; break;
}
case CLEAN_REBASE: case CLEAN_REBASE:
case CLEAN_PICK: { case CLEAN_PICK:
setMerged(c, message(c, txt + " as " + commit.name())); setMerged(c, message(c, txt + " as " + commit.name()));
merged.add(commit);
break; break;
}
case ALREADY_MERGED: case ALREADY_MERGED:
setMerged(c, null); setMerged(c, null);
merged.add(commit);
break; break;
case PATH_CONFLICT: case PATH_CONFLICT:
@@ -751,20 +745,21 @@ public class MergeOp {
case INVALID_PROJECT_CONFIGURATION: case INVALID_PROJECT_CONFIGURATION:
case INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND: case INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND:
case INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT: case INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT:
case SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN: { case SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN:
setNew(c, message(c, txt)); setNew(c, message(c, txt));
break; break;
}
case MISSING_DEPENDENCY: { case MISSING_DEPENDENCY:
potentiallyStillSubmittable.add(commit); potentiallyStillSubmittable.add(commit);
break; break;
}
default: default:
setNew(c, message(c, "Unspecified merge failure: " + s.name())); setNew(c, message(c, "Unspecified merge failure: " + s.name()));
break; break;
} }
} catch (OrmException err) {
log.warn("Error updating change status for " + c.getId(), err);
}
} }
} }
@@ -880,7 +875,11 @@ public class MergeOp {
return m; return m;
} }
private void setMerged(final Change c, final ChangeMessage msg) { private void setMerged(final Change c, final ChangeMessage msg)
throws OrmException {
try {
db.changes().beginTransaction(c.getId());
// We must pull the patchset out of commits, because the patchset ID is // We must pull the patchset out of commits, because the patchset ID is
// modified when using the cherry-pick merge strategy. // modified when using the cherry-pick merge strategy.
CodeReviewCommit commit = commits.get(c.getId()); CodeReviewCommit commit = commits.get(c.getId());
@@ -888,8 +887,10 @@ public class MergeOp {
setMergedPatchSet(c.getId(), merged); setMergedPatchSet(c.getId(), merged);
PatchSetApproval submitter = saveApprovals(c, merged); PatchSetApproval submitter = saveApprovals(c, merged);
addMergedMessage(submitter, msg); addMergedMessage(submitter, msg);
sendMergedEmail(c, submitter);
db.commit();
sendMergedEmail(c, submitter);
if (submitter != null) { if (submitter != null) {
try { try {
hooks.doChangeMergedHook(c, hooks.doChangeMergedHook(c,
@@ -899,10 +900,13 @@ public class MergeOp {
log.error("Cannot run hook for submitted patch set " + c.getId(), ex); log.error("Cannot run hook for submitted patch set " + c.getId(), ex);
} }
} }
} finally {
db.rollback();
}
} }
private void setMergedPatchSet(Change.Id changeId, final PatchSet.Id merged) { private void setMergedPatchSet(Change.Id changeId, final PatchSet.Id merged)
try { throws OrmException {
db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() { db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
@Override @Override
public Change update(Change c) { public Change update(Change c) {
@@ -925,13 +929,10 @@ public class MergeOp {
return c; return c;
} }
}); });
} catch (OrmConcurrencyException err) {
} catch (OrmException err) {
log.warn("Cannot update change status", err);
}
} }
private PatchSetApproval saveApprovals(Change c, PatchSet.Id merged) { private PatchSetApproval saveApprovals(Change c, PatchSet.Id merged)
throws OrmException {
// Flatten out all existing approvals based upon the current // Flatten out all existing approvals based upon the current
// permissions. Once the change is closed the approvals are // permissions. Once the change is closed the approvals are
// not updated at presentation view time, so we need to make. // not updated at presentation view time, so we need to make.
@@ -966,23 +967,18 @@ public class MergeOp {
db.patchSetApprovals().update(approvals); db.patchSetApprovals().update(approvals);
db.patchSetApprovals().deleteKeys(toDelete); db.patchSetApprovals().deleteKeys(toDelete);
} catch (NoSuchChangeException err) { } catch (NoSuchChangeException err) {
log.warn("Cannot normalize approvals for change " + changeId, err); throw new OrmException(err);
} catch (OrmException err) {
log.warn("Cannot normalize approvals for change " + changeId, err);
} }
return submitter; return submitter;
} }
private void addMergedMessage(PatchSetApproval submitter, ChangeMessage msg) { private void addMergedMessage(PatchSetApproval submitter, ChangeMessage msg)
throws OrmException {
if (msg != null) { if (msg != null) {
if (submitter != null && msg.getAuthor() == null) { if (submitter != null && msg.getAuthor() == null) {
msg.setAuthor(submitter.getAccountId()); msg.setAuthor(submitter.getAccountId());
} }
try {
db.changeMessages().insert(Collections.singleton(msg)); db.changeMessages().insert(Collections.singleton(msg));
} catch (OrmException err) {
log.warn("Cannot store message on change", err);
}
} }
} }