Implement a multi-sub-task progress monitor for ReceiveCommits

Because there are a lot of file and database accesses, having a
progress monitor is useful. Moreover, we no longer rely on JGit for
ref updates, so we can't depend on its progress meter.

This is a special progress meter implementation that multiplexes
output from the various sub-tasks that ReceiveCommits performs. We
need this multiplexing because we don't know ahead of time how much of
most kinds of work must be performed. For example, a single ref update
can close an arbitrary number of changes.

The progress meter includes a "spinner" animation that updates the
output every 500ms regardless of activity. Additionally, the whole
progress meter updates whenever one sub-task increases by 1%. Because
of this, the MultiProgressMonitor must run in the main thread to avoid
I/O stalls, delegating work to a background worker.

Change-Id: If7f355830387a1a36a4e3b7ca01693239ce2d956
This commit is contained in:
Dave Borowitz
2012-02-29 11:39:00 -08:00
parent 3643ac6ec5
commit 06cb1d2526
3 changed files with 364 additions and 27 deletions

View File

@@ -15,9 +15,10 @@
package com.google.gerrit.server.git;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.ReceiveCommits.MessageSender;
import com.google.gerrit.server.git.WorkQueue.Executor;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.util.RequestScopePropagator;
import com.google.gerrit.server.git.WorkQueue.Executor;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.Inject;
@@ -30,19 +31,15 @@ import org.eclipse.jgit.transport.ReceivePack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.OutputStream;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/** Hook that delegates to {@link ReceiveCommits} in a worker thread. */
public class AsyncReceiveCommits implements PreReceiveHook {
private static final Logger log =
LoggerFactory.getLogger(AsyncReceiveCommits.class);
private final ReceiveCommits rc;
private final Executor executor;
private final RequestScopePropagator scopePropagator;
public interface Factory {
AsyncReceiveCommits create(ProjectControl projectControl,
Repository repository);
@@ -70,7 +67,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
@Override
public void run() {
rc.processCommands(commands);
rc.processCommands(commands, progress);
}
@Override
@@ -94,6 +91,35 @@ public class AsyncReceiveCommits implements PreReceiveHook {
}
}
private class MessageSenderOutputStream extends OutputStream {
private final MessageSender messageSender = rc.getMessageSender();
@Override
public void write(int b) {
messageSender.sendBytes(new byte[]{(byte)b});
}
@Override
public void write(byte[] what, int off, int len) {
messageSender.sendBytes(what, off, len);
}
@Override
public void write(byte[] what) {
messageSender.sendBytes(what);
}
@Override
public void flush() {
messageSender.flush();
}
}
private final ReceiveCommits rc;
private final Executor executor;
private final RequestScopePropagator scopePropagator;
private final MultiProgressMonitor progress;
@Inject
AsyncReceiveCommits(final ReceiveCommits.Factory factory,
@ReceiveCommitsExecutor final Executor executor,
@@ -104,29 +130,25 @@ public class AsyncReceiveCommits implements PreReceiveHook {
this.scopePropagator = scopePropagator;
rc = factory.create(projectControl, repo);
rc.getReceivePack().setPreReceiveHook(this);
progress = new MultiProgressMonitor(
new MessageSenderOutputStream(), "Updating changes");
}
@Override
public void onPreReceive(final ReceivePack rp,
final Collection<ReceiveCommand> commands) {
Future<?> workerFuture = executor.submit(
scopePropagator.wrap(new Worker(commands)));
Exception err = null;
try {
workerFuture.get();
progress.waitFor(executor.submit(
scopePropagator.wrap(new Worker(commands))));
} catch (ExecutionException e) {
err = e;
} catch (InterruptedException e) {
err = e;
}
if (err != null) {
log.warn("Error in ReceiveCommits", err);
log.warn("Error in ReceiveCommits", e);
rc.getMessageSender().sendError("internal error while processing changes");
// ReceiveCommits has tried its best to catch errors, so anything at this
// point is very bad.
for (final ReceiveCommand c : commands) {
if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
ReceiveCommits.reject(c, "internal error");
rc.reject(c, "internal error");
}
}
}