Debugging Eclipse’ SubProgressMonitor and SubMonitor: 0% work completed or work not reported

SubProgressMonitor and SubMonitor are Eclipse classes used to indicate progress on a Job or task running in the Eclipse workbench. SubProgressMonitor was deprecated in a semi-recent Eclipse release, and the preferred (and seemingly better) progress monitor class is SubMonitor. If you are creating new classes you should definitely use SubMonitor: more information is available from this Eclipse article (https://eclipse.org/articles/Article-Progress-Monitors/article.html).

However, if you are working on a mature code base with a long and storied history, it is likely that your code will contains references to both SubProgressMonitor and SubMonitor. While migrating from SubProgressMonitor to SubMonitor is preferable, this is not always possible.

SubProgressMonitor’s basic pattern is try / beginTask / new / worked / done:

SubMonitor’s basic pattern is convert / split / (worked):

While working with code that used both SubProgressMonitor and SubMonitor interchangeably, I discovered that work that being reported (split/worked) from child SubMonitors was not being reported back to parent SubProgressMonitors. Searching the web for obvious solutions revealed no suggestions, so it was time to pull the code to both classes and see what was happening.

Both SubMonitor and SubProgressMonitor are implemented in the rt.equinox.bundles repository, in the org.eclipse.equinox.common plugin.

Main repo:
https://git.eclipse.org/c/equinox/rt.equinox.bundles.git/

Github mirror:
https://github.com/eclipse/rt.equinox.bundles

To pull the repo, use:
git clone git://git.eclipse.org/gitroot/equinox/rt.equinox.bundles.git

SubMonitor is implemented in:
rt.equinox.bundles\bundles\org.eclipse.equinox.common\src\org\eclipse\core\runtime\SubMonitor.java

SubProgressMonitor is implemented in:
rt.equinox.bundles\bundles\org.eclipse.equinox.common\src\org\eclipse\core\runtime\SubProgressMonitor.java

Import the org.eclipse.equinox.common plug-in into your workspace (Import > Git > Projects from Git).

( If you are targeting pre-Neon Eclipse release, use newChild(…) instead of split. As per the above Eclipse article, “the two do pretty much the same thing except that split(…) also performs cancellation checks.” )

The specific issue I encountered was that progress (work ticks) were not being reported back to the user in the Eclipse workbench UI, even though methods were passed an IProgressMonitor, and were correctly calling SubMonitor.(convert/newChild).

There can be a few reason for this:
1) A NullProgressMonitor was substituted for a Job-backed IProgressMonitor
2) The tick scale is too large (eg you have indicated that your work will include 1000 ticks, but you have only indicated work of 1–10 ticks, which may round down to 0%)
3) One of the parent monitors is discarding the work due to a previous breach of the SubProgressMonitor ‘contract’.

Each SubMonitor/SubProgressMonitor has a single parent. These monitors form a single-branched hierarchical tree of monitors, beginning with a progress monitor provided by an Eclipse Job. SubProgressMonitor and SubMonitor may be used together, so a monitor tree may look like this:

IProgressMonitor provided by job (actually an inner class of Job implementing the interface)
\_ SubProgressMonitor
\_ SubProgressMonitor
\_ SubMonitor
\_ etc

Each SubMonitor/SubProgressMonitor has at most a single parent. When debugging, the parent of a monitor can be accessed from an instance variable of the monitor class:

  • SubMonitor.root.root of type IProgressMonitor
  • SubProgressMonitor.progressMonitor of type IProgressMonitor

With this information, you may examine the monitor tree of a problematic monitor, by beginning with the child monitor and “climbing the tree” until an issue is identified.

How to debug NullProgressMonitor parent in the monitor tree

Locate a place in your code where work is being worked but not reported. This would be an IProgressMonitor.worked(…)/SubMonitor.newChild(…) or SubMonitor.split(…) call. Add a breakpoint on this method, and debug your application to cause the statement to be triggered.

Step inside the call worked/split call. From here you can examine move up the tree of progress monitors to determine if any of the parents is a NullProgressMonitor. If the type is a SubMonitor, check the root.root variable; if the type is a SubProgressMonitor, check the progressMonitor variable.

If the issue a NullProgressMonitor as one of the parents, you will likely find an instance of this class somewhere up the tree; removing this will likely solve the issue.

How to debug a parent monitor discarding a child’s work

This is more difficult than merely scanning the tree for NullProgressMonitor as above. The SubProgressMonitor class, as written, will silently discard work reported if the user has broken the “contract” of the class.

The contract of the class, as it relates to this problem, is:

  • You must call beginTask(…) at once on a monitor; if you call it zero times (eg do not ever call beginTask(…) ), work will be ignored.
  • You should not call beginTask(…) twice on the same monitor; if you do, all subsequent work will be ignored.

This can be seen in the SubProgressMonitor.internalWorked(…) method, where if ‘nestedBeginTasks’ is not exactly one, the call is suppressed without warning.

While we can’t do anything about the implementation of SubProgressMonitor (you should be using SubMonitor, anyways), we can do something about the ‘without warning’ part.

Lets instrument the class by adding our own warnings when the contract is violated:

1) Add the following fields to SubProgressMonitor:

2) Replace the 3-arg constructor with:

3) Replace beginTask(…) as follows:

4) Replace internalWorked(…) as follows:

Note the TODO item inside the if. Since this block of code will get triggered often by other code in the workbench, it is necessary to filter out all threads EXCEPT for your own code.

With the above changes, you should now receive warnings (along with stack traces) for the following two scenarios:
1) Multiple calls to beginTask(…); the call after the first will have the stack trace reported, along with the valid original first call.
2) Zero calls to beginTask(…); the call to internalWorked(…) will have the stack trace reported, along with the stack trace of the object’s construction.

Instrumenting SubMonitor is mostly not necessary. It is smarter in how it is written, and includes warnings when it’s contract is violated. See the logProblem(…) method in SubMonitor for details.