1
0
Fork 0
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:
Pawel Piech 2008-11-05 22:35:49 +00:00
parent b27e322c49
commit e2c7961a23
6 changed files with 459 additions and 26 deletions

View file

@ -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();
}
}

View file

@ -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());

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}
}
}
}

View file

@ -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; }
};