summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Aniszczyk <caniszczyk@gmail.com>2011-03-01 12:05:59 -0500
committerCode Review <codereview-daemon@eclipse.org>2011-03-01 12:05:59 -0500
commit268ccbebe39a6c509efdcc1a5c21e8fc55871275 (patch)
treef6433c9adb5a6cd96e9dcd3a13a41a79f106571f
parentda4fe453563ac1dbf8794972a689cf9efbda0e7c (diff)
parent68ab451d3907e38f7c259ef52a9c3c6a6ee42ead (diff)
downloadjgit-268ccbebe39a6c509efdcc1a5c21e8fc55871275.tar.gz
jgit-268ccbebe39a6c509efdcc1a5c21e8fc55871275.zip
Merge "ProgressMonitor: Refactor to use background alarms"
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java271
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java152
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java141
4 files changed, 411 insertions, 158 deletions
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
index 506031713a..5cc058950c 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
@@ -52,6 +52,7 @@ import java.io.BufferedOutputStream;
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.diff.DiffAlgorithm;
import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
@@ -189,7 +190,9 @@ class Diff extends TextBuiltin {
} else if (newTree == null)
newTree = new FileTreeIterator(db);
- diffFmt.setProgressMonitor(new TextProgressMonitor());
+ TextProgressMonitor pm = new TextProgressMonitor();
+ pm.setDelayStart(2, TimeUnit.SECONDS);
+ diffFmt.setProgressMonitor(pm);
diffFmt.setPathFilter(pathFilter);
if (detectRenames != null)
diffFmt.setDetectRenames(detectRenames.booleanValue());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
new file mode 100644
index 0000000000..5eb959752b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2008-2011, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+/** ProgressMonitor that batches update events. */
+public abstract class BatchingProgressMonitor implements ProgressMonitor {
+ private static final ScheduledThreadPoolExecutor alarmQueue;
+
+ static final Object alarmQueueKiller;
+
+ static {
+ // To support garbage collection, start our thread but
+ // swap out the thread factory. When our class is GC'd
+ // the alarmQueueKiller will finalize and ask the executor
+ // to shutdown, ending the worker.
+ //
+ int threads = 1;
+ alarmQueue = new ScheduledThreadPoolExecutor(threads,
+ new ThreadFactory() {
+ public Thread newThread(Runnable taskBody) {
+ Thread thr = new Thread("JGit-AlarmQueue");
+ thr.setDaemon(true);
+ return thr;
+ }
+ });
+ alarmQueue.allowCoreThreadTimeOut(false);
+ alarmQueue.setMaximumPoolSize(alarmQueue.getCorePoolSize());
+ alarmQueue.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+ alarmQueue.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+ alarmQueue.prestartAllCoreThreads();
+
+ // Now that the threads are running, its critical to swap out
+ // our own thread factory for one that isn't in the ClassLoader.
+ // This allows the class to GC.
+ //
+ alarmQueue.setThreadFactory(Executors.defaultThreadFactory());
+
+ alarmQueueKiller = new Object() {
+ @Override
+ protected void finalize() {
+ alarmQueue.shutdownNow();
+ }
+ };
+ }
+
+ private long delayStartTime;
+
+ private TimeUnit delayStartUnit = TimeUnit.MILLISECONDS;
+
+ private Task task;
+
+ /**
+ * Set an optional delay before the first output.
+ *
+ * @param time
+ * how long to wait before output. If 0 output begins on the
+ * first {@link #update(int)} call.
+ * @param unit
+ * time unit of {@code time}.
+ */
+ public void setDelayStart(long time, TimeUnit unit) {
+ delayStartTime = time;
+ delayStartUnit = unit;
+ }
+
+ public void start(int totalTasks) {
+ // Ignore the number of tasks.
+ }
+
+ public void beginTask(String title, int work) {
+ endTask();
+ task = new Task(title, work);
+ if (delayStartTime != 0)
+ task.delay(delayStartTime, delayStartUnit);
+ }
+
+ public void update(int completed) {
+ if (task != null)
+ task.update(this, completed);
+ }
+
+ public void endTask() {
+ if (task != null) {
+ task.end(this);
+ task = null;
+ }
+ }
+
+ public boolean isCancelled() {
+ return false;
+ }
+
+ /**
+ * Update the progress monitor if the total work isn't known,
+ *
+ * @param taskName
+ * name of the task.
+ * @param workCurr
+ * number of units already completed.
+ */
+ protected abstract void onUpdate(String taskName, int workCurr);
+
+ /**
+ * Finish the progress monitor when the total wasn't known in advance.
+ *
+ * @param taskName
+ * name of the task.
+ * @param workCurr
+ * total number of units processed.
+ */
+ protected abstract void onEndTask(String taskName, int workCurr);
+
+ /**
+ * Update the progress monitor when the total is known in advance.
+ *
+ * @param taskName
+ * name of the task.
+ * @param workCurr
+ * number of units already completed.
+ * @param workTotal
+ * estimated number of units to process.
+ * @param percentDone
+ * {@code workCurr * 100 / workTotal}.
+ */
+ protected abstract void onUpdate(String taskName, int workCurr,
+ int workTotal, int percentDone);
+
+ /**
+ * Finish the progress monitor when the total is known in advance.
+ *
+ * @param taskName
+ * name of the task.
+ * @param workCurr
+ * total number of units processed.
+ * @param workTotal
+ * estimated number of units to process.
+ * @param percentDone
+ * {@code workCurr * 100 / workTotal}.
+ */
+ protected abstract void onEndTask(String taskName, int workCurr,
+ int workTotal, int percentDone);
+
+ private static class Task implements Runnable {
+ /** Title of the current task. */
+ private final String taskName;
+
+ /** Number of work units, or {@link ProgressMonitor#UNKNOWN}. */
+ private final int totalWork;
+
+ /** True when timer expires and output should occur on next update. */
+ private volatile boolean display;
+
+ /** Scheduled timer, supporting cancellation if task ends early. */
+ private Future<?> timerFuture;
+
+ /** True if the task has displayed anything. */
+ private boolean output;
+
+ /** Number of work units already completed. */
+ private int lastWork;
+
+ /** Percentage of {@link #totalWork} that is done. */
+ private int lastPercent;
+
+ Task(String taskName, int totalWork) {
+ this.taskName = taskName;
+ this.totalWork = totalWork;
+ this.display = true;
+ }
+
+ void delay(long time, TimeUnit unit) {
+ display = false;
+ timerFuture = alarmQueue.schedule(this, time, unit);
+ }
+
+ public void run() {
+ display = true;
+ }
+
+ void update(BatchingProgressMonitor pm, int completed) {
+ lastWork += completed;
+
+ if (totalWork == UNKNOWN) {
+ // Only display once per second, as the alarm fires.
+ if (display) {
+ pm.onUpdate(taskName, lastWork);
+ output = true;
+ restartTimer();
+ }
+ } else {
+ // Display once per second or when 1% is done.
+ int currPercent = lastWork * 100 / totalWork;
+ if (display) {
+ pm.onUpdate(taskName, lastWork, totalWork, currPercent);
+ output = true;
+ restartTimer();
+ lastPercent = currPercent;
+ } else if (currPercent != lastPercent) {
+ pm.onUpdate(taskName, lastWork, totalWork, currPercent);
+ output = true;
+ lastPercent = currPercent;
+ }
+ }
+ }
+
+ private void restartTimer() {
+ display = false;
+ timerFuture = alarmQueue.schedule(this, 1, TimeUnit.SECONDS);
+ }
+
+ void end(BatchingProgressMonitor pm) {
+ if (output) {
+ if (totalWork == UNKNOWN) {
+ pm.onEndTask(taskName, lastWork);
+ } else {
+ int pDone = lastWork * 100 / totalWork;
+ pm.onEndTask(taskName, lastWork, totalWork, pDone);
+ }
+ }
+ if (timerFuture != null)
+ timerFuture.cancel(false /* no interrupt */);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
index a668b11be8..7e48609070 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
@@ -44,99 +44,103 @@
package org.eclipse.jgit.lib;
-/**
- * A simple progress reporter printing on stderr
- */
-public class TextProgressMonitor implements ProgressMonitor {
- private boolean output;
-
- private long taskBeganAt;
-
- private String msg;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
- private int lastWorked;
+/** A simple progress reporter printing on a stream. */
+public class TextProgressMonitor extends BatchingProgressMonitor {
+ private final Writer out;
- private int totalWork;
+ private boolean write;
/** Initialize a new progress monitor. */
public TextProgressMonitor() {
- taskBeganAt = System.currentTimeMillis();
+ this(new PrintWriter(System.err));
}
- public void start(final int totalTasks) {
- // Ignore the number of tasks.
- taskBeganAt = System.currentTimeMillis();
+ /**
+ * Initialize a new progress monitor.
+ *
+ * @param out
+ * the stream to receive messages on.
+ */
+ public TextProgressMonitor(Writer out) {
+ this.out = out;
+ this.write = true;
}
- public void beginTask(final String title, final int total) {
- endTask();
- msg = title;
- lastWorked = 0;
- totalWork = total;
+ @Override
+ protected void onUpdate(String taskName, int workCurr) {
+ StringBuilder s = new StringBuilder();
+ format(s, taskName, workCurr);
+ send(s);
}
- public void update(final int completed) {
- if (msg == null)
- return;
-
- final int cmp = lastWorked + completed;
- if (!output && System.currentTimeMillis() - taskBeganAt < 500)
- return;
- if (totalWork == UNKNOWN) {
- display(cmp);
- System.err.flush();
- } else {
- if ((cmp * 100 / totalWork) != (lastWorked * 100) / totalWork) {
- display(cmp);
- System.err.flush();
- }
- }
- lastWorked = cmp;
- output = true;
+ @Override
+ protected void onEndTask(String taskName, int workCurr) {
+ StringBuilder s = new StringBuilder();
+ format(s, taskName, workCurr);
+ s.append("\n");
+ send(s);
}
- private void display(final int cmp) {
- final StringBuilder m = new StringBuilder();
- m.append('\r');
- m.append(msg);
- m.append(": ");
- while (m.length() < 25)
- m.append(' ');
+ private void format(StringBuilder s, String taskName, int workCurr) {
+ s.append("\r");
+ s.append(taskName);
+ s.append(": ");
+ while (s.length() < 25)
+ s.append(' ');
+ s.append(workCurr);
+ }
- if (totalWork == UNKNOWN) {
- m.append(cmp);
- } else {
- final String twstr = String.valueOf(totalWork);
- String cmpstr = String.valueOf(cmp);
- while (cmpstr.length() < twstr.length())
- cmpstr = " " + cmpstr;
- final int pcnt = (cmp * 100 / totalWork);
- if (pcnt < 100)
- m.append(' ');
- if (pcnt < 10)
- m.append(' ');
- m.append(pcnt);
- m.append("% (");
- m.append(cmpstr);
- m.append("/");
- m.append(twstr);
- m.append(")");
- }
+ @Override
+ protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt) {
+ StringBuilder s = new StringBuilder();
+ format(s, taskName, cmp, totalWork, pcnt);
+ send(s);
+ }
- System.err.print(m);
+ @Override
+ protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt) {
+ StringBuilder s = new StringBuilder();
+ format(s, taskName, cmp, totalWork, pcnt);
+ s.append("\n");
+ send(s);
}
- public boolean isCancelled() {
- return false;
+ private void format(StringBuilder s, String taskName, int cmp,
+ int totalWork, int pcnt) {
+ s.append("\r");
+ s.append(taskName);
+ s.append(": ");
+ while (s.length() < 25)
+ s.append(' ');
+
+ String endStr = String.valueOf(totalWork);
+ String curStr = String.valueOf(cmp);
+ while (curStr.length() < endStr.length())
+ curStr = " " + curStr;
+ if (pcnt < 100)
+ s.append(' ');
+ if (pcnt < 10)
+ s.append(' ');
+ s.append(pcnt);
+ s.append("% (");
+ s.append(curStr);
+ s.append("/");
+ s.append(endStr);
+ s.append(")");
}
- public void endTask() {
- if (output) {
- if (totalWork != UNKNOWN)
- display(totalWork);
- System.err.println();
+ private void send(StringBuilder s) {
+ if (write) {
+ try {
+ out.write(s.toString());
+ out.flush();
+ } catch (IOException err) {
+ write = false;
+ }
}
- output = false;
- msg = null;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java
index efce7b1da7..c7b22bdabf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java
@@ -43,110 +43,85 @@
package org.eclipse.jgit.transport;
+import java.io.IOException;
import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
+import org.eclipse.jgit.lib.BatchingProgressMonitor;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ProgressMonitor;
/** Write progress messages out to the sideband channel. */
-class SideBandProgressMonitor implements ProgressMonitor {
- private PrintWriter out;
+class SideBandProgressMonitor extends BatchingProgressMonitor {
+ private final OutputStream out;
- private boolean output;
-
- private long taskBeganAt;
-
- private long lastOutput;
-
- private String msg;
-
- private int lastWorked;
-
- private int totalWork;
+ private boolean write;
SideBandProgressMonitor(final OutputStream os) {
- out = new PrintWriter(new OutputStreamWriter(os, Constants.CHARSET));
+ out = os;
+ write = true;
}
- public void start(final int totalTasks) {
- // Ignore the number of tasks.
- taskBeganAt = System.currentTimeMillis();
- lastOutput = taskBeganAt;
+ @Override
+ protected void onUpdate(String taskName, int workCurr) {
+ StringBuilder s = new StringBuilder();
+ format(s, taskName, workCurr);
+ s.append(" \r");
+ send(s);
}
- public void beginTask(final String title, final int total) {
- endTask();
- msg = title;
- lastWorked = 0;
- totalWork = total;
+ @Override
+ protected void onEndTask(String taskName, int workCurr) {
+ StringBuilder s = new StringBuilder();
+ format(s, taskName, workCurr);
+ s.append(", done\n");
+ send(s);
}
- public void update(final int completed) {
- if (msg == null)
- return;
-
- final int cmp = lastWorked + completed;
- final long now = System.currentTimeMillis();
- if (!output && now - taskBeganAt < 500)
- return;
- if (totalWork == UNKNOWN) {
- if (now - lastOutput >= 500) {
- display(cmp, null);
- lastOutput = now;
- }
- } else {
- if ((cmp * 100 / totalWork) != (lastWorked * 100) / totalWork
- || now - lastOutput >= 500) {
- display(cmp, null);
- lastOutput = now;
- }
- }
- lastWorked = cmp;
- output = true;
+ private void format(StringBuilder s, String taskName, int workCurr) {
+ s.append(taskName);
+ s.append(": ");
+ s.append(workCurr);
}
- private void display(final int cmp, final String eol) {
- final StringBuilder m = new StringBuilder();
- m.append(msg);
- m.append(": ");
+ @Override
+ protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt) {
+ StringBuilder s = new StringBuilder();
+ format(s, taskName, cmp, totalWork, pcnt);
+ s.append(" \r");
+ send(s);
+ }
- if (totalWork == UNKNOWN) {
- m.append(cmp);
- } else {
- final int pcnt = (cmp * 100 / totalWork);
- if (pcnt < 100)
- m.append(' ');
- if (pcnt < 10)
- m.append(' ');
- m.append(pcnt);
- m.append("% (");
- m.append(cmp);
- m.append("/");
- m.append(totalWork);
- m.append(")");
- }
- if (eol != null)
- m.append(eol);
- else
- m.append(" \r");
- out.print(m);
- out.flush();
+ @Override
+ protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt) {
+ StringBuilder s = new StringBuilder();
+ format(s, taskName, cmp, totalWork, pcnt);
+ s.append("\n");
+ send(s);
}
- public boolean isCancelled() {
- return false;
+ private void format(StringBuilder s, String taskName, int cmp,
+ int totalWork, int pcnt) {
+ s.append(taskName);
+ s.append(": ");
+ if (pcnt < 100)
+ s.append(' ');
+ if (pcnt < 10)
+ s.append(' ');
+ s.append(pcnt);
+ s.append("% (");
+ s.append(cmp);
+ s.append("/");
+ s.append(totalWork);
+ s.append(")");
}
- public void endTask() {
- if (output) {
- if (totalWork == UNKNOWN)
- display(lastWorked, ", done\n");
- else
- display(totalWork, "\n");
+ private void send(StringBuilder s) {
+ if (write) {
+ try {
+ out.write(Constants.encode(s.toString()));
+ out.flush();
+ } catch (IOException err) {
+ write = false;
+ }
}
- output = false;
- msg = null;
}
}