Bug 200997 - [Progress] Can't seem to cancel jobs from ProgressView
Summary: [Progress] Can't seem to cancel jobs from ProgressView
Status: RESOLVED FIXED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: Runtime (show other bugs)
Version: 3.3.1   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: 3.5 M3   Edit
Assignee: John Arthorne CLA
QA Contact:
URL:
Whiteboard:
Keywords: greatbug
: 269817 (view as bug list)
Depends on: 122986
Blocks:
  Show dependency tree
 
Reported: 2007-08-23 17:33 EDT by Joe Winchester CLA
Modified: 2009-04-03 13:29 EDT (History)
9 users (show)

See Also:


Attachments
Fix that seems to enable jobs to be cancelled from the ProgressView (11.41 KB, text/plain)
2007-08-23 17:33 EDT, Joe Winchester CLA
no flags Details
Plug-in/View to test it interactively (29.31 KB, application/zip)
2008-10-03 03:48 EDT, Stephan Wahlbrink CLA
no flags Details
Patch fixing it on jobs level (2.97 KB, patch)
2008-10-03 03:53 EDT, Stephan Wahlbrink CLA
john.arthorne: iplog+
Details | Diff
Patch for automatic tests (3.01 KB, patch)
2008-10-03 03:54 EDT, Stephan Wahlbrink CLA
john.arthorne: iplog+
Details | Diff
Updated patch (9.47 KB, patch)
2008-10-03 15:46 EDT, John Arthorne CLA
no flags Details | Diff
Plug-in/View to test it interactively (31.73 KB, application/zip)
2008-10-03 17:14 EDT, Stephan Wahlbrink CLA
no flags Details
Fix and extended test that canceling() is called only the first time (3.74 KB, patch)
2008-10-04 08:37 EDT, Stephan Wahlbrink CLA
john.arthorne: iplog+
Details | Diff
Updated patch (3.73 KB, patch)
2008-10-06 21:41 EDT, John Arthorne CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Joe Winchester CLA 2007-08-23 17:33:54 EDT
Created attachment 76826 [details]
Fix that seems to enable jobs to be cancelled from the ProgressView

Symptom.  Create non user non system jobs and can't seem to cancel them from progress view.

Debugged it and found the code in JobInfo

 public void cancel() {
        this.canceled = true;
        this.job.cancel();
        //Call the refresh so that this is updated immediately
        ProgressManager.getInstance().refreshJobInfo(this);
    }

Note that this sets the cancel status to true, before calling this.job.cancel
This then ends up calling

JobManager.cancel(InternalJob aJob){
...
	if (monitor != null) {
			if (!monitor.isCanceled()) {
				monitor.setCanceled(true);
				job.canceling();
			}
			return false;
		}
..

Note that what this does is check the monitor to see if it's already cancelled.  This does this by returning the JobInfo from the job that was already cancelled and the cancelling method on the job is never called.

the fix that seems to work is to set the isCancelled to true before doing the cancel of the job, and also having a flag to stop recursion.
 private boolean isCancelling = false;
 public void cancel() {
  	if(!isCancelling){
    		isCancelling = true;
    		this.job.cancel();
    		this.canceled = true;        
    		//	Call the refresh so that this is updated immediately
    		ProgressManager.getInstance().refreshJobInfo(this);
    	}
    }
    }
Comment 1 Tod Creasey CLA 2007-10-15 15:18:39 EDT
Looking at the code in JobManager we hit the following before we hit the code you are pointing out:

synchronized (lock) {
			switch (job.getState()) {
				case Job.NONE :
					return true;
				case Job.RUNNING :
					//cannot cancel a job that has already started (as opposed to ABOUT_TO_RUN)
					if (job.internalGetState() == Job.RUNNING) {
						monitor = job.getProgressMonitor();
						break;
					}
					//signal that the job should be canceled before it gets a chance to run
					job.setAboutToRunCanceled(true);
					return true;
				default :
					changeState(job, Job.NONE);
			}
		}

We would only get to the code you are concerned about if the job is waiting or if it is sleeping which doesn't jive with what you are describing.

Is your problem that sleeping or waiting jobs are not getting the cancelling call?


