diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/RequestMonitorWithProgress.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/RequestMonitorWithProgress.java new file mode 100644 index 00000000000..579fa512c12 --- /dev/null +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/RequestMonitorWithProgress.java @@ -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(); + } +} diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/Sequence.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/Sequence.java index b70ed8c9d4f..ffde09d60e5 100644 --- a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/Sequence.java +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/Sequence.java @@ -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 { * 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 + * null, 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.
+ *
+ * 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 { 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 { 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 { // 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()); diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/provisional/launching/FinalLaunchSequence.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/provisional/launching/FinalLaunchSequence.java index 592d2772cd4..e71395e30ae 100644 --- a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/provisional/launching/FinalLaunchSequence.java +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/provisional/launching/FinalLaunchSequence.java @@ -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; diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/provisional/launching/ServicesLaunchSequence.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/provisional/launching/ServicesLaunchSequence.java index 7e576e292c4..f3574df450b 100644 --- a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/provisional/launching/ServicesLaunchSequence.java +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/provisional/launching/ServicesLaunchSequence.java @@ -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; } diff --git a/plugins/org.eclipse.dd.tests.dsf/src/org/eclipse/dd/tests/dsf/concurrent/DsfSequenceProgressTests.java b/plugins/org.eclipse.dd.tests.dsf/src/org/eclipse/dd/tests/dsf/concurrent/DsfSequenceProgressTests.java new file mode 100644 index 00000000000..c0cdcb4d309 --- /dev/null +++ b/plugins/org.eclipse.dd.tests.dsf/src/org/eclipse/dd/tests/dsf/concurrent/DsfSequenceProgressTests.java @@ -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 fExceptions = Collections.synchronizedList(new ArrayList()); + 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.
+ *
+ * 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; + } + } + } +} diff --git a/plugins/org.eclipse.dd.tests.dsf/src/org/eclipse/dd/tests/dsf/concurrent/DsfSequenceTests.java b/plugins/org.eclipse.dd.tests.dsf/src/org/eclipse/dd/tests/dsf/concurrent/DsfSequenceTests.java index b84c2e57367..3714d18ba99 100644 --- a/plugins/org.eclipse.dd.tests.dsf/src/org/eclipse/dd/tests/dsf/concurrent/DsfSequenceTests.java +++ b/plugins/org.eclipse.dd.tests.dsf/src/org/eclipse/dd/tests/dsf/concurrent/DsfSequenceTests.java @@ -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; } };