BatchUpdate: Create ChangeControl inside transaction

Previously, we were passing in ChangeControls to addOp, which
contained their own defensive copy of the Change object. Callers
already have that ChangeControl instance available, so they might be
tempted to read/write the Change within it inside the body of a
BatchUpdate.Op, which doesn't do what they expect.

Instead, just pass in the Change.Id, and hoist the user setup into the
BatchUpdate creation itself. (This has the side effect of preventing a
single batch update from performing updates on behalf of multiple
users, which is ok.) Push the ChangeControl creation into the
BatchUpdate internals, and use the change that was read in the
transaction rather than depending on a change previously read not in
the transaction and passed in by the caller (via ChangeControl).

This improves consistency in another important way: the Change
instance accessed via ctx.getChangeUpdate().getChange() (aka
ctx.getChangeUpdate().getChangeNotes().getChange()) was read in the
same transaction as the change accessed via ctx.getChange(). (They are
currently still two different instances due to the aforementioned
defensive copy, but that is a quirk we will deal with as we continue
working on notedb update semantics.)

Change-Id: Ifab569aece17a4bcf18b180f2b90b2d5daa3c4ac
This commit is contained in:
Dave Borowitz
2015-10-09 12:00:40 -04:00
parent 9f22f40286
commit a495b26800
12 changed files with 45 additions and 41 deletions

View File

