Using Progress Monitors

Summary
In this article I'll explain how to report progress in Eclipse. I'll
discuss the contract on IProgressMonitor, demonstrate some common patterns
using SubMonitor, and explain how to migrate legacy code to take advantage
of the API introduced in Eclipse 4.6.

By Stefan Xenos, Google
October 19, 2016

1.0 Introduction

So you're writing a long-running method. It takes an IProgressMonitor as a parameter. You want it to
report smooth progress, be cancellable from the UI thread and accomplish this with minimal boilerplate.
Unfortunately, most of the examples you've seen either omit progress reporting entirely or contain
more boilerplate than code.

Is it possible to get the smooth progress reporting without all that boilerplate? If so, what are the
responsibilities of your method and what are the responsibilities of its caller? This article should help.

2.0 Using SubMonitor

Use SubMonitor. If you remember nothing else from this article, remember that.

SubMonitor is an implementation of IProgressMonitor that simplifies everything related to progress
reporting. Whenever you write a method that accepts an IProgressMonitor, the first thing you should
do is convert it to a SubMonitor using SubMonitor.convert(...).

2.1 Allocating Ticks

There are several overloads for convert and most of them accept a number of ticks as an
argument. These are said to "allocate ticks". A tick is a unit of work to be performed. Allocating
ticks distributes those ticks across the remaining space of the progress monitor but doesn't
actually report any progress. It basically sets the denominator that will be used for subsequent
progress reporting.

How many ticks do you need? Take a look at all the slow things your method does and assign them
a number of ticks based on how long you think they will take. Any method you call that takes a
progress monitor should be considered a "slow thing". If you think one thing will take longer
than another, give it more ticks. If you think it will take twice as long, give it twice as many.
When you're done, add up all the ticks. That's how many you should allocate. Ticks only have
meaning relative to other ticks on the same monitor instance: their absolute value doesn't
mean anything.

There are several methods which allocate ticks. Normally you'll allocate them at construction-time
using SubMonitor.convert(...) but this only works if you're creating a new
SubMonitor instance.

Sometimes you'll want to allocate (or reallocate) the ticks on an existing
monitor in which case you'll want SubMonitor.setWorkRemaining. You can
call this as often as you like on a SubMonitor instance. When you do, any remaining unconsumed
space on the monitor is divided into the given number of ticks and any previously-allocated
ticks are forgotten.

The last method that allocates ticks is called beginTask. It's used as part of the
progress reporting framework and you rarely need to call it directly. You'll see this used
a lot in older code and we'll get more into it later. For now, it's best to avoid it in new
code unless you're implementing your own IProgressMonitor.

2.2 Consuming Ticks

Once you've allocated the ticks you can consume them. Consuming ticks is what reports progress.
Let's say you allocate 50 ticks and then consume 3 of them. That means your progress bar will
be 3/50 of the way across, or 6%. Note that you must allocate ticks before you can consume them
on any given progress monitor instance. Attempting to consume ticks without allocating them is an error.

There are several methods on SubMonitor which consume ticks. Namely, split(...),
newChild(...), and worked(...). Practically speaking the only one you
need is split(...).

I'll be using split(...) for most of the code examples in this article, but it is new
in Eclipse 4.6. If your code is meant to work on earlier versions of Eclipse you should use
newChild(...) instead of split(...). The two do pretty much the same
thing except that split(...) also performs cancellation checks.

2.3 Split

split(...) doesn't immediately consume the ticks. It uses the ticks to create a new
child monitor but first it fully consumes any leftover ticks in any previous children
of the same parent.

I'll demonstrate with an example:

void myMethod(IProgressMonitor monitor) {
// No ticks have been allocated yet, so we can't consume them.
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
// monitor is now being managed by subMonitor. We shouldn't use it directly again.
// subMonitor has 100 ticks allocated for it and we can start consuming them.
SubMonitor child1 = subMonitor.split(10);
// subMonitor now has 90 ticks allocated. 10 of the original 100 were used to build child1.
// child1 has no ticks allocated for it yet so we can't consume ticks from it yet.
child1.setWorkRemaining(100);
// child1 now has 100 ticks allocated for it. Consuming 1 tick from child1 now would
// advance the root monitor by 0.1%.
SubMonitor grandchild1 = child1.split(50);
// child1 now has 50 ticks allocated.
SubMonitor grandchild2 = child1.split(50);
// Allocating a new grandchild from child1 has caused grandchild1 to be consumed.
// Our root progress monitor now shows 5% progress.
SubMonitor child2 = subMonitor.split(40);
// Allocating a new child from subMonitor has caused child1 and grandchild2 to be
// consumed. Our root progress monitor now shows 10% progress.
SubMonitor child3 = subMonitor.split(10);
// Child2 was consumed. The root progress monitor now shows 50% progress.
SubMonitor child4 = subMonitor.split(40);
// Child3 was consumed. The root progress monitor now shows 60% progress.
}

