diff options
author | Stefan Xenos | 2015-10-13 18:23:54 +0000 |
---|---|---|
committer | Stefan Xenos | 2015-10-13 18:23:54 +0000 |
commit | f67b2c8cc955aa41e949591c9325f76b2e37885c (patch) | |
tree | 02e92ea5661086e60f128d1ac81cf7ccb824170b /bundles/org.eclipse.equinox.common/src | |
parent | 6d8a79c25c4cea891cc1cc35b82aaafe948f4161 (diff) | |
download | rt.equinox.bundles-f67b2c8cc955aa41e949591c9325f76b2e37885c.tar.gz rt.equinox.bundles-f67b2c8cc955aa41e949591c9325f76b2e37885c.tar.xz rt.equinox.bundles-f67b2c8cc955aa41e949591c9325f76b2e37885c.zip |
Bug 475747 - Implement automatic cancellation checks in SubMonitorI20151014-1100
Add "split", a replacement for newChild which automatically performs
a cancellation check if either the newly-created child will
consume one or more ticks or a certain number of trivially-small
children have been created since the last cancellation check.
A cancellation check will throw OperationCanceledException if
the progress monitor has been cancelled.
Signed-off-by: Stefan Xenos <sxenos@gmail.com>
Change-Id: I8e7c2d88b238f7b1a17f80f80f0f9fc3b9d30d8b
Diffstat (limited to 'bundles/org.eclipse.equinox.common/src')
-rw-r--r-- | bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SubMonitor.java | 293 |
1 files changed, 224 insertions, 69 deletions
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SubMonitor.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SubMonitor.java index cf930f170..36655b206 100644 --- a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SubMonitor.java +++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SubMonitor.java @@ -10,6 +10,7 @@ * Stefan Xenos - bug 174539 - add a 1-argument convert(...) method * Stefan Xenos - bug 174040 - SubMonitor#convert doesn't always set task name * Stefan Xenos - bug 206942 - updated javadoc to recommend better constants for infinite progress + * Stefan Xenos (Google) - bug 475747 - Support efficient, convenient cancellation checks in SubMonitor * IBM Corporation - ongoing maintenance *******************************************************************************/ package org.eclipse.core.runtime; @@ -35,7 +36,7 @@ package org.eclipse.core.runtime; * <ul> * <li>At the start of your method, use <code>SubMonitor.convert(...).</code> to convert the IProgressMonitor * into a SubMonitor. </li> - * <li>Use <code>SubMonitor.newChild(...)</code> whenever you need to call another method that + * <li>Use <code>SubMonitor.split(...)</code> whenever you need to call another method that * accepts an IProgressMonitor.</li> * </ul> * <p></p> @@ -69,7 +70,7 @@ package org.eclipse.core.runtime; * <p>This example demonstrates how the recommended usage of <code>SubMonitor</code> makes it unnecessary to call * IProgressMonitor.done() in most situations.</p> * - * <p>It is never necessary to call done() on a monitor obtained from <code>convert</code> or <code>progress.newChild()</code>. + * <p>It is never necessary to call done() on a monitor obtained from <code>convert</code> or <code>progress.split()</code>. * In this example, there is no guarantee that <code>monitor</code> is an instance of <code>SubMonitor</code>, making it * necessary to call <code>monitor.done()</code>. The JavaDoc contract makes this the responsibility of the caller.</p> * @@ -83,13 +84,13 @@ package org.eclipse.core.runtime; * SubMonitor progress = SubMonitor.convert(monitor, 100); * * // Use 30% of the progress to do some work - * doSomeWork(progress.newChild(30)); + * doSomeWork(progress.split(30)); * * // Advance the monitor by another 30% * progress.worked(30); * * // Use the remaining 40% of the progress to do some more work - * doSomeWork(progress.newChild(40)); + * doSomeWork(progress.split(40)); * } * </pre> * @@ -109,13 +110,13 @@ package org.eclipse.core.runtime; * SubMonitor progress = SubMonitor.convert(monitor, 100); * try { * // Use 30% of the progress to do some work - * doSomeWork(progress.newChild(30)); + * doSomeWork(progress.split(30)); * * // Advance the monitor by another 30% * progress.worked(30); * * // Use the remaining 40% of the progress to do some more work - * doSomeWork(progress.newChild(40)); + * doSomeWork(progress.split(40)); * * } finally { * if (monitor != null) { @@ -136,7 +137,7 @@ package org.eclipse.core.runtime; * * if (condition) { * // Use 50% of the progress to do some work - * doSomeWork(progress.newChild(50)); + * doSomeWork(progress.split(50)); * } * * // Don't report any work, but ensure that we have 50 ticks remaining on the progress monitor. @@ -146,7 +147,7 @@ package org.eclipse.core.runtime; * progress.setWorkRemaining(50); * * // Use the remainder of the progress monitor to do the rest of the work - * doSomeWork(progress.newChild(50)); + * doSomeWork(progress.split(50)); * } * </pre> * @@ -155,7 +156,7 @@ package org.eclipse.core.runtime; * <pre> * if (condition) { * // Use 50% of the progress to do some work - * doSomeWork(progress.newChild(50)); + * doSomeWork(progress.split(50)); * } else { * // Bad: Causes the progress monitor to appear to start at 50%, wasting half of the * // space in the monitor. @@ -175,16 +176,16 @@ package org.eclipse.core.runtime; * * // Create a new progress monitor that uses 70% of the total progress and will allocate one tick * // for each element of the given collection. - * SubMonitor loopProgress = progress.newChild(70).setWorkRemaining(someCollection.size()); + * SubMonitor loopProgress = progress.split(70).setWorkRemaining(someCollection.size()); * * for (Iterator iter = someCollection.iterator(); iter.hasNext();) { * Object next = iter.next(); * - * doWorkOnElement(next, loopProgress.newChild(1)); + * doWorkOnElement(next, loopProgress.split(1)); * } * * // Use the remaining 30% of the progress monitor to do some work outside the loop - * doSomeWork(progress.newChild(30)); + * doSomeWork(progress.split(30)); * } * </pre> * @@ -204,7 +205,7 @@ package org.eclipse.core.runtime; * // use 0.01% of the space remaining in the monitor to process the next node. * progress.setWorkRemaining(10000); * - * doWorkOnElement(node, progress.newChild(1)); + * doWorkOnElement(node, progress.split(1)); * * node = node.next; * } @@ -220,6 +221,16 @@ package org.eclipse.core.runtime; public final class SubMonitor implements IProgressMonitorWithBlocking { /** + * Number of trivial operations (operations which do not report any progress) which can be + * performed before the monitor performs a cancellation check. This ensures that cancellation + * checks do not create a performance problem in tight loops that create a lot of SubMonitors, + * while still ensuring that cancellation is checked occasionally in such loops. This only + * affects operations which are too small to report any progress. Operations which are large + * enough to consume at least one tick will always be checked for cancellation. + */ + private static final int TRIVIAL_OPERATIONS_BEFORE_CANCELLATION_CHECK = 1000; + + /** * Minimum number of ticks to allocate when calling beginTask on an unknown IProgressMonitor. * Pick a number that is big enough such that, no matter where progress is being displayed, * the user would be unlikely to notice if progress were to be reported with higher accuracy. @@ -231,19 +242,25 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { * its active descendents share the same RootInfo. */ private static final class RootInfo { - private final IProgressMonitor root; + final IProgressMonitor root; /** * Remembers the last task name. Prevents us from setting the same task name multiple * times in a row. */ - private String taskName = null; + String taskName = null; /** * Remembers the last subtask name. Prevents the SubMonitor from setting the same * subtask string more than once in a row. */ - private String subTask = null; + String subTask = null; + + /** + * Counter that indicates when we should perform an cancellation check for a trivial + * operation. + */ + int cancellationCheckCounter; /** * Creates a RootInfo struct that delegates to the given progress @@ -294,6 +311,11 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { ((IProgressMonitorWithBlocking) root).setBlocked(reason); } + public void checkForCancellation() { + if (root.isCanceled()) { + throw new OperationCanceledException(); + } + } } /** @@ -314,14 +336,14 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { /** * Number of ticks allocated for this instance's children. This is the total number - * of ticks that may be passed into worked(int) or newChild(int). + * of ticks that may be passed into worked(int) or split(int). */ private int totalForChildren; /** - * Children created by newChild will be completed automatically the next time + * Children created by split will be completed automatically the next time * the parent progress monitor is touched. This points to the last incomplete child - * created with newChild. + * created with split. */ private IProgressMonitor lastSubMonitor = null; @@ -336,7 +358,7 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { private final int flags; /** - * May be passed as a flag to newChild. Indicates that the calls + * May be passed as a flag to split. Indicates that the calls * to subTask on the child should be ignored. Without this flag, * calling subTask on the child will result in a call to subTask * on its parent. @@ -344,7 +366,7 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { public static final int SUPPRESS_SUBTASK = 0x0001; /** - * May be passed as a flag to newChild. Indicates that strings + * May be passed as a flag to split. Indicates that strings * passed into beginTask should be ignored. If this flag is * specified, then the progress monitor instance will accept null * as the first argument to beginTask. Without this flag, any @@ -354,7 +376,7 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { public static final int SUPPRESS_BEGINTASK = 0x0002; /** - * May be passed as a flag to newChild. Indicates that strings + * May be passed as a flag to split. Indicates that strings * passed into setTaskName should be ignored. If this string * is omitted, then a call to setTaskName on the child will * result in a call to setTaskName on the parent. @@ -362,19 +384,30 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { public static final int SUPPRESS_SETTASKNAME = 0x0004; /** - * May be passed as a flag to newChild. Indicates that strings + * May be passed as a flag to split. Indicates that strings * passed to setTaskName, subTask, and beginTask should all be ignored. */ public static final int SUPPRESS_ALL_LABELS = SUPPRESS_SETTASKNAME | SUPPRESS_BEGINTASK | SUPPRESS_SUBTASK; /** - * May be passed as a flag to newChild. Indicates that strings + * May be passed as a flag to split. Indicates that strings * passed to setTaskName, subTask, and beginTask should all be propagated * to the parent. */ public static final int SUPPRESS_NONE = 0; /** + * Bitwise combination of all flags which may be passed in to the public interface on {@link #split} + */ + private static final int ALL_PUBLIC_FLAGS = SUPPRESS_ALL_LABELS; + + /** + * Bitwise combination of all flags which are inherited directly from a parent SubMonitor to its immediate + * children. All other flags are either not inherited or are inherited from more complicated logic in {@link #split} + */ + private static final int ALL_INHERITED_FLAGS = SUPPRESS_SUBTASK; + + /** * Creates a new SubMonitor that will report its progress via * the given RootInfo. * @param rootInfo the root of this progress monitor tree @@ -397,6 +430,9 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { * <p>This method should generally be called at the beginning of a method that accepts * an IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor.</p> * + * <p>Since it is illegal to call beginTask on the same IProgressMonitor more than once, + * the same instance of IProgressMonitor must not be passed to convert more than once.</p> + * * @param monitor monitor to convert to a SubMonitor instance or null. Treats null * as a new instance of <code>NullProgressMonitor</code>. * @return a SubMonitor instance that adapts the argument @@ -413,6 +449,9 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { * * <p>This method should generally be called at the beginning of a method that accepts * an IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor.</p> + * + * <p>Since it is illegal to call beginTask on the same IProgressMonitor more than once, + * the same instance of IProgressMonitor must not be passed to convert more than once.</p> * * @param monitor monitor to convert to a SubMonitor instance or null. Treats null * as a new instance of <code>NullProgressMonitor</code>. @@ -431,7 +470,10 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { * * <p>This method should generally be called at the beginning of a method that accepts * an IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor.</p> - * + * + * <p>Since it is illegal to call beginTask on the same IProgressMonitor more than once, + * the same instance of IProgressMonitor must not be passed to convert more than once.</p> + * * @param monitor to convert into a SubMonitor instance or null. If given a null argument, * the resulting SubMonitor will not report its progress anywhere. * @param taskName user readable name to pass to monitor.beginTask. Never null. @@ -454,7 +496,7 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { /** * <p>Sets the work remaining for this SubMonitor instance. This is the total number - * of ticks that may be reported by all subsequent calls to worked(int), newChild(int), etc. + * of ticks that may be reported by all subsequent calls to worked(int), split(int), etc. * This may be called many times for the same SubMonitor instance. When this method * is called, the remaining space on the progress monitor is redistributed into the given * number of ticks.</p> @@ -580,6 +622,25 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { } /** + * <p>Creates a new SubMonitor that will consume the given number of ticks from its parent. + * Shorthand for calling {@link #newChild(int, int)} with (totalWork, SUPPRESS_BEGINTASK). + * + * <p>This is much like {@link #split(int)} but it does not check for cancellation and will not + * throw {@link OperationCanceledException}. New code should consider using {@link #split(int)} + * to benefit from automatic cancellation checks. + * + * @param totalWork number of ticks to consume from the receiver + * @return new sub progress monitor that may be used in place of a new SubMonitor + */ + public SubMonitor newChild(int totalWork) { + return newChild(totalWork, SUPPRESS_BEGINTASK); + } + + /** + * <p>This is much like {@link #split}, but it does not check for cancellation and will not + * throw {@link OperationCanceledException}. New code should consider using {@link #split} + * to benefit from automatic cancellation checks. + * * <p>Creates a sub progress monitor that will consume the given number of ticks from the * receiver. It is not necessary to call <code>beginTask</code> or <code>done</code> on the * result. However, the resulting progress monitor will not report any work after the first @@ -639,31 +700,63 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { * @param totalWork number of ticks to consume from the receiver * @return new sub progress monitor that may be used in place of a new SubMonitor */ - public SubMonitor newChild(int totalWork) { - return newChild(totalWork, SUPPRESS_BEGINTASK); + public SubMonitor newChild(int totalWork, int suppressFlags) { + double totalWorkDouble = (totalWork > 0) ? totalWork : 0.0d; + totalWorkDouble = Math.min(totalWorkDouble, totalForChildren - usedForChildren); + cleanupActiveChild(); + + // Compute the flags for the child. We want the net effect to be as though the child is + // delegating to its parent, even though it is actually talking directly to the root. + // This means that we need to compute the flags such that - even if a label isn't + // suppressed by the child - if that same label would have been suppressed when the + // child delegated to its parent, the child must explicitly suppress the label. + int childFlags = flags & ALL_INHERITED_FLAGS; + + if ((flags & SUPPRESS_SETTASKNAME) != 0) { + // If the parent was ignoring labels passed to setTaskName, then the child will ignore + // labels passed to either beginTask or setTaskName - since both delegate to setTaskName + // on the parent + childFlags |= SUPPRESS_SETTASKNAME | SUPPRESS_BEGINTASK; + } + + // Note: the SUPPRESS_BEGINTASK flag does not affect the child since there + // is no method on the child that would delegate to beginTask on the parent. + childFlags |= (suppressFlags & ALL_PUBLIC_FLAGS); + + SubMonitor result = new SubMonitor(root, consume(totalWorkDouble), (int) totalWorkDouble, childFlags); + lastSubMonitor = result; + return result; } /** + * This is shorthand for calling <code>split(totalWork, SUPPRESS_BEGINTASK)</code>. See + * {@link #split(int, int)} for more details. + * * <p>Creates a sub progress monitor that will consume the given number of ticks from the * receiver. It is not necessary to call <code>beginTask</code> or <code>done</code> on the * result. However, the resulting progress monitor will not report any work after the first - * call to done() or before ticks are allocated. Ticks may be allocated by calling beginTask - * or setWorkRemaining.</p> + * call to done() or before ticks are allocated. Ticks may be allocated by calling {@link #beginTask} + * or {@link #setWorkRemaining}.</p> * - * <p>Each SubMonitor only has one active child at a time. Each time newChild() is called, the - * result becomes the new active child and any unused progress from the previously-active child is - * consumed.</p> + * <p>This method is much like {@link #newChild}, but it will additionally check for cancellation and + * will throw an OperationCanceledException if the monitor has been cancelled. Not every call to + * this method will trigger a cancellation check. The checks will be performed as often as possible + * without degrading the performance of the caller. + * + * <p>Each SubMonitor only has one active child at a time. Each time {@link #newChild} or + * {@link #split} is called, the result becomes the new active child and any unused progress + * from the previously-active child is consumed.</p> * * <p>This is property makes it unnecessary to call done() on a SubMonitor instance, since child * monitors are automatically cleaned up the next time the parent is touched.</p> * * <code><pre> * //////////////////////////////////////////////////////////////////////////// - * // Example 1: Typical usage of newChild + * // Example 1: Typical usage of split * void myMethod(IProgressMonitor parent) { * SubMonitor progress = SubMonitor.convert(parent, 100); - * doSomething(progress.newChild(50)); - * doSomethingElse(progress.newChild(50)); + * doSomething(progress.split(50)); + * doSomethingElse(progress.split(50)); * } * * //////////////////////////////////////////////////////////////////////////// @@ -677,7 +770,7 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { * // Creating the next child monitor will clean up the previous one, * // causing progress to be reported smoothly even if we don't do anything * // with the monitors we create - * progress.newChild(1); + * progress.split(1); * } * } * @@ -688,51 +781,113 @@ public final class SubMonitor implements IProgressMonitorWithBlocking { * * // WRONG WAY: Won't have the intended effect, as only one of these progress * // monitors may be active at a time and the other will report no progress. - * callMethod(progress.newChild(50), computeValue(progress.newChild(50))); + * callMethod(progress.split(50), computeValue(progress.split(50))); * } * * void rightMethod(IProgressMonitor parent) { * SubMonitor progress = SubMonitor.convert(parent, 100); * * // RIGHT WAY: Break up method calls so that only one SubMonitor is in use at a time. - * Object someValue = computeValue(progress.newChild(50)); - * callMethod(progress.newChild(50), someValue); + * Object someValue = computeValue(progress.split(50)); + * callMethod(progress.split(50), someValue); * } * </pre></code> * * @param totalWork number of ticks to consume from the receiver - * @return new sub progress monitor that may be used in place of a new SubMonitor + * @return a new SubMonitor instance + * @since 3.8 */ - public SubMonitor newChild(int totalWork, int suppressFlags) { - double totalWorkDouble = (totalWork > 0) ? totalWork : 0.0d; - totalWorkDouble = Math.min(totalWorkDouble, totalForChildren - usedForChildren); - cleanupActiveChild(); - - // Compute the flags for the child. We want the net effect to be as though the child is - // delegating to its parent, even though it is actually talking directly to the root. - // This means that we need to compute the flags such that - even if a label isn't - // suppressed by the child - if that same label would have been suppressed when the - // child delegated to its parent, the child must explicitly suppress the label. - int childFlags = SUPPRESS_NONE; - - if ((flags & SUPPRESS_SETTASKNAME) != 0) { - // If the parent was ignoring labels passed to setTaskName, then the child will ignore - // labels passed to either beginTask or setTaskName - since both delegate to setTaskName - // on the parent - childFlags |= SUPPRESS_SETTASKNAME | SUPPRESS_BEGINTASK; - } + public SubMonitor split(int totalWork) { + return split(totalWork, SUPPRESS_BEGINTASK); + } - if ((flags & SUPPRESS_SUBTASK) != 0) { - // If the parent was suppressing labels passed to subTask, so will the child. - childFlags |= SUPPRESS_SUBTASK; + /** + * <p>Creates a sub progress monitor that will consume the given number of ticks from the + * receiver. It is not necessary to call <code>beginTask</code> or <code>done</code> on the + * result. However, the resulting progress monitor will not report any work after the first + * call to done() or before ticks are allocated. Ticks may be allocated by calling {@link #beginTask} + * or {@link #setWorkRemaining}</p> + * + * <p>This method is much like {@link #newChild}, but will additionally check for cancellation and + * will throw an OperationCanceledException if the monitor has been cancelled. Not every call to + * this method will trigger a cancellation check. The checks will be performed as often as possible + * without degrading the performance of the caller. + * + * <p>Each SubMonitor only has one active child at a time. Each time {@link #newChild} or + * {@link #split} is called, the result becomes the new active child and any unused progress + * from the previously-active child is consumed.</p> + * + * <p>This is property makes it unnecessary to call done() on a SubMonitor instance, since child + * monitors are automatically cleaned up the next time the parent is touched.</p> + * + * <code><pre> + * //////////////////////////////////////////////////////////////////////////// + * // Example 1: Typical usage of split + * void myMethod(IProgressMonitor parent) { + * SubMonitor progress = SubMonitor.convert(parent, 100); + * doSomething(progress.split(50)); + * doSomethingElse(progress.split(50)); + * } + * + * //////////////////////////////////////////////////////////////////////////// + * // Example 2: Demonstrates the function of active children. Creating children + * // is sufficient to smoothly report progress, even if worked(...) and done() + * // are never called. + * void myMethod(IProgressMonitor parent) { + * SubMonitor progress = SubMonitor.convert(parent, 100); + * + * for (int i = 0; i < 100; i++) { + * // Creating the next child monitor will clean up the previous one, + * // causing progress to be reported smoothly even if we don't do anything + * // with the monitors we create + * progress.split(1); + * } + * } + * + * //////////////////////////////////////////////////////////////////////////// + * // Example 3: Demonstrates a common anti-pattern + * void wrongMethod(IProgressMonitor parent) { + * SubMonitor progress = SubMonitor.convert(parent, 100); + * + * // WRONG WAY: Won't have the intended effect, as only one of these progress + * // monitors may be active at a time and the other will report no progress. + * callMethod(progress.split(50), computeValue(progress.split(50))); + * } + * + * void rightMethod(IProgressMonitor parent) { + * SubMonitor progress = SubMonitor.convert(parent, 100); + * + * // RIGHT WAY: Break up method calls so that only one SubMonitor is in use at a time. + * Object someValue = computeValue(progress.split(50)); + * callMethod(progress.split(50), someValue); + * } + * </pre></code> + * + * @param totalWork number of ticks to consume from the receiver + * @return new sub progress monitor that may be used in place of a new SubMonitor + * @since 3.8 + */ + public SubMonitor split(int totalWork, int suppressFlags) { + int oldUsedForParent = this.usedForParent; + SubMonitor result = newChild(totalWork, suppressFlags); + + int ticksTheChildWillReportToParent = result.totalParent; + + // If the new child reports a nonzero amount of progress. + if (ticksTheChildWillReportToParent > 0) { + // Don't check for cancellation if the child is consuming 100% of its parent since whatever code created + // the parent already performed this check. + if (oldUsedForParent > 0 || usedForParent < totalParent) { + // Treat this as a nontrivial operation and check for cancellation unconditionally. + root.checkForCancellation(); + } + } else { + // This is a trivial operation. Only perform a cancellation check after the counter expires. + if (++root.cancellationCheckCounter >= TRIVIAL_OPERATIONS_BEFORE_CANCELLATION_CHECK) { + root.cancellationCheckCounter = 0; + root.checkForCancellation(); + } } - - // Note: the SUPPRESS_BEGINTASK flag does not affect the child since there - // is no method on the child that would delegate to beginTask on the parent. - childFlags |= suppressFlags; - - SubMonitor result = new SubMonitor(root, consume(totalWorkDouble), (int) totalWorkDouble, childFlags); - lastSubMonitor = result; return result; } |