@@ -93,9 +93,10 @@ public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
final String msgTxt, final Account account)
throws RestApiException, UpdateException {
Op op = new Op(msgTxt, account);
Change c = control.getChange();
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
control.getChange().getProject(), TimeUtil.nowTs())) {
u.addOp(control, op).execute();
c.getProject(), control.getCurrentUser(), TimeUtil.nowTs())) {
u.addOp(c.getId(), op).execute();
}
return op.change;
}
@@ -116,7 +117,7 @@ public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
@Override
public void updateChange(ChangeContext ctx) throws OrmException,
ResourceConflictException {
change = ctx.readChange();
change = ctx.getChange();
if (change == null || !change.getStatus().isOpen()) {
throw new ResourceConflictException("change is " + status(change));
} else if (change.getStatus() == Change.Status.DRAFT) {

View File

@@ -215,8 +215,9 @@ public class CherryPickChange {
PatchSet current = db.get().patchSets().get(change.currentPatchSetId());
try (BatchUpdate bu = batchUpdateFactory.create(
db.get(), change.getDest().getParentKey(), TimeUtil.nowTs())) {
bu.addOp(changeControl, inserter
db.get(), change.getDest().getParentKey(), identifiedUser,
TimeUtil.nowTs())) {
bu.addOp(change.getId(), inserter
.setMessage("Uploaded patch set " + newPatchSetId.get() + ".")
.setDraft(current.isDraft())
.setUploader(identifiedUser.getAccountId())

View File

@@ -466,8 +466,10 @@ public class ConsistencyChecker {
PatchSetInserter inserter =
patchSetInserterFactory.create(repo, rw, ctl, commit);
try (BatchUpdate bu = updateFactory.create(
db.get(), change.getDest().getParentKey(), TimeUtil.nowTs())) {
bu.addOp(ctl, inserter.setValidatePolicy(CommitValidators.Policy.NONE)
db.get(), change.getDest().getParentKey(), user.get(),
TimeUtil.nowTs())) {
bu.addOp(change.getId(), inserter
.setValidatePolicy(CommitValidators.Policy.NONE)
.setRunHooks(false)
.setSendMail(false)
.setAllowClosed(true)

View File

@@ -220,7 +220,7 @@ public class PatchSetInserter extends BatchUpdate.Op {
@Override
public void updateChange(ChangeContext ctx) throws OrmException,
InvalidChangeOperationException {
change = ctx.readChange();
change = ctx.getChange();
Change.Id id = change.getId();
final PatchSet.Id currentPatchSetId = change.currentPatchSetId();
if (!change.getStatus().isOpen() && !allowClosed) {

View File

@@ -78,8 +78,8 @@ public class PutTopic implements RestModifyView<ChangeResource, Input>,
Op op = new Op(ctl, input != null ? input : new Input());
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
req.getChange().getProject(), TimeUtil.nowTs())) {
u.addOp(ctl, op);
req.getChange().getProject(), ctl.getCurrentUser(), TimeUtil.nowTs())) {
u.addOp(req.getChange().getId(), op);
u.execute();
}
return Strings.isNullOrEmpty(op.newTopicName)
@@ -102,7 +102,7 @@ public class PutTopic implements RestModifyView<ChangeResource, Input>,
@Override
public void updateChange(ChangeContext ctx) throws OrmException {
change = ctx.readChange();
change = ctx.getChange();
String newTopicName = Strings.nullToEmpty(input.topic);
String oldTopicName = Strings.nullToEmpty(change.getTopic());
if (oldTopicName.equals(newTopicName)) {

View File

@@ -288,8 +288,8 @@ public class RebaseChange {
.setRunHooks(runHooks);
try (BatchUpdate bu = updateFactory.create(
db.get(), change.getDest().getParentKey(), TimeUtil.nowTs())) {
bu.addOp(changeControl, patchSetInserter.setMessage(
db.get(), change.getProject(), uploader, TimeUtil.nowTs())) {
bu.addOp(change.getId(), patchSetInserter.setMessage(
"Patch Set " + patchSetInserter.getPatchSetId().get()
+ ": Patch Set " + patchSetId.get() + " was rebased"));
bu.execute();

View File

@@ -88,8 +88,8 @@ public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
Op op = new Op(input);
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
req.getChange().getProject(), TimeUtil.nowTs())) {
u.addOp(ctl, op).execute();
req.getChange().getProject(), ctl.getCurrentUser(), TimeUtil.nowTs())) {
u.addOp(req.getChange().getId(), op).execute();
}
return json.create(ChangeJson.NO_OPTIONS).format(op.change);
}
@@ -110,7 +110,7 @@ public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
public void updateChange(ChangeContext ctx) throws OrmException,
ResourceConflictException {
caller = (IdentifiedUser) ctx.getUser();
change = ctx.readChange();
change = ctx.getChange();
if (change == null || change.getStatus() != Status.ABANDONED) {
throw new ResourceConflictException("change is " + status(change));
}

View File

@@ -239,8 +239,9 @@ public class ChangeEditUtil {
}
try (BatchUpdate bu = updateFactory.create(
db.get(), change.getDest().getParentKey(), TimeUtil.nowTs())) {
bu.addOp(ctl, inserter
db.get(), change.getProject(), ctl.getCurrentUser(),
TimeUtil.nowTs())) {
bu.addOp(change.getId(), inserter
.setDraft(change.getStatus() == Status.DRAFT ||
basePatchSet.isDraft())
.setMessage(message.toString()));

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.server.git;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
@@ -31,7 +30,6 @@ import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -46,7 +44,6 @@ import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -75,7 +72,7 @@ import java.util.Map;
public class BatchUpdate implements AutoCloseable {
public interface Factory {
public BatchUpdate create(ReviewDb db, Project.NameKey project,
Timestamp when);
CurrentUser user, Timestamp when);
}
public class Context {
@@ -128,8 +125,8 @@ public class BatchUpdate implements AutoCloseable {
return update;
}
public Change readChange() throws OrmException {
return db.changes().get(update.getChange().getId());
public Change getChange() {
return update.getChange();
}
public CurrentUser getUser() {
@@ -155,14 +152,15 @@ public class BatchUpdate implements AutoCloseable {
private final ReviewDb db;
private final GitRepositoryManager repoManager;
private final ChangeIndexer indexer;
private final ChangeControl.GenericFactory changeControlFactory;
private final ChangeUpdate.Factory changeUpdateFactory;
private final GitReferenceUpdated gitRefUpdated;
private final Project.NameKey project;
private final CurrentUser user;
private final Timestamp when;
private final ListMultimap<Change.Id, Op> ops = ArrayListMultimap.create();
private final Map<Change.Id, ChangeControl> changeControls = new HashMap<>();
private final List<CheckedFuture<?, IOException>> indexFutures =
new ArrayList<>();
@@ -175,17 +173,21 @@ public class BatchUpdate implements AutoCloseable {
@AssistedInject
BatchUpdate(GitRepositoryManager repoManager,
ChangeIndexer indexer,
ChangeControl.GenericFactory changeControlFactory,
ChangeUpdate.Factory changeUpdateFactory,
GitReferenceUpdated gitRefUpdated,
@Assisted ReviewDb db,
@Assisted Project.NameKey project,
@Assisted CurrentUser user,
@Assisted Timestamp when) {
this.db = db;
this.repoManager = repoManager;
this.indexer = indexer;
this.changeControlFactory = changeControlFactory;
this.changeUpdateFactory = changeUpdateFactory;
this.gitRefUpdated = gitRefUpdated;
this.project = project;
this.user = user;
this.when = when;
}
@@ -232,14 +234,8 @@ public class BatchUpdate implements AutoCloseable {
return inserter;
}
public BatchUpdate addOp(ChangeControl ctl, Op op) {
Change.Id id = ctl.getChange().getId();
ChangeControl old = changeControls.get(id);
// TODO(dborowitz): Not sure this is guaranteed in general.
checkArgument(old == null || old == ctl,
"mismatched ChangeControls for change %s", id);
public BatchUpdate addOp(Change.Id id, Op op) {
ops.put(id, op);
changeControls.put(id, ctl);
return this;
}
@@ -303,9 +299,10 @@ public class BatchUpdate implements AutoCloseable {
try {
for (Map.Entry<Change.Id, Collection<Op>> e : ops.asMap().entrySet()) {
Change.Id id = e.getKey();
ChangeUpdate update =
changeUpdateFactory.create(changeControls.get(id), when);
db.changes().beginTransaction(id);
Change change = db.changes().get(id);
ChangeControl ctl = changeControlFactory.controlFor(change, user);
ChangeUpdate update = changeUpdateFactory.create(ctl, when);
try {
for (Op op : e.getValue()) {
op.updateChange(new ChangeContext(update));

View File

@@ -72,14 +72,15 @@ public class CherryPick extends SubmitStrategy {
try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
while (!sorted.isEmpty()) {
CodeReviewCommit n = sorted.remove(0);
Change.Id cid = n.change().getId();
if (first && branchTip == null) {
u.addOp(n.getControl(), new CherryPickUnbornRootOp(mergeTip, n));
u.addOp(cid, new CherryPickUnbornRootOp(mergeTip, n));
} else if (n.getParentCount() == 0) {
u.addOp(n.getControl(), new CherryPickRootOp(n));
u.addOp(cid, new CherryPickRootOp(n));
} else if (n.getParentCount() == 1) {
u.addOp(n.getControl(), new CherryPickOneOp(mergeTip, n));
u.addOp(cid, new CherryPickOneOp(mergeTip, n));
} else {
u.addOp(n.getControl(), new CherryPickMultipleParentsOp(mergeTip, n));
u.addOp(cid, new CherryPickMultipleParentsOp(mergeTip, n));
}
first = false;
}

View File

@@ -100,7 +100,8 @@ public abstract class SubmitStrategy {
}
BatchUpdate newBatchUpdate(Timestamp when) {
return batchUpdateFactory.create(db, destBranch.getParentKey(), when)
return batchUpdateFactory
.create(db, destBranch.getParentKey(), caller, when)
.setRepository(repo, rw, inserter);
}
}

View File

@@ -1336,8 +1336,8 @@ public abstract class AbstractQueryChangesTest {
.setRunHooks(false)
.setValidatePolicy(CommitValidators.Policy.NONE);
try (BatchUpdate bu = updateFactory.create(
db, c.getDest().getParentKey(), TimeUtil.nowTs())) {
bu.addOp(ctl, inserter);
db, c.getDest().getParentKey(), user, TimeUtil.nowTs())) {
bu.addOp(c.getId(), inserter);
bu.execute();
}