2.4 Cancellation

Long-running methods should check the value of IProgressMonitor.isCanceled()
periodically. If it returns true, they should terminate cleanly and throw
OperationCanceledException. In Eclipse 4.5 and earlier, this was done with
explicit cancellation checks like this:

if (monitor.isCanceled()) {
throw new OperationCanceledException();
}

Unfortunately, these sorts of cancellation checks are cumbersome and can become a
performance bottleneck if performed too frequently.

In Eclipse 4.6 and on, cancellation checks are be performed implicitly by
SubMonitor.split(...). Code should be migrated to use split
wherever possible and explicit cancellation checks should be deleted.

So how does split work and how does it replace explicit cancellation checks?
It's basically just a helper that does the same thing as newChild but
additionally includes a cancellation check. Internally, split does something like this
(pseudocode):

In some rare cases, you really need to perform an explicit cancellation check
at a specific time and can't rely on the intermittent cancellation checks done
by split. In such cases, you can use the SubMonitor.checkCanceled
utility introduced in Eclipse 4.7.

For example, this code converts an IProgressMonitor to a SubMonitor while performing
a guaranteed cancellation check:

SubMonitor subMonitor = SubMonitor.convert(monitor).checkCanceled();

2.5 Testing and Debugging

Whenever you add or change progress monitoring in a method, you should test it by
enabling the progress monitor self-tests while you run your code. To do this, add
the following tracing options to an Eclipse .options file:

Once enabled, you will see a warning written to the log whenever your code violates
the API contracts on a SubMonitor. The tracing options can also be enabled in the
tracing tab of any launch configuration. If you maintain any code that reports
progress, it's generally a good idea to leave these options enabled at all times.

If you see nothing it either means that your code is working perfectly or that the
diagnostic tool isn't running. You can confirm that the diagnostic tool is running by
using your debugger to confirm that the following variable is true:

More information about enabling Eclipse tracing options can be found
here.

3.0 Examples

Lets look at some examples of common progress reporting patterns.

3.1 Calling child methods

Most long-running operation will need to call out to other long-running operations.

void doSomething(IProgressMonitor monitor) {
// Convert the given monitor into a SubMonitor instance. We shouldn't use the original
// monitor object again since subMonitor will consume the entire monitor.
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
// Use 30% of the progress to do some work
someChildTask(subMonitor.split(30));
// Use the remaining 70% of the progress to do some more work
someChildTask(subMonitor.split(70));
}

3.2 Branches

Sometimes some long-running piece of code is optional. If the optional piece is skipped, you still want the
total work to add up to 100%, but you also don't want to waste a portion of the progress monitor just to
make the ticks line up. One approach is to do the optional part first and then use setWorkRemaining to
redistribute the remaining ticks.

void doSomething(IProgressMonitor monitor) {
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
if (condition1) {
doSomeWork(subMonitor.split(20));
}
// Don't report any work, but ensure that we have 80 ticks remaining on the progress monitor.
// If we already consumed ticks in the above branch, this is a no-op. Otherwise, the remaining
// space in the monitor is redistributed.
subMonitor.setWorkRemaining(80);
if (condition2) {
doMoreWork(subMonitor.split(40));
}
subMonitor.setWorkRemaining(40)
doSomeMoreWork(subMonitor.split(40));
}

This approach works well enough in most cases and requires minimal boilerplate but the progress it reports
can sometimes be uneven if the method ends with a bunch of conditionals that are often skipped. Another
approach is to count the number of ticks in advance:

3.4 Skipping elements in a loop

Sometimes some elements from a loop will be skipped. In that case, it's better to use the space
in the progress monitor to report the work done for the long-running elements rather than the
fast elements.

This works well enough if skipped elements are rare but if most of the elements are skipped, this pattern
will tend to make poor use of the end of the monitor. If many elements might be skipped, it's better to
pre-filter the list like this:

3.5 Queues

What if you're performing a depth-first search or some other algorithm where more work is
continually discovered as you proceed? Try putting the work-discovered-so-far in a queue
and using the size of the queue to allocate ticks on your monitor. When using this pattern,
always make sure you never allocate less than some minimum number of ticks or you'll consume
the entire progress monitor on the first few iterations of your algorithm.

void depthFirstSearch(IProgressMonitor monitor, Object root) {
SubMonitor subMonitor = SubMonitor.convert(monitor);
ArrayList queue = new ArrayList();
queue.add(root);
while (!queue.isEmpty()) {
// Allocate a number of ticks equal to the size of the queue or some constant,
// whatever is larger. This constant prevents the entire monitor from being consumed
// at the start when the queue is very small.
subMonitor.setWorkRemaining(Math.max(queue.size(), 20));
Object next = queue.remove(queue.size() - 1);
processElement(next, subMonitor.split(1));
queue.addAll(getChildrenFor(next));
}
}

3.6 Unknown progress

What about those situations where you really have no idea how much work to report or how
long it will take? Try allocating a small percentage of the remaining space on each iteration.

Notice the idiom setWorkRemaining(denominator).split(numerator). This can be used
at any point to consume numerator/denominator of the remaining space in a monitor.

3.7 Naming conventions

In these examples we've used the same naming convention that has been used within
the Eclipse platform. You may wish to use the same convention to help convey the purpose of your
progress monitors:

subMonitor - a SubMonitor that tracks the entire progress of the method.

loopMonitor - a SubMonitor that tracks the progress of a single loop. If a method contains
multiple loops, this is used as a suffix.

iterationMonitor - a SubMonitor that tracks the progress of a single iteration of a loop.
If there are multiple nested loops, this may be used as a suffix.

monitor - an IProgressMonitor that is passed as a method argument.

4.0 Responsibilities of callers and callees

Imagine you need to invoke another method that accepts an IProgressMonitor.
What are the responsibilities of the caller and what are the responsibilities
of the callee?

The caller:

Will pass an IProgressMonitor instance which has not had beginTask invoked on it yet
(or an implementation such as SubMonitor which permits beginTask to be invoked multiple times).

Will not expect that the callee invokes done() on the monitor. The
caller must either use an SubMonitor (or a similar implementation which does not require done()
to be invoked), or it must take responsibility for calling done() on the monitor after the
callee has finished.

Will not pass in a null monitor unless the JavaDoc for the callee says that it accepts null.

Will pass in a monitor which ignores the String argument to beginTask unless the JavaDoc
for the callee says that it requires otherwise.

The callee:

Will call beginTask 0 or 1 times on the monitor, at its option.

Will not promise to call done() on the monitor, although it is allowed to do so.

Will not call setCanceled on the monitor.

Will not accept a null monitor unless its JavaDoc says otherwise.

Will not expect the caller to do anything with the string passed to beginTask unless
its JavaDoc says otherwise.

In practice, callers will be using SubMonitor wherever possible and method
implementations will not be calling done(). This means that the only
calls to done() will occur in root-level methods (methods which obtain
their own IProgressMonitor via some mechanism other than having it passed it as a method parameter).

5.0 Different versions of Eclipse

In Eclipse 4.5 (Mars) and earlier, it was standard practice for method implementations
to invoke done() on any monitor passed in as an argument and for the caller
to rely upon this fact.

In Eclipse 4.6 (Neon), method implementations should still invoke done() if
they did so previously. Callers are also required to either invoke done()
or select a monitor implementation like SubMonitor which doesn't require the use of
done().

In Eclipse 4.7 (Oxygen) and higher, method implementations are not required to invoke
done(). Callers must either invoke done() or select a monitor
implementation like SubMonitor which doesn't require the use of done().

5.1 Changes in Eclipse 4.6

The following changes were made in Eclipse 4.6:

SubMonitor now has a static done(IProgressMonitor) method that can be used to
call done() on a possibly-null IProgressMonitor instance.

The recommended policy for checking cancellation has changed. Rather than
using explicit cancellation checks, clients should rely on the implicit
cancellation checks done by SubMonitor.split.

SubProgressMonitor is now deprecated.

As of Eclipse 4.6 (Neon), any client code that obtains a root monitor (any monitor
that isn't passed in as a method argument) is responsible for invoking done()
on that monitor. It must not rely on the methods it calls to invoke
done(). Please see the
Migration guide
for more information on how to locate such code.

Method implementations that previously invoked done() should
continue to do so, since the root monitors need to be updated first.

5.2 Migrating from SubProgressMonitor to SubMonitor

Eclipse 3.2 and earlier used SubProgressMonitor for nesting progress monitors. This class is
deprecated as of Eclipse 4.6 (Neon). It has been replaced by SubMonitor.

The process for converting code which used SubProgressMonitor into SubMonitor is:

Calls to IProgressMonitor.beginTask on the root monitor should be replaced by a call
to SubMonitor.convert. Keep the returned SubMonitor around as a local variable and refer
to it instead of the root monitor for the remainder of the method.

All calls to new SubProgressMonitor(IProgressMonitor, int) should be replaced by calls to
SubMonitor.split(int).

If a SubProgressMonitor is constructed using the SUPPRESS_SUBTASK_LABEL flag, replace it with the
two-argument version of SubMonitor.split(int, int) using SubMonitor.SUPPRESS_SUBTASK
as the second argument.

It is not necessary to call done on an instance of SubMonitor.

There is no drop-in replacement for PREPEND_MAIN_LABEL_TO_SUBTASK. PREPEND_MAIN_LABEL_TO_SUBTASK
made use of string arithmetic that didn't handle translation well. Clients that were using this
previously should switch to using fully-formatted task labels instead.

Note that SubMonitor.convert(monitor, ticks) is not a direct replacement for
new SubProgressMonitor(monitor, ticks). The former fully consumes a
monitor which hasn't had ticks allocated on it yet and creates a new monitor with the given
number of ticks allocated. The latter consumes only the given number of ticks from an input
monitor which has already had ticks allocated and produces a monitor with no ticks allocated.
If you attempt to do a search-and-replace of one to the other, your progress reporting won't work.

5.3 Changes in Eclipse 4.7

In Eclipse 4.7 (Oxygen) and higher, method implementations that receive a progress monitor are
not required to invoke done() on it. Such calls may be removed if they were present
in earlier versions.

Methods in plugins that are also intended for use with earlier Eclipse versions should continue calling
done() as long as those earlier Eclipse versions are still being supported by the plugin.

6.0 The protocol of IProgressMonitor

This section is for those of you doing something advanced. What if you want to implement your own
IProgressMonitor or can't use the SubMonitor helper class? In that case you'll need
to work with the IProgressMonitor contracts directly.

An IProgressMonitor instance can be in one of three states. Any given
implementation may or may not track state changes and may or may
not do anything about them.

UNALLOCATED

This is the initial state of a newly created IProgressMonitor instance,
before beginTask() has been called to allocate ticks. Attempting to
call worked() or otherwise consume ticks from a monitor in this state is an error.
From here the monitor can enter the ALLOCATED state as a result of a beginTask()
call or the FINISHED done() state as a result of a call to done().

ALLOCATED

The monitor enters this state after the first and only call to beginTask()
has allocated ticks for the monitor. Attempting to call beginTask() in this
state is an error. From here the monitor can enter the FINISHED state as a result of a
done() call.

FINISHED

The monitor enters this state after the first call to done().
Unlike beginTask(), done() may be called any number of times.
However, only the first call to done() has any effect.
Reporting work or calling beginTask() on a FINISHED monitor is a programming
error and will have no effect. Unless the implementation says otherwise, done() must always
be called on a monitor once beginTask() has been called.
It is not necessary to call done() on a monitor in the UNALLOCATED
state.

6.1 Cancellation

A monitor can become cancelled at any time due to activity on another thread.
The monitor indicates that it has been cancelled by returning
true from isCanceled(). When a long-running operation detects that
it has been cancelled, it should abort its operation cleanly.

It is technically possible to cancel a monitor by invoking
setCanceled() but you should
never do this. A long-running method that wishes to cancel itself it should
throw OperationCanceledException rather than invoking any particular method
on its monitor.

6.2 Threading

All implementations of IProgressMonitor are single-threaded by default. Unless the
JavaDoc for the specific monitor implementation says otherwise, all methods
must be invoked on the same thread that instantiated the monitor. If the concrete
type of the IProgressMonitor is unknown, it must be assumed to be single-threaded.

6.3 Allocating ticks

When using beginTask to allocate ticks on an IProgressMonitor, the caller should
always allocate at least 1000 ticks. Many root monitors use this value to set
the resolution for all subsequent progress reporting, even if the ticks are
later subdivided using a SubMonitor.

SubMonitor users don't need to worry about this since SubMonitor.convert
does this internally when converting an unknown monitor.

6.4 Passing strings to beginTask

At the lowest level, any method that needs to allocate ticks ends up calling
beginTask. Usually this is done indirectly by a call to
SubMonitor.convert(...). Unfortunately, beginTask
also takes a string argument which root monitors use to set the task name.

Every long-running operation needs to allocate ticks but most don't want to
modify the task name. For this reason, most callers of beginTask
call it with the empty string or a temporary string that isn't intended
to be seen by the end user. Similarly, most implementations of
beginTask are expected to ignore the string argument.

The exception is root monitors. Many root monitors display the string argument
somewhere. For this reason, any code that obtains a root monitor is expected
to convert it to a form that will filter out the string argument
before passing it to another method.

The default expectation is that progress monitors passed as parameters will
do this filtering. Any method that receives a progress monitor and does not
want the beginTask(...) argument filtered must say so clearly in
its JavaDoc.

6.5 IProgressMonitor.UNKNOWN

IProgressMonitor.UNKNOWN is only supported by root monitors and should never
be used by method implementations which receive a progress monitor as a parameter.

6.6 Task names and performance concerns

As of the time of this writing (Eclipse 4.6), several of the root monitors
in Eclipse have expensive implementations of setTaskName(...)
and subTask(...). Plugin authors are advised not to call
these methods more than 3 times per second. Doing so may introduce
performance problems. See
Eclipse bug 445802
for more information.