BatchUpdate: Support executing multiple updates at once
Execute all repo updates, then all DB updates, etc. Allow callers to provide a listener instance with methods that get called between each phase. This object doesn't receive any Context objects, because each of those is tied to a single Op instance. Callers can keep track of their own state as necessary. Change-Id: Ia69a3e40cae2fd7bd51307e5e161755c19873ca0
This commit is contained in:
parent
f8e35efeed
commit
b009b9d9e2
@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkState;
|
|||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
import com.google.common.util.concurrent.CheckedFuture;
|
import com.google.common.util.concurrent.CheckedFuture;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
@ -239,6 +240,112 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for listening during batch update execution.
|
||||||
|
* <p>
|
||||||
|
* When used during execution of multiple batch updates, the {@code after*}
|
||||||
|
* methods are called after that phase has been completed for <em>all</em> updates.
|
||||||
|
*/
|
||||||
|
public static class Listener {
|
||||||
|
private static final Listener NONE = new Listener();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after updating all repositories and flushing objects but before
|
||||||
|
* updating any refs.
|
||||||
|
*/
|
||||||
|
public void afterUpdateRepos() throws Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called after updating all refs. */
|
||||||
|
public void afterRefUpdates() throws Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called after updating all changes. */
|
||||||
|
public void afterUpdateChanges() throws Exception {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Order getOrder(Collection<BatchUpdate> updates) {
|
||||||
|
Order o = null;
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
if (o == null) {
|
||||||
|
o = u.order;
|
||||||
|
} else if (u.order != o) {
|
||||||
|
throw new IllegalArgumentException("cannot mix execution orders");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void execute(Collection<BatchUpdate> updates, Listener listener)
|
||||||
|
throws UpdateException, RestApiException {
|
||||||
|
if (updates.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Order order = getOrder(updates);
|
||||||
|
switch (order) {
|
||||||
|
case REPO_BEFORE_DB:
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
u.executeUpdateRepo();
|
||||||
|
}
|
||||||
|
listener.afterUpdateRepos();
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
u.executeRefUpdates();
|
||||||
|
}
|
||||||
|
listener.afterRefUpdates();
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
u.executeChangeOps();
|
||||||
|
}
|
||||||
|
listener.afterUpdateChanges();
|
||||||
|
break;
|
||||||
|
case DB_BEFORE_REPO:
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
u.executeChangeOps();
|
||||||
|
}
|
||||||
|
listener.afterUpdateChanges();
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
u.executeUpdateRepo();
|
||||||
|
}
|
||||||
|
listener.afterUpdateRepos();
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
u.executeRefUpdates();
|
||||||
|
}
|
||||||
|
listener.afterRefUpdates();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("invalid execution order: " + order);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CheckedFuture<?, IOException>> indexFutures = new ArrayList<>();
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
indexFutures.addAll(u.indexFutures);
|
||||||
|
}
|
||||||
|
ChangeIndexer.allAsList(indexFutures).get();
|
||||||
|
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
if (u.batchRefUpdate != null) {
|
||||||
|
// Fire ref update events only after all mutations are finished, since
|
||||||
|
// callers may assume a patch set ref being created means the change
|
||||||
|
// was created, or a branch advancing meaning some changes were
|
||||||
|
// closed.
|
||||||
|
u.gitRefUpdated.fire(u.project, u.batchRefUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BatchUpdate u : updates) {
|
||||||
|
u.executePostOps();
|
||||||
|
}
|
||||||
|
} catch (UpdateException | RestApiException e) {
|
||||||
|
// Propagate REST API exceptions thrown by operations; they commonly throw
|
||||||
|
// exceptions like ResourceConflictException to indicate an atomic update
|
||||||
|
// failure.
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Throwables.propagateIfPossible(e);
|
||||||
|
throw new UpdateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final ReviewDb db;
|
private final ReviewDb db;
|
||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
@ -357,59 +464,35 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void execute() throws UpdateException, RestApiException {
|
public void execute() throws UpdateException, RestApiException {
|
||||||
try {
|
execute(Listener.NONE);
|
||||||
switch (order) {
|
|
||||||
case REPO_BEFORE_DB:
|
|
||||||
executeRefUpdates();
|
|
||||||
executeChangeOps();
|
|
||||||
break;
|
|
||||||
case DB_BEFORE_REPO:
|
|
||||||
executeChangeOps();
|
|
||||||
executeRefUpdates();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("invalid execution order: " + order);
|
|
||||||
}
|
|
||||||
|
|
||||||
reindexChanges();
|
|
||||||
|
|
||||||
if (batchRefUpdate != null) {
|
|
||||||
// Fire ref update events only after all mutations are finished, since
|
|
||||||
// callers may assume a patch set ref being created means the change was
|
|
||||||
// created, or a branch advancing meaning some changes were closed.
|
|
||||||
gitRefUpdated.fire(project, batchRefUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
executePostOps();
|
|
||||||
} catch (UpdateException | RestApiException e) {
|
|
||||||
// Propagate REST API exceptions thrown by operations; they commonly throw
|
|
||||||
// exceptions like ResourceConflictException to indicate an atomic update
|
|
||||||
// failure.
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Throwables.propagateIfPossible(e);
|
|
||||||
throw new UpdateException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeRefUpdates()
|
public void execute(Listener listener)
|
||||||
throws IOException, UpdateException, RestApiException {
|
throws UpdateException, RestApiException {
|
||||||
|
execute(ImmutableList.of(this), listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeUpdateRepo() throws UpdateException, RestApiException {
|
||||||
try {
|
try {
|
||||||
RepoContext ctx = new RepoContext();
|
RepoContext ctx = new RepoContext();
|
||||||
for (Op op : ops.values()) {
|
for (Op op : ops.values()) {
|
||||||
op.updateRepo(ctx);
|
op.updateRepo(ctx);
|
||||||
}
|
}
|
||||||
|
if (inserter != null) {
|
||||||
|
inserter.flush();
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Throwables.propagateIfPossible(e, RestApiException.class);
|
Throwables.propagateIfPossible(e, RestApiException.class);
|
||||||
throw new UpdateException(e);
|
throw new UpdateException(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeRefUpdates() throws IOException, UpdateException {
|
||||||
if (commands.isEmpty()) {
|
if (commands.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// May not be opened if the caller added ref updates but no new objects.
|
// May not be opened if the caller added ref updates but no new objects.
|
||||||
initRepository();
|
initRepository();
|
||||||
inserter.flush();
|
|
||||||
batchRefUpdate = repo.getRefDatabase().newBatchUpdate();
|
batchRefUpdate = repo.getRefDatabase().newBatchUpdate();
|
||||||
commands.addTo(batchRefUpdate);
|
commands.addTo(batchRefUpdate);
|
||||||
batchRefUpdate.execute(revWalk, NullProgressMonitor.INSTANCE);
|
batchRefUpdate.execute(revWalk, NullProgressMonitor.INSTANCE);
|
||||||
@ -465,10 +548,6 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
changeControlFactory.controlFor(c, user));
|
changeControlFactory.controlFor(c, user));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reindexChanges() throws IOException {
|
|
||||||
ChangeIndexer.allAsList(indexFutures).checkedGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executePostOps() throws Exception {
|
private void executePostOps() throws Exception {
|
||||||
Context ctx = new Context();
|
Context ctx = new Context();
|
||||||
for (Op op : ops.values()) {
|
for (Op op : ops.values()) {
|
||||||
|
Loading…
Reference in New Issue
Block a user