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:
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user