Comment 2 Tod Creasey CLA 2007-10-15 15:21:52 EDT
Sorry - found the use case. It works best on a long running job - my test job was being closed while I debugged.

So the problem is that the job gets cancelled from the progress view but cancelling() is not being called correct?
Comment 3 Tod Creasey CLA 2007-10-15 15:39:15 EDT
The problem here is the addition of the new #cancelling() API in Job. If a JopbInfo gets cancelled then we need to leave it in an uncancelled state so that Job#cancelling gets called.

The problem is that the job will call cancel again on the JobInfo. Keeping a second flag as you suggest is the way to prevent this problem but I am reluctant to add yet another piece of state.

John we should talk this over.
Comment 4 Joe Winchester CLA 2007-10-16 07:50:01 EDT
Yes, the problem is that the cancelling() method isn't called.  The job I have is a long running task that is doing database IO and I need to be called back when it's cancelled so I can close connections and set state so that threads run to completion and so forth. 
I agree that the extra state is hacky and a better solution should be found - whatever works best as long as the job gets called back.
Cheers, Joe
Comment 5 Edoardo Comar CLA 2007-10-31 12:53:39 EDT
Just FYI - the bug applies to both user and non-user jobs.
canceling gets called in a user job if the user action was to click on cancel in the progress DIALOG, while canecling does not get called (as in non-user jobs) one the user jobs is 'pushed' in the background and the canceling request comes thru the progress view rather than the dialog.
The dialog invoeks job.cancel() and that works
The progressview invokes jobinfo.cancel() and that's broken.

could you just fix the progress ui to invoke jobinfo.getJob().cancel() ...
Comment 6 Edoardo Comar CLA 2007-10-31 13:41:05 EDT
(In reply to comment #5)
> could you just fix the progress ui to invoke jobinfo.getJob().cancel() ...

well works but it's ugly since there are various places that call
org.eclipse.ui.internal.progress.JobTreeElement.cancel()

:(

Comment 7 Tod Creasey CLA 2007-12-05 08:59:34 EST
Eduardo re: comment #5 this is what we call in JobInfo#cancel()
Comment 8 Edoardo Comar CLA 2007-12-05 12:45:33 EST
(In reply to comment #7)
> Eduardo re: comment #5 this is what we call in JobInfo#cancel()

sorry I did not explain myself well - the Ui calls JobInfo.cancel()
which is not a straight call to job.cancel (therein lies this bug!).

this is the call stack 

Thread [main] (Suspended (breakpoint at line 95 in JobInfo))	
	JobInfo.cancel() line: 95	
	ProgressInfoItem.cancelOrRemove() line: 315	
	ProgressInfoItem$1.widgetSelected(SelectionEvent) line: 228	
	TypedListener.handleEvent(Event) line: 227	
	EventTable.sendEvent(Event) line: 66	
	ToolItem(Widget).sendEvent(Event) line: 1101	
	Display.runDeferredEvents() line: 3319	
	Display.readAndDispatch() line: 2971	


if the UI called jobinfo.getJob().cancel() instead 
the cancellation would actually work - but the JobInfo.cancel() class is actually used elsewhere so fixing the UI as I initially thought is not viable. 

Joe's solution is.
Comment 9 Tod Creasey CLA 2007-12-05 12:58:44 EST
So are you saying

 this.canceled = true;
        this.job.cancel();
        //Call the refresh so that this is updated immediately
        ProgressManager.getInstance().refreshJobInfo(this);

should be 

this.job.cancel();
this.canceled = true;
//Call the refresh so that this is updated immediately
 ProgressManager.getInstance().refreshJobInfo(this);
Comment 10 Edoardo Comar CLA 2007-12-05 13:02:07 EST
(In reply to comment #9)

Tod, I think Joe gave the correct solution in post #1

<quote>
the fix that seems to work is to set the isCancelled to true before doing the
cancel of the job, and also having a flag to stop recursion.
 private boolean isCancelling = false;
 public void cancel() {
        if(!isCancelling){
                isCancelling = true;
                this.job.cancel();
                this.canceled = true;        
                //      Call the refresh so that this is updated immediately
                ProgressManager.getInstance().refreshJobInfo(this);
        }
    }
    }
</quote>
Comment 11 Joe Winchester CLA 2007-12-06 05:47:17 EST
(In reply to comment #9)
> So are you saying
>  this.canceled = true;
>         this.job.cancel();
>         //Call the refresh so that this is updated immediately
>         ProgressManager.getInstance().refreshJobInfo(this);
> should be 
> this.job.cancel();
> this.canceled = true;
> //Call the refresh so that this is updated immediately
>  ProgressManager.getInstance().refreshJobInfo(this);

Hi Tod,

No this fix won't work by just swopping the two lines.  If you do this.job.cancel() before setting the canceled flag to true it goes into a recursive overflow.  That's why the extra "isCancelling" flag works best.

Regards,

Joe
Comment 12 Tod Creasey CLA 2007-12-06 10:34:44 EST
Thanks for the analysis everyone. John and I have talked and we agree that it is better to be solved at the Jobs level as other monitors may have this issue.
Comment 13 Edoardo Comar CLA 2008-09-30 12:03:28 EDT
Hi everyboidy - this bug seems to have been forgotten. 
Are there any plans to address it ?
Comment 14 John Arthorne CLA 2008-09-30 13:51:43 EDT
Not forgotten, just not very high on the priority list.
Comment 15 Stephan Wahlbrink CLA 2008-10-03 03:48:49 EDT
Created attachment 114158 [details]
Plug-in/View to test it interactively

Plug-in with a view to test easily job canceling in the Workbench
Comment 16 Stephan Wahlbrink CLA 2008-10-03 03:53:27 EDT
Created attachment 114160 [details]
Patch fixing it on jobs level

Patch adds a flag to InternalJob and checking it. 
So core-jobs plug-in works correctly (#canceling is called exactly one time) independent of progress implementation. The check is synchronized without further effort; recursive infinitive loop cannot happen.
Comment 17 Stephan Wahlbrink CLA 2008-10-03 03:54:46 EDT
Created attachment 114161 [details]
Patch for automatic tests

Adds an additional test for canceling by progress monitor
Comment 18 John Arthorne CLA 2008-10-03 15:46:58 EDT
Created attachment 114221 [details]
Updated patch

Stephan, thank you for the attached fix, regression test, and interactive test plugin, you know how to win a busy committer's heart.

However, there is one problem with your patch that you never reset the new flag. This means if the job is run a second time the canceling() method is never called. This was causing some deadlocks in other parts of the job test suite.

Attached is an updated fix that resets the flag after calling canceling(). I have also updated the test to catch this extra case, and avoided the change to the default progress provider by using a test progress provider we have in the test framework.
Comment 19 Stephan Wahlbrink CLA 2008-10-03 16:59:58 EDT
(In reply to comment #18)
> However, there is one problem with your patch that you never reset the new
> flag. This means if the job is run a second time the canceling() method is
> never called. This was causing some deadlocks in other parts of the job test
> suite.

Yes, you are right, I forgot to reset the flag. But I think your code resets the flag too early, it does no longer prevent multiple calls of canceling() in a single run. The right position would be before or after a job is running, perhaps in JobManager#changeState for the case of new state RUNNING/ABOUNT_TO_RUN (ca. line 349)?

> and avoided the change to
> the default progress provider by using a test progress provider we have in the
> test framework.

Indeed the better solution.
Comment 20 Stephan Wahlbrink CLA 2008-10-03 17:14:20 EDT
Created attachment 114231 [details]
Plug-in/View to test it interactively

update allowing to reuse jobs.
Comment 21 Stephan Wahlbrink CLA 2008-10-04 08:37:39 EDT
Created attachment 114258 [details]
Fix and extended test that canceling() is called only the first time

Patch with bugfix as proposed in comment #19.
Comment 22 John Arthorne CLA 2008-10-06 21:41:35 EDT
Created attachment 114366 [details]
Updated patch

Thanks again Stephan. I made a small change to clear the flag when the state becomes NONE rather than when the job is started. It seems cleaner for the job to be in a completely fresh state when in the NONE (unscheduled) state.
Comment 23 John Arthorne CLA 2008-10-06 21:42:22 EDT
Fix and tests released.
Comment 24 John Arthorne CLA 2009-04-03 13:19:11 EDT
*** Bug 269817 has been marked as a duplicate of this bug. ***