mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-07-02 06:35:28 +02:00
[251806] - [concurrent] Step in a sequence cannot control the progress monitor of the sequence
This commit is contained in:
parent
b27e322c49
commit
e2c7961a23
6 changed files with 459 additions and 26 deletions
|
@ -0,0 +1,41 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008 Wind River Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Wind River Systems - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.dd.dsf.concurrent;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
|
||||
/**
|
||||
* A request monitor which uses a progress monitor as a parent. When the parent
|
||||
* progress monitor is canceled, the request monitor will also be canceled,
|
||||
* although the cancellation listeners will not be called.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public class RequestMonitorWithProgress extends RequestMonitor {
|
||||
|
||||
private final IProgressMonitor fProgressMonitor;
|
||||
|
||||
public RequestMonitorWithProgress(Executor executor, IProgressMonitor progressMonitor) {
|
||||
super(executor, null);
|
||||
fProgressMonitor = progressMonitor;
|
||||
}
|
||||
|
||||
public IProgressMonitor getProgressMonitor() {
|
||||
return fProgressMonitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isCanceled() {
|
||||
return super.isCanceled() || fProgressMonitor.isCanceled();
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
*
|
||||
* Contributors:
|
||||
* Wind River Systems - initial API and implementation
|
||||
* Nokia - added StepWithProgress. Oct, 2008
|
||||
*******************************************************************************/
|
||||
package org.eclipse.dd.dsf.concurrent;
|
||||
|
||||
|
@ -23,6 +24,7 @@ import org.eclipse.core.runtime.IStatus;
|
|||
import org.eclipse.core.runtime.MultiStatus;
|
||||
import org.eclipse.core.runtime.NullProgressMonitor;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.SubProgressMonitor;
|
||||
import org.eclipse.dd.dsf.concurrent.RequestMonitor.ICanceledListener;
|
||||
import org.eclipse.dd.dsf.internal.DsfPlugin;
|
||||
|
||||
|
@ -91,8 +93,53 @@ abstract public class Sequence extends DsfRunnable implements Future<Object> {
|
|||
* step.
|
||||
*/
|
||||
public int getTicks() { return 1; }
|
||||
|
||||
/**
|
||||
* Task name for this step. This will be displayed in the label of the
|
||||
* progress monitor of the owner sequence.
|
||||
*
|
||||
* @return name of the task carried out by the step, can be
|
||||
* <code>null</code>, in which case the overall task name will be used.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public String getTaskName() {
|
||||
return ""; //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A step that will report execution progress by itself on the progress
|
||||
* monitor of the owner sequence.<br>
|
||||
* <br>
|
||||
* Note we don't offer a rollBack(rm, pm) as we don't want end user to be
|
||||
* able to cancel the rollback.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
abstract public static class StepWithProgress extends Step {
|
||||
|
||||
@Override
|
||||
// don't allow subclass to implement this by "final" it.
|
||||
final public void execute(RequestMonitor rm) {
|
||||
assert false : "execute(RequestMonitor rm, IProgressMonitor pm) should be called instead"; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the step with a progress monitor. Note the given progress
|
||||
* monitor is a sub progress monitor of the owner sequence which is
|
||||
* supposed to be fully controlled by the step. Namely the step should
|
||||
* call beginTask() and done() of the monitor.
|
||||
*
|
||||
* @param rm
|
||||
* @param pm
|
||||
*/
|
||||
public void execute(RequestMonitor rm, IProgressMonitor pm) {
|
||||
rm.done();
|
||||
pm.done();
|
||||
}
|
||||
}
|
||||
|
||||
/** The synchronization object for this future */
|
||||
final Sync fSync = new Sync();
|
||||
|
||||
|
@ -125,28 +172,84 @@ abstract public class Sequence extends DsfRunnable implements Future<Object> {
|
|||
|
||||
final private IProgressMonitor fProgressMonitor;
|
||||
|
||||
|
||||
|
||||
/** Convenience constructor with limited arguments. */
|
||||
public Sequence(DsfExecutor executor) {
|
||||
this(executor, new NullProgressMonitor(), "", "", null); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
/** Convenience constructor with limited arguments. */
|
||||
/**
|
||||
* Creates a sequence with a request monitor. If the client cancels the
|
||||
* request monitor, then the request monitors in the
|
||||
* {@link Step#execute(RequestMonitor)}
|
||||
* implementations will immediately call the cancel listeners to notify.
|
||||
*
|
||||
* @param executor The DSF executor which will be used to invoke all
|
||||
* steps.
|
||||
* @param rm The request monitor which will be invoked when the sequence
|
||||
* is completed.
|
||||
*/
|
||||
public Sequence(DsfExecutor executor, RequestMonitor rm) {
|
||||
this(executor, new NullProgressMonitor(), "", "", rm); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sequence with a progress monitor. If the progress monitor is
|
||||
* canceled, then request monitors in the
|
||||
* {@link Step#execute(RequestMonitor)} implementations will need to call
|
||||
* rm.isCanceled() to discover the cancellation.
|
||||
* @param executor The DSF executor which will be used to invoke all
|
||||
* steps.
|
||||
* @param pm Progress monitor for monitoring this sequence.
|
||||
* @param taskName Name that will be used in call to
|
||||
* {@link IProgressMonitor#beginTask(String, int)},when the task is
|
||||
* started.
|
||||
* @param rollbackTaskName Name that will be used in call to
|
||||
* {@link IProgressMonitor#subTask(String)} if the task is canceled or
|
||||
* aborted.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public Sequence(DsfExecutor executor, IProgressMonitor pm, String taskName, String rollbackTaskName) {
|
||||
this(executor, pm, "", "", null); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sequence with a request monitor that includes a progress
|
||||
* monitor.
|
||||
* @param executor The DSF executor which will be used to invoke all
|
||||
* steps.
|
||||
* @param rm The request monitor containing the progress monitor
|
||||
* @param taskName Name that will be used in call to
|
||||
* {@link IProgressMonitor#beginTask(String, int)},when the task is
|
||||
* started.
|
||||
* @param rollbackTaskName Name that will be used in call to
|
||||
* {@link IProgressMonitor#subTask(String)} if the task is canceled or
|
||||
* aborted.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public Sequence(DsfExecutor executor, RequestMonitorWithProgress rm, String taskName, String rollbackTaskName) {
|
||||
this(executor, rm.getProgressMonitor(), "", "", rm); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that initialized the steps and the result callback.
|
||||
* @param executor The DSF executor which will be used to invoke all steps.
|
||||
* @param pm Progress monitor for monitoring this sequence. This parameter cannot be null.
|
||||
* @param taskName Name that will be used in call to {@link IProgressMonitor#beginTask(String, int)},
|
||||
* when the task is started.
|
||||
* @param rollbackTaskName Name that will be used in call to {@link IProgressMonitor#subTask(String)}
|
||||
* if the task is cancelled or aborted.
|
||||
* @param Result that will be submitted to executor when sequence is finished. Can be null if calling from
|
||||
* non-executor thread and using {@link Future#get()} method to wait for the sequence result.
|
||||
* @param executor The DSF executor which will be used to invoke all
|
||||
* steps.
|
||||
* @param pm Progress monitor for monitoring this sequence. This
|
||||
* parameter cannot be null.
|
||||
* @param taskName Name that will be used in call to
|
||||
* {@link IProgressMonitor#beginTask(String, int)},when the task is
|
||||
* started.
|
||||
* @param rollbackTaskName Name that will be used in call to
|
||||
* {@link IProgressMonitor#subTask(String)} if the task is canceled or
|
||||
* aborted.
|
||||
* @param Result that will be submitted to executor when sequence is
|
||||
* finished. Can be null if calling from non-executor thread and using
|
||||
* {@link Future#get()} method to wait for the sequence result.
|
||||
*
|
||||
* @deprecated This constructor should not be used because it creates a
|
||||
* potential ambiguity when one of the two monitors is canceled.
|
||||
*/
|
||||
public Sequence(DsfExecutor executor, IProgressMonitor pm, String taskName, String rollbackTaskName, RequestMonitor rm) {
|
||||
fExecutor = executor;
|
||||
|
@ -283,31 +386,59 @@ abstract public class Sequence extends DsfRunnable implements Future<Object> {
|
|||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Proceed with executing next step.
|
||||
fCurrentStepIdx = nextStepIndex;
|
||||
try {
|
||||
getSteps()[fCurrentStepIdx].execute(new RequestMonitor(fExecutor, null) {
|
||||
Step currentStep = getSteps()[fCurrentStepIdx];
|
||||
final boolean stepControlsProgress = (currentStep instanceof StepWithProgress);
|
||||
|
||||
RequestMonitor rm = new RequestMonitor(fExecutor, fRequestMonitor) {
|
||||
final private int fStepIdx = fCurrentStepIdx;
|
||||
|
||||
@Override
|
||||
public void handleCompleted() {
|
||||
public void handleSuccess() {
|
||||
// Check if we're still the correct step.
|
||||
assert fStepIdx == fCurrentStepIdx;
|
||||
|
||||
// Proceed to the next step.
|
||||
if (isSuccess()) {
|
||||
if (!stepControlsProgress) {
|
||||
// then sequence handles the progress report.
|
||||
fProgressMonitor.worked(getSteps()[fStepIdx].getTicks());
|
||||
executeStep(fStepIdx + 1);
|
||||
} else {
|
||||
abortExecution(getStatus());
|
||||
}
|
||||
executeStep(fStepIdx + 1);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleCancel() {
|
||||
Sequence.this.cancel(false);
|
||||
cancelExecution();
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void handleErrorOrWarning() {
|
||||
abortExecution(getStatus());
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Sequence \"" + fTaskName + "\", result for executing step #" + fStepIdx + " = " + getStatus(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
}
|
||||
});
|
||||
} catch(Throwable t) {
|
||||
};
|
||||
|
||||
fProgressMonitor.subTask(currentStep.getTaskName());
|
||||
|
||||
if (stepControlsProgress) {
|
||||
|
||||
// Create a sub-monitor that will be controlled by the step.
|
||||
SubProgressMonitor subMon = new SubProgressMonitor(fProgressMonitor, currentStep.getTicks(),
|
||||
SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
|
||||
|
||||
((StepWithProgress) currentStep).execute(rm, subMon);
|
||||
} else { // regular Step
|
||||
currentStep.execute(rm);
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
/*
|
||||
* Catching the exception here will only work if the exception
|
||||
* happens within the execute method. It will not work in cases
|
||||
|
@ -350,7 +481,11 @@ abstract public class Sequence extends DsfRunnable implements Future<Object> {
|
|||
|
||||
// Proceed to the next step.
|
||||
if (isSuccess()) {
|
||||
// NOTE: The getTicks() is ticks for executing the step,
|
||||
// not for rollBack,
|
||||
// though it does not really hurt to use it here.
|
||||
fProgressMonitor.worked(getSteps()[fStepIdx].getTicks());
|
||||
|
||||
rollBackStep(fStepIdx - 1);
|
||||
} else {
|
||||
abortRollBack(getStatus());
|
||||
|
|
|
@ -489,7 +489,7 @@ public class FinalLaunchSequence extends Sequence {
|
|||
DsfServicesTracker fTracker;
|
||||
|
||||
public FinalLaunchSequence(DsfExecutor executor, GdbLaunch launch, SessionType sessionType, boolean attach, IProgressMonitor pm) {
|
||||
super(executor, pm, "Configuring GDB", "Aborting configuring GDB", null);
|
||||
super(executor, pm, "Configuring GDB", "Aborting configuring GDB");
|
||||
fLaunch = launch;
|
||||
fSessionType = sessionType;
|
||||
fAttach = attach;
|
||||
|
|
|
@ -120,7 +120,7 @@ public class ServicesLaunchSequence extends Sequence {
|
|||
CSourceLookup fSourceLookup;
|
||||
|
||||
public ServicesLaunchSequence(DsfSession session, GdbLaunch launch, IProgressMonitor pm) {
|
||||
super(session.getExecutor(), pm, "Initializing debugger services", "Aborting debugger services initialization", null);
|
||||
super(session.getExecutor(), pm, "Initializing debugger services", "Aborting debugger services initialization");
|
||||
fSession = session;
|
||||
fLaunch = launch;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008 Nokia and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Nokia - initial implementation. Oct. 2008
|
||||
*******************************************************************************/
|
||||
package org.eclipse.dd.tests.dsf.concurrent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.dd.dsf.concurrent.DsfRunnable;
|
||||
import org.eclipse.dd.dsf.concurrent.RequestMonitor;
|
||||
import org.eclipse.dd.dsf.concurrent.Sequence;
|
||||
import org.eclipse.dd.tests.dsf.TestDsfExecutor;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test whether a step in a sequence can control the progress monitor.
|
||||
*/
|
||||
public class DsfSequenceProgressTests {
|
||||
private List<Throwable> fExceptions = Collections.synchronizedList(new ArrayList<Throwable>());
|
||||
TestDsfExecutor fExecutor;
|
||||
|
||||
@Before
|
||||
public void startExecutor() throws ExecutionException, InterruptedException {
|
||||
fExecutor = new TestDsfExecutor();
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdownExecutor() throws ExecutionException, InterruptedException {
|
||||
fExecutor.submit(new DsfRunnable() { public void run() {
|
||||
fExecutor.shutdown();
|
||||
}}).get();
|
||||
if (fExecutor.exceptionsCaught()) {
|
||||
Throwable[] exceptions = fExecutor.getExceptions();
|
||||
throw new ExecutionException(exceptions[0]);
|
||||
}
|
||||
fExecutor = null;
|
||||
}
|
||||
|
||||
// Create a counter for tracking number of steps performed and steps
|
||||
// rolled back.
|
||||
class IntegerHolder { int fInteger; }
|
||||
final IntegerHolder stepCounter = new IntegerHolder();
|
||||
final IntegerHolder rollBackCounter = new IntegerHolder();
|
||||
|
||||
class SleepStep extends Sequence.Step {
|
||||
|
||||
final int STEP_TIME = 5; // seconds
|
||||
|
||||
@Override
|
||||
public int getTicks() {
|
||||
return STEP_TIME;
|
||||
}
|
||||
|
||||
@Override public void execute(RequestMonitor requestMonitor) {
|
||||
stepCounter.fInteger++;
|
||||
|
||||
sleep(getTicks(), null, null);
|
||||
|
||||
requestMonitor.done();
|
||||
}
|
||||
|
||||
@Override public void rollBack(RequestMonitor requestMonitor) {
|
||||
rollBackCounter.fInteger++;
|
||||
|
||||
sleep(1, null, null);
|
||||
|
||||
requestMonitor.done();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SleepStepWithProgress extends Sequence.StepWithProgress {
|
||||
|
||||
final int STEP_TIME = 5; // seconds
|
||||
|
||||
@Override
|
||||
public int getTicks() {
|
||||
return STEP_TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(RequestMonitor rm, IProgressMonitor pm) {
|
||||
stepCounter.fInteger++;
|
||||
|
||||
pm.beginTask("", getTicks());
|
||||
|
||||
sleep(getTicks(), rm, pm);
|
||||
|
||||
rm.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollBack(RequestMonitor rm) {
|
||||
rollBackCounter.fInteger++;
|
||||
|
||||
sleep(2, null, null);
|
||||
rm.done();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
/**
|
||||
* Run this as a JUnit plugin test.
|
||||
* In the test workbench, watch the progress bar in the Progress View.
|
||||
* During execution of a StepWithProgress, you should see the progress bar
|
||||
* is growing and you can have more responsive cancel. Meanwhile, during execution
|
||||
* of a step without progress, you should see that progress bar does not
|
||||
* grow and cancel does not work until end of the step. <br>
|
||||
* <br>
|
||||
* Also watch that when you cancel the progress bar during the execution of the
|
||||
* sequence, you should see that "rollback" starts to happen.
|
||||
*/
|
||||
public void sequenceProgressTest() throws InterruptedException, ExecutionException {
|
||||
|
||||
final Sequence.Step[] steps = new Sequence.Step[] {
|
||||
|
||||
new SleepStepWithProgress() {
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "StepWithProgress #1";
|
||||
}},
|
||||
|
||||
new SleepStepWithProgress() {
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "StepWithProgress #2";
|
||||
}},
|
||||
|
||||
new SleepStep() {
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "Step #3";
|
||||
}},
|
||||
|
||||
new SleepStep() {
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "Step #4";
|
||||
}},
|
||||
};
|
||||
|
||||
|
||||
fExceptions.clear();
|
||||
|
||||
Job myJob = new Job("Run test sequence") {
|
||||
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
// Create and start.
|
||||
Sequence sequence = new Sequence(fExecutor, monitor, "Run my sequence", "Rollback my sequence") {
|
||||
@Override public Step[] getSteps() { return steps; }
|
||||
};
|
||||
fExecutor.execute(sequence);
|
||||
|
||||
// Block and wait for sequence to complete.
|
||||
try {
|
||||
sequence.get();
|
||||
} catch (InterruptedException e) {
|
||||
// ignore here.
|
||||
} catch (ExecutionException e) {
|
||||
// Expected exception, ignore here.
|
||||
} finally {
|
||||
try {
|
||||
System.out.println("StepCounter: " + stepCounter.fInteger);
|
||||
System.out.println("RollBackCounter: " + rollBackCounter.fInteger);
|
||||
|
||||
if (sequence.isCancelled())
|
||||
Assert.assertTrue(
|
||||
"Wrong number of steps were rolled back after cancellation.",
|
||||
stepCounter.fInteger == rollBackCounter.fInteger);
|
||||
else {
|
||||
Assert.assertTrue(
|
||||
"Wrong number of steps executed.",
|
||||
stepCounter.fInteger == steps.length);
|
||||
Assert.assertTrue(
|
||||
"Some steps are mistakenly rolled back",
|
||||
rollBackCounter.fInteger == 0);
|
||||
}
|
||||
|
||||
// Check state from Future interface
|
||||
Assert.assertTrue(sequence.isDone());
|
||||
} catch (AssertionFailedError e) {
|
||||
fExceptions.add(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}};
|
||||
|
||||
myJob.schedule();
|
||||
|
||||
// Wait for the job to finish
|
||||
waitForJob(myJob);
|
||||
|
||||
// now throw any assertion errors.
|
||||
if (fExceptions.size() > 0)
|
||||
throw (AssertionFailedError)fExceptions.get(0);
|
||||
|
||||
}
|
||||
|
||||
private static void sleep(int seconds, RequestMonitor rm, IProgressMonitor pm) {
|
||||
try {
|
||||
for (int i = 0; i < seconds; i++) {
|
||||
Thread.sleep(1000);
|
||||
|
||||
if (pm != null) {
|
||||
pm.worked(1);
|
||||
|
||||
if (pm.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for a job to finish without possible blocking of UI thread.
|
||||
//
|
||||
private static void waitForJob(Job job) {
|
||||
Display display = Display.getCurrent();
|
||||
while (true) {
|
||||
IStatus status = job.getResult();
|
||||
if (status != null)
|
||||
break;
|
||||
if (display != null) {
|
||||
while (display.readAndDispatch()) ;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
job.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -289,7 +289,7 @@ public class DsfSequenceTests {
|
|||
IProgressMonitor pm = new NullProgressMonitor();
|
||||
|
||||
// Create the seqeunce with our steps.
|
||||
Sequence sequence = new Sequence(fExecutor, pm, "", "", null) { //$NON-NLS-1$ //$NON-NLS-2$
|
||||
Sequence sequence = new Sequence(fExecutor, pm, "", "") { //$NON-NLS-1$ //$NON-NLS-2$
|
||||
@Override public Step[] getSteps() { return steps; }
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue