Bug 210394 - SubMonitor.convert(SubMonitor) isn't useful
Summary: SubMonitor.convert(SubMonitor) isn't useful
Status: RESOLVED FIXED
Alias: None
Product: Equinox
Classification: Eclipse Project
Component: Compendium (show other bugs)
Version: 3.4   Edit
Hardware: PC Windows XP
: P3 enhancement (vote)
Target Milestone: 3.4 M5   Edit
Assignee: equinox.compendium-inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords: contributed
Depends on:
Blocks:
 
Reported: 2007-11-20 09:36 EST by Min Idzelis CLA
Modified: 2008-01-07 16:21 EST (History)
3 users (show)

See Also:


Attachments
Test Code to illustrate bug (1.61 KB, text/plain)
2007-12-08 17:07 EST, Min Idzelis CLA
no flags Details
Addresses the behavior described as "issue 1" (761 bytes, text/plain)
2007-12-09 21:55 EST, Stefan Xenos CLA
no flags Details
Updates the test suites to check for issue 1 (1.34 KB, text/plain)
2007-12-09 21:57 EST, Stefan Xenos CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Min Idzelis CLA 2007-11-20 09:36:53 EST
Currently, when you try to use a SubMonitor.convert to convert the incoming monitor into a SubMonitor, but the incoming parameter is already a SubMonitor, the convert method just returns itself. 

This isn't very useful. 

I think it would be more useful to have convert() behave similarly for SubMonitor arguments as it does for IProgressMonitor arguments. 

That is,

If you convert a SubMonitor into a new SubMonitor, it should create a new SubMonitor (or as an optimization, it could call setRemainingWork() on the passed in SubMonitor)

For example, this doesn't work as expected

SubMonitor monitor = SubMonitor.convert( iprogressmonitor );
monitor.beginTask("",2);

SubMonitor step1 = monitor.newChild(1);
...
step1.done();

SubMonitor step2 = monitor.newChild(2);
... // Here we find out that we had really 5 additional steps to accomplish
SubMonitor subStep2 = SubMonitor.convert(step2, 5);
subStep2.worked(1);
subStep2.worked(1);
subStep2.worked(1);
subStep2.worked(1);
subStep2.worked(1);

Here, since subStep2 is really the same instance as step2, calling all the worked(1) methods on it will cause the progress monitor to automatically go to completetion earlier than what was expected.
Comment 1 John Arthorne CLA 2007-11-21 15:44:51 EST
> If you convert a SubMonitor into a new SubMonitor, it should create a new
> SubMonitor (or as an optimization, it could call setRemainingWork() on the
> passed in SubMonitor)

Isn't this what it currently does?  SubMonitor.convert calls SubMonitor.beginTask, which calls SubMonitor.setWorkRemaining.
Comment 2 Stefan Xenos CLA 2007-12-05 19:58:20 EST
Min, the code already works the way you're requesting it to.

> Here, since subStep2 is really the same instance as step2, calling all 
> the worked(1) methods on it will cause the progress monitor to automatically 
> go to completetion earlier than what was expected.

If what you say is true, then it means that there is a bug in setWorkRemaining(...), but it is not caused by the fact that the monitor shares the same instance.

> SubMonitor subStep2 = SubMonitor.convert(step2, 5);

If you replace this with:

step2.setWorkRemaining(5)

...your example will do exactly the same thing.

Comment 3 Stefan Xenos CLA 2007-12-07 00:14:50 EST
Just to be sure that everything works, I wrote the following JUnit test. It passes. SubMonitor is working correctly, and does not reach completion until the very end of the method.


	public void test21039() {
		TestProgressMonitor testMonitor = new TestProgressMonitor();
		SubMonitor monitor = SubMonitor.convert( testMonitor );
		monitor.beginTask("",2);

		SubMonitor step1 = monitor.newChild(1);
		step1.done();
		
		assertEquals(500.0, testMonitor.getTotalWork(), 1.0);

		SubMonitor step2 = monitor.newChild(2);
		// Here we find out that we had really 5 additional steps to accomplish
		SubMonitor subStep2 = SubMonitor.convert(step2, 5);
		subStep2.worked(1);
		assertEquals(600.0, testMonitor.getTotalWork(), 1.0);
		subStep2.worked(1);
		assertEquals(700.0, testMonitor.getTotalWork(), 1.0);
		subStep2.worked(1);
		assertEquals(800.0, testMonitor.getTotalWork(), 1.0);
		subStep2.worked(1);
		assertEquals(900.0, testMonitor.getTotalWork(), 1.0);
		subStep2.worked(1);
		assertEquals(1000.0, testMonitor.getTotalWork(), 1.0);
	}
Comment 4 Stefan Xenos CLA 2007-12-07 00:16:17 EST
CC'ing obesedin in case he wants to include this regression test as part of SubMonitorTest.
Comment 5 John Arthorne CLA 2007-12-07 09:15:16 EST
Thanks Stefan. I have committed the JUnit test. Extra tests are always good.
Comment 6 Min Idzelis CLA 2007-12-08 17:05:37 EST
I have updates. 

I think you've proved me wrong, but I still have some issues. 

Issue 1)

Seems like the documentation for SubMonitors encourages you not to call done() on the arguments that are returned to you. If you don't do this, the percentage complete isn't successfully computed. (The next usage of the parent monitor doesn't automatically "work" the remaining results)

(see enclosed test case to reproduce the problem)

Issue 2) 

I think this "gotcha" was got me starting thinking that this was a bug in SubMonitor. (Which of course it really wasn't, but it was an easy mistake to make.)

Seems like it's unwise to call SubMonitor.convert() when the passed in argument is a SubMonitor that's already had beginTask() called on it, and had already had some work performed on it. This is because the SubMonitor.convert() will basically reset the total worked on the monitor to a new value. Seems like what the programmer wanted to do was call do a newChild() first, and then other subroutines could safely issue a SubMonitor.convert() on the result. Seems like this would be a common programmer error, and a big gotcha. 

Some code to illustrate the gotcha

SubMonitor sub1 = SubMonitor.convert(monitor);
sub1.beginTask("", 4);
sub1.worked(1); // first sub task (25% of total)
SubMonitor converted = SubMonitor.convert(sub1, 5); // OOPS
converted.worked(5);
sub1.worked(1); // third sub task (25% of total)

-----------------
The OOPS line really should have been this:

SubMonitor nested = sub1.newChild(2); // second subtask (50% of total)
SubMonitor converted = SubMonitor.convert(nested, 5); // OOPS

Is there a way to detect this condition and throw an IllegalArgumentException to help the programmer with this gotcha?
Comment 7 Min Idzelis CLA 2007-12-08 17:07:06 EST
Created attachment 84809 [details]
Test Code to illustrate bug
Comment 8 Min Idzelis CLA 2007-12-08 17:08:14 EST
Reopen bug
Comment 9 Stefan Xenos CLA 2007-12-09 21:34:29 EST
Issue 1:

> The next usage of the parent monitor doesn't automatically "work" the 
> remaining results

I suppose this could be changed without breaking anything. I didn't do it this way originally because I was concerned that a future code change might cause the child to delegate to the parent's worked(...) method in some situation, which would cause the child to be prematurely consumed.

However, I can see how this would produce more accurate progress in the situation where you're mixing a lot of newChild(...) calls with worked(...) and never calling done(). John: What do you think?


> Is there a way to detect this condition and throw an IllegalArgumentException

That's a good idea and wouldn't have been too hard to do... but there's no way we could make this change at this point, since it would break API.

It might be possible to add a new convertUnused(...) method that would make the check... but having too many convert(...) methods with subtly different semantics may make the API confusing. 

A better approach might be to add getters to SubMonitor that let you determine if the monitor has ever reported any work. That would let clients write such a helper method themselves.
Comment 10 Stefan Xenos CLA 2007-12-09 21:55:45 EST
Created attachment 84830 [details]
Addresses the behavior described as "issue 1"

Should we decide that this is correct behavior, this patch makes SubMonitor clean up unconsumed work from its children when you call worked() or internalWorked() on the parent.
Comment 11 Stefan Xenos CLA 2007-12-09 21:57:53 EST
Created attachment 84831 [details]
Updates the test suites to check for issue 1

Updates the test suites to check for issue 1.
Comment 12 Stefan Xenos CLA 2007-12-09 22:00:53 EST
I've attached patches for issue 1.

Regarding issue 2: We might want to consider adding a getWorkRemaining() method to  SubMonitor - that would let clients write a version of convert(...) that asserts that no ticks have been allocated on the converted monitor.
Comment 13 Oleg Besedin CLA 2008-01-07 16:21:35 EST
Applied patch to CVS Head, thanks Stefan!

As for addition of getWorkRemaining(), that seems to over complicate things as if developer realizes that he needs to check getWorkRemaining(), he probably knows the difference between #newChild() and #convert(SubMonitor) and used the proper method to beging with. 

So while it is possible to imagine a situation where getWorkRemaining() would be nice to have, it is probably not the one described in issue 2.