diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/DsfPlugin.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/DsfPlugin.java index abcae5d1d16..0e05acf461c 100644 --- a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/DsfPlugin.java +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/DsfPlugin.java @@ -76,6 +76,24 @@ public class DsfPlugin extends Plugin { System.out.println(message); } } + + public static String getDebugTime() { + StringBuilder traceBuilder = new StringBuilder(); + + // Record the time + long time = System.currentTimeMillis(); + long seconds = (time / 1000) % 1000; + if (seconds < 100) traceBuilder.append('0'); + if (seconds < 10) traceBuilder.append('0'); + traceBuilder.append(seconds); + traceBuilder.append(','); + long millis = time % 1000; + if (millis < 100) traceBuilder.append('0'); + if (millis < 10) traceBuilder.append('0'); + traceBuilder.append(millis); + return traceBuilder.toString(); + } + } diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/ConfinedToDsfExecutor.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/ConfinedToDsfExecutor.java index 3b880946af5..9feab36de06 100644 --- a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/ConfinedToDsfExecutor.java +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/ConfinedToDsfExecutor.java @@ -30,7 +30,7 @@ import java.lang.annotation.Target; * It should be null if it cannot be determined from the given object. */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR}) @Inherited @Documented public @interface ConfinedToDsfExecutor { diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DefaultDsfExecutor.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DefaultDsfExecutor.java index 1453b258d82..44e07542379 100644 --- a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DefaultDsfExecutor.java +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DefaultDsfExecutor.java @@ -91,7 +91,6 @@ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor } } } - // // Utilities used for tracing. @@ -132,7 +131,7 @@ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor int fSequenceNumber = -1; /** Trace of where the runnable/callable was submitted to the executor */ - StackTraceElement[] fSubmittedAt = null; + StackTraceWrapper fSubmittedAt = null; /** Reference to the runnable/callable that submitted this runnable/callable to the executor */ TracingWrapper fSubmittedBy = null; @@ -142,8 +141,8 @@ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor */ TracingWrapper(int offset) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - fSubmittedAt = new StackTraceElement[stackTrace.length - offset]; - System.arraycopy(stackTrace, offset - 1, fSubmittedAt, 0, fSubmittedAt.length); + fSubmittedAt = new StackTraceWrapper(new StackTraceElement[stackTrace.length - offset]); + System.arraycopy(stackTrace, offset - 1, fSubmittedAt.fStackTraceElements, 0, fSubmittedAt.fStackTraceElements.length); if (isInExecutorThread() && fCurrentlyExecuting != null) { fSubmittedBy = fCurrentlyExecuting; } @@ -158,16 +157,7 @@ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor StringBuilder traceBuilder = new StringBuilder(); // Record the time - long time = System.currentTimeMillis(); - long seconds = (time / 1000) % 1000; - if (seconds < 100) traceBuilder.append('0'); - if (seconds < 10) traceBuilder.append('0'); - traceBuilder.append(seconds); - traceBuilder.append(','); - long millis = time % 1000; - if (millis < 100) traceBuilder.append('0'); - if (millis < 10) traceBuilder.append('0'); - traceBuilder.append(millis); + traceBuilder.append(DsfPlugin.getDebugTime()); traceBuilder.append(' '); // Record the executor # @@ -187,7 +177,7 @@ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor } if (dsfExecutable.fCreatedAt != null) { traceBuilder.append(" at "); - traceBuilder.append(dsfExecutable.fCreatedAt[0].toString()); + traceBuilder.append(dsfExecutable.fCreatedAt.fStackTraceElements[0].toString()); } } } @@ -200,7 +190,7 @@ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor traceBuilder.append(fSubmittedBy.fSequenceNumber); } traceBuilder.append(" at "); - traceBuilder.append(fSubmittedAt[0].toString()); + traceBuilder.append(fSubmittedAt.fStackTraceElements[0].toString()); // Finally, the executable's toString(). traceBuilder.append("\n "); @@ -214,6 +204,7 @@ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor abstract protected Object getExecutable(); } + class TracingWrapperRunnable extends TracingWrapper implements Runnable { final Runnable fRunnable; public TracingWrapperRunnable(Runnable runnable, int offset) { @@ -225,6 +216,13 @@ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor public void run() { traceExecution(); + + // If debugging a DSF exeutable, mark that it was executed. + if (DEBUG_EXECUTOR && fRunnable instanceof DsfExecutable) { + ((DsfExecutable)fRunnable).setExecuted(); + } + + // Finally invoke the runnable code. fRunnable.run(); } } @@ -240,6 +238,13 @@ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor public T call() throws Exception { traceExecution(); + + // If debugging a DSF exeutable, mark that it was executed. + if (DEBUG_EXECUTOR && fCallable instanceof DsfExecutable) { + ((DsfExecutable)fCallable).setExecuted(); + } + + // Finally invoke the runnable code. return fCallable.call(); } } diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/Done.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/Done.java index 7136de89b8c..2f5f26ac0c0 100644 --- a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/Done.java +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/Done.java @@ -53,7 +53,7 @@ abstract public class Done extends DsfRunnable { * @return Returns true if there was an error that was propagated and * the caller can stop processing result. */ - protected boolean propagateErrorToClient(DsfExecutor executor, Done clientDone, String message) { + protected boolean propagateError(DsfExecutor executor, Done clientDone, String message) { if (clientDone.getStatus().getSeverity() == IStatus.CANCEL) { return true; } diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfExecutable.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfExecutable.java index 6ed2646a003..4b2f96e3ee8 100644 --- a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfExecutable.java +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfExecutable.java @@ -13,6 +13,9 @@ package org.eclipse.dd.dsf.concurrent; import java.util.HashSet; import java.util.Set; +import org.eclipse.core.runtime.Platform; +import org.eclipse.dd.dsf.DsfPlugin; + /** * Base class for DSF-instrumented alternative to the Runnable/Callable interfaces. *

@@ -23,15 +26,48 @@ import java.util.Set; */ @Immutable public class DsfExecutable { - final StackTraceElement[] fCreatedAt; + /** + * Flag indicating that tracing of the DSF executor is enabled. It enables + * storing of the "creator" information as well as tracing of disposed + * runnables that have not been submitted to the executor. + */ + static boolean DEBUG_EXECUTOR = false; + + /** + * Flag indicating that assertions are enabled. It enables storing of the + * "creator" executable for debugging purposes. + */ + static boolean ASSERTIONS_ENABLED = false; + + static { + assert ASSERTIONS_ENABLED = true; + DEBUG_EXECUTOR = DsfPlugin.DEBUG && "true".equals( //$NON-NLS-1$ + Platform.getDebugOption("org.eclipse.dd.dsf/debug/executor")); //$NON-NLS-1$ + assert ASSERTIONS_ENABLED = true; + } + + /** + * Field that holds the stack trace of where this executable was created. + * Used for tracing and debugging only. + */ + final StackTraceWrapper fCreatedAt; + + /** + * Field holding the reference of the executable that created this + * executable. Used for tracing only. + */ final DefaultDsfExecutor.TracingWrapper fCreatedBy; + /** + * Flag indicating whether this executable was ever executed by an + * executor. Used for tracing only. + */ + private boolean fExecuted = false; + @SuppressWarnings("unchecked") public DsfExecutable() { // Use assertion flag (-ea) to jre to avoid affecting performance when not debugging. - boolean assertsEnabled = false; - assert assertsEnabled = true; - if (assertsEnabled || DefaultDsfExecutor.DEBUG_EXECUTOR) { + if (ASSERTIONS_ENABLED || DEBUG_EXECUTOR) { // Find the runnable/callable that is currently running. DefaultDsfExecutor executor = DefaultDsfExecutor.fThreadToExecutorMap.get(Thread.currentThread()); if (executor != null) { @@ -53,11 +89,38 @@ public class DsfExecutable { for (i = 3; i < stackTrace.length; i++) { if ( !classNamesSet.contains(stackTrace[i].getClassName()) ) break; } - fCreatedAt = new StackTraceElement[stackTrace.length - i]; - System.arraycopy(stackTrace, i, fCreatedAt, 0, fCreatedAt.length); + fCreatedAt = new StackTraceWrapper(new StackTraceElement[stackTrace.length - i]); + System.arraycopy(stackTrace, i, fCreatedAt.fStackTraceElements, 0, fCreatedAt.fStackTraceElements.length); } else { fCreatedAt = null; fCreatedBy = null; } } + + /** + * Marks this executable to indicate that it has been executed by the + * executor. To be invoked only by DsfExecutor. + */ + void setExecuted() { + fExecuted = true; + } + + @Override + protected void finalize() { + if (DEBUG_EXECUTOR && !fExecuted) { + StringBuilder traceBuilder = new StringBuilder(); + + // Record the time + traceBuilder.append(DsfPlugin.getDebugTime()); + traceBuilder.append(' '); + + // Record the event + traceBuilder.append("DsfExecutable was never executed:\n "); + traceBuilder.append(this); + traceBuilder.append("\nCreated at:"); + traceBuilder.append(fCreatedAt); + + DsfPlugin.debug(traceBuilder.toString()); + } + } } diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfQuery.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfQuery.java index 351f84bd674..fce82bb2f65 100644 --- a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfQuery.java +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfQuery.java @@ -10,13 +10,13 @@ *******************************************************************************/ package org.eclipse.dd.dsf.concurrent; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.dd.dsf.DsfPlugin; -import org.eclipse.dd.dsf.service.IDsfService; /** * A convenience class that allows a client to retrieve data from services @@ -28,135 +28,124 @@ import org.eclipse.dd.dsf.service.IDsfService; * @see java.util.concurrent.Callable */ @ThreadSafe -abstract public class DsfQuery { - - private V fResult; - private boolean fValid; - private DsfExecutor fExecutor; - private Future fFuture; - private boolean fWaiting; - private IStatus fStatus = Status.OK_STATUS; - - public DsfQuery(DsfExecutor executor) { - fExecutor = executor; +abstract public class DsfQuery extends DsfRunnable + implements Future +{ + /** The synchronization object for this query */ + final Sync fSync = new Sync(); + + public V get() throws InterruptedException, ExecutionException { return fSync.doGet(); } + + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return fSync.doGet(); } - + /** - * Start data retrieval. - * Client must implement this method to do whatever is needed to retrieve data. - * Retrieval can be (but does not have to be) asynchronious - it meas this method can return - * before data is retrieved. When data is ready Proxy must be notified by calling done() method. - */ - protected abstract void execute(); - - /** - * Allows deriving classes to implement their own snipped additional - * cancellation code. + * Don't try to interrupt the DSF executor thread, just ignore the request + * if set. */ - protected void revokeChildren(V result) {}; - - /** - * Get data associated with this proxy. This method is thread safe and - * it will block until data is ready. Because it's a blocking call and it waits - * for commands to be processed on the dispatch thread, this methods itself - * CANNOT be called on the dispatch thread. - */ - public synchronized V get() { - assert !fExecutor.isInExecutorThread(); - if(!fValid) { - if (!fWaiting) { - fFuture = fExecutor.submit(new DsfRunnable() { - public void run() { - // Note: not sure if this try-catch is desirable. It might encourage - // implementors to not catch its own exceptions. If the query code takes - // more than one dispatch, then this code will not be helpful anyway. - try { - DsfQuery.this.execute(); - } catch (Throwable t) { - doneException(t); - } - } - }); - } - - fWaiting = true; - try { - while(fWaiting) { - wait(); - } - } catch (InterruptedException e) { - fStatus = new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfService.INTERNAL_ERROR, - "Interrupted exception while waiting for result.", e); - fValid = true; - } - assert fValid; - } - return fResult; + public boolean cancel(boolean mayInterruptIfRunning) { + return fSync.doCancel(); } - /** - * Same as get(), but with code to automatically re-threw the exception if one - * was reported by the run() method. - */ - public V getWithThrows() throws CoreException { - V retVal = get(); - if (!getStatus().isOK()) { - throw new CoreException(getStatus()); - } - return retVal; - } + public boolean isCancelled() { return fSync.doIsCancelled(); } - public IStatus getStatus() { return fStatus; } + public boolean isDone() { return fSync.doIsDone(); } - /** Abort current operation and keep old proxy data */ - public synchronized void cancel() { - assert fExecutor.isInExecutorThread(); - assert !fWaiting || !fValid; - if (fWaiting) { - fFuture.cancel(false); - fWaiting = false; - notifyAll(); - } else if (fValid) { - revokeChildren(fResult); - } - fValid = true; + + protected void done(V result) { + fSync.doSet(result); } - /** Abort current operation and set proxy data to 'result' */ - public synchronized void cancel(V newResult) { - fResult = newResult; - cancel(); - } - - public Object getCachedResult() { - return fResult; - } - - public boolean isValid() { return fValid; } - - public synchronized void done(V result) { - // Valid could be true if request was cancelled while data was - // being retrieved, and then done() was called. - if (fValid) return; - - fResult = result; - fValid = true; - if (fWaiting) { - fWaiting = false; - notifyAll(); - } - } - - public synchronized void doneError(IStatus errorStatus) { - if (fValid) return; - fStatus = errorStatus; - done(null); - } - - public synchronized void doneException(Throwable t) { - if (fValid) return; - doneError(new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfService.INTERNAL_ERROR, - "Exception while computing result.", t)); + protected void doneException(Throwable t) { + fSync.doSetException(t); } + + abstract protected void execute(); + + public void run() { + if (fSync.doRun()) { + execute(); + } + } + + @SuppressWarnings("serial") + final class Sync extends AbstractQueuedSynchronizer { + private static final int STATE_RUNNING = 1; + private static final int STATE_DONE = 2; + private static final int STATE_CANCELLED = 4; + + private V fResult; + private Throwable fException; + + private boolean ranOrCancelled(int state) { + return (state & (STATE_DONE | STATE_CANCELLED)) != 0; + } + + protected int tryAcquireShared(int ignore) { + return doIsDone()? 1 : -1; + } + + protected boolean tryReleaseShared(int ignore) { + return true; + } + + boolean doIsCancelled() { + return getState() == STATE_CANCELLED; + } + + boolean doIsDone() { + return ranOrCancelled(getState()); + } + + V doGet() throws InterruptedException, ExecutionException { + acquireSharedInterruptibly(0); + if (getState() == STATE_CANCELLED) throw new CancellationException(); + if (fException != null) throw new ExecutionException(fException); + return fResult; + } + + V doGet(long nanosTimeout) throws InterruptedException, ExecutionException, TimeoutException { + if (!tryAcquireSharedNanos(0, nanosTimeout)) throw new TimeoutException(); + if (getState() == STATE_CANCELLED) throw new CancellationException(); + if (fException != null) throw new ExecutionException(fException); + return fResult; + } + + void doSet(V v) { + while(true) { + int s = getState(); + if (ranOrCancelled(s)) return; + if (compareAndSetState(s, STATE_DONE)) break; + } + fResult = v; + releaseShared(0); + } + + void doSetException(Throwable t) { + while(true) { + int s = getState(); + if (ranOrCancelled(s)) return; + if (compareAndSetState(s, STATE_DONE)) break; + } + fException = t; + fResult = null; + releaseShared(0); + } + + boolean doCancel() { + while(true) { + int s = getState(); + if (ranOrCancelled(s)) return false; + if (compareAndSetState(s, STATE_CANCELLED)) break; + } + releaseShared(0); + return true; + } + + boolean doRun() { + return compareAndSetState(0, STATE_RUNNING); + } + } } diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfSequence.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfSequence.java index 3de19cd88ae..6fe0903cd82 100644 --- a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfSequence.java +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/DsfSequence.java @@ -10,8 +10,17 @@ *******************************************************************************/ package org.eclipse.dd.dsf.concurrent; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; 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.dd.dsf.DsfPlugin; @@ -39,202 +48,406 @@ import org.eclipse.dd.dsf.DsfPlugin; * has to be re-implemented every time. The Sequence class tries to address * this problem by containing this pattern in a single class. */ -abstract public class DsfSequence { +@ThreadSafe +abstract public class DsfSequence extends DsfRunnable implements Future { /** - * The abstract class that each step has to implement + * The abstract class that each step has to implement. */ - abstract public class Step { - public void execute() { stepFinished(); } - public void rollBack() { stepRolledBack(); } + abstract public static class Step { + private DsfSequence fSequence; + + /** + * Sets the sequence that this step belongs to. It is only accessible + * by the sequence itself, and is not meant to be called by sequence + * sub-classes. + */ + void setSequence(DsfSequence sequence) { fSequence = sequence; } + + /** Returns the sequence that this step is running in. */ + public DsfSequence getSequence() { return fSequence; } + + /** + * Executes the next step. Overriding classes should perform the + * work in this method. + * @param done Result token to submit to executor when step is finished. + */ + public void execute(Done done) { + getSequence().getExecutor().execute(done); + } + + /** + * Roll back gives the step implementation a chance to undo the + * operation that was performed by execute(). + * @param done Result token to submit to executor when rolling back the step is finished. + */ + public void rollBack(Done done) { + getSequence().getExecutor().execute(done); + } + + /** + * Returns the number of progress monitor ticks corresponding to this + * step. + */ public int getTicks() { return 1; } } - private DsfExecutor fExecutor; - private Step[] fSteps; - private Done fDoneQC; - private String fTaskName; - private String fRollbackTaskName; - private IProgressMonitor fProgressMonitor = new NullProgressMonitor(); + /** The synchronization object for this future */ + final Sync fSync = new Sync(); + + /** + * Executor that this sequence is running in. It is used by the sequence + * to submit the runnables for steps, and for submitting the result. + */ + final private DsfExecutor fExecutor; + + /** + * Result callback to invoke when the sequence is finished. Intended to + * be used when the sequence is created and invoked from the executor + * thread. Otherwise, the {@link Future#get()} method is the appropriate + * method of retrieving the result. + */ + final private Done fDone; + + /** Status indicating the success/failure of the test. Used internally only. */ + @ConfinedToDsfExecutor("getExecutor") + private IStatus fStatus = Status.OK_STATUS; + + @ConfinedToDsfExecutor("getExecutor") private int fCurrentStepIdx = 0; - boolean fCancelled = false; - /** - * Default constructor. If this constructor is used, the steps need to be initialized - * before the sequence can be invoked. - * @param executor the DSF executor which will be used to invoke all steps - */ - public DsfSequence(DsfExecutor executor) { - this(executor, null); + /** Task name for this sequence used with the progress monitor */ + final private String fTaskName; + + /** Task name used when the sequence is being rolled back. */ + final private String fRollbackTaskName; + + final private IProgressMonitor fProgressMonitor; + + + + /** Convenience constructor with limited arguments. */ + public DsfSequence(DsfExecutor executor) { + this(executor, new NullProgressMonitor(), "", "", null); } - + + /** Convenience constructor with limited arguments. */ + public DsfSequence(DsfExecutor executor, Done done) { + this(executor, new NullProgressMonitor(), "", "", done); + } + /** - * Constructor that initialized the steps. - * @param executor the DSF executor which will be used to invoke all steps - * @param steps sequence steps + * 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. */ - public DsfSequence(DsfExecutor executor, Step[] steps) { + + public DsfSequence(DsfExecutor executor, IProgressMonitor pm, String taskName, String rollbackTaskName, Done done) { fExecutor = executor; - fSteps = steps; + fProgressMonitor = pm; + fTaskName = taskName; + fRollbackTaskName = rollbackTaskName; + fDone = done; } + + /** + * Returns the steps to be executed. It is up to the deriving class to + * supply the steps and to ensure that the list of steps will not be + * modified after the sequence is constructed. + */ + abstract public Step[] getSteps(); + /** Returns the DSF executor for this sequence */ public DsfExecutor getExecutor() { return fExecutor; } - /** - * Sets the done callback to be submitted when the sequence is finished. - * If the sequence is submitted by a caller in the dispatch thread, this is - * the way that the original caller can be notified of the sequence - * completion. If the caller blocks and waits for the sequence - * completion, the Done callback is not necessary. - * @param doneQC callback to submit when sequence completes, can be null - */ - public void setDone(Done doneQC) { - fDoneQC = doneQC; - } - /** * Returns the Done callback that is registered with the Sequence * @param doneQC callback that will be submitted when sequence completes, * null if there is no callback configured */ - public Done getDone() { return fDoneQC; } - - /** Sets the steps to be executed. */ - public void setSteps(Step[] steps) { - assert fCurrentStepIdx == 0; - fSteps = steps; - } - - /** Returns the steps to be executed. */ - public Step[] getSteps() { return fSteps; } + public Done getDone() { return fDone; } /** - * Returns index of the step that is currently being executed. - *
NOTE: After sequence is invoked, this method should be called - * only in the DSF executor thread. - * @return + * The get method blocks until sequence is complete, but always returns null. + * @see java.concurrent.Future#get */ - public int getCurrentIdx() { return fCurrentStepIdx; } - - /** - * Sets the progress monitor that will be called by the sequence with udpates. - * @param pm - */ - public void setProgressMonitor(IProgressMonitor pm) { fProgressMonitor = pm; } - - /** - * Sets the task name for this sequence. To be used with progress monitor; - * @param taskName - */ - public void setTaskName(String taskName) { fTaskName = taskName; } - - /** - * Sets the task name to be used with progress monitor, if this sequence needs - * to be rolled back as result of cancellation or error. - * @param taskName - */ - public void setRollBackTaskName(String n) { fRollbackTaskName = n; } - - - /** Submits this sequence to the executor. */ - public void invokeLater() { - getExecutor().submit( new DsfRunnable() { public void run() { doInvoke(); } }); - } - - /** - * Submits this sequence to the DSF executor, and blocks waiting for the - * sequence to complete. - *
NOTE: This method is NOT to be called on the DSF executor thread. - */ - public synchronized void invoke() { - assert !fExecutor.isInExecutorThread() : - "Cannot be called on dispatch thread: " + this; - setDone(new Done() { - public void run() { - synchronized(DsfSequence.this) { DsfSequence.this.notifyAll(); } - } - }); - invokeLater(); - try { - wait(); - } catch (InterruptedException e) {} + public Object get() throws InterruptedException, ExecutionException { + fSync.doGet(); + return null; } - private void doInvoke() { - assert fCurrentStepIdx == 0; - if (fTaskName != null) { - fProgressMonitor.subTask(fTaskName); + /** + * The get method blocks until sequence is complete or until timeout is + * reached, but always returns null. + * @see java.concurrent.Future#get + */ + public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + fSync.doGet(); + return null; + } + + /** + * Don't try to interrupt the DSF executor thread, just ignore the request + * if set. + */ + public boolean cancel(boolean mayInterruptIfRunning) { + return fSync.doCancel(); + } + + public boolean isCancelled() { return fSync.doIsCancelled(); } + + public boolean isDone() { return fSync.doIsDone(); } + + + public void run() { + // Change the state to running. + if (fSync.doRun()) { + // Set the reference to this sequence in each step. + int totalTicks = 0; + for (Step step : getSteps()) { + step.setSequence(this); + totalTicks += step.getTicks(); + } + + // Set the task name + if (fTaskName != null) { + fProgressMonitor.beginTask(fTaskName, totalTicks); + } + + // Call the first step + executeStep(0); + } else { + fSync.doFinish(); } - fSteps[fCurrentStepIdx].execute(); - } - - /** - * Cancells the execution of this sequence. The roll-back will start when - * the current step completes. - * - */ - public void cancel() { - fCancelled = true; } /** * To be called only by the step implementation, Tells the sequence to * submit the next step. */ - public void stepFinished() { - getExecutor().submit(new DsfRunnable() { public void run() { - fProgressMonitor.worked(getSteps()[fCurrentStepIdx].getTicks()); - fCurrentStepIdx++; - if (fCurrentStepIdx < fSteps.length) { - if (fCancelled) { - abort(new Status( - IStatus.CANCEL, DsfPlugin.PLUGIN_ID, -1, - "Cancelled" + fTaskName != null ? ": " + fTaskName : "", - null)); - } - fSteps[fCurrentStepIdx].execute(); + private void executeStep(int nextStepIndex) { + if (isCancelled()) { + cancelExecution(); + } else { + if (nextStepIndex < getSteps().length) { + fCurrentStepIdx = nextStepIndex; + getSteps()[fCurrentStepIdx].execute(new Done() { + final private int fStepIdx = fCurrentStepIdx; + public void run() { + // Check if we're still the correct step. + assert fStepIdx == fCurrentStepIdx; + + // Proceed to the next step. + if (getStatus().isOK()) { + fProgressMonitor.worked(getSteps()[fStepIdx].getTicks()); + executeStep(fStepIdx + 1); + } else { + abortExecution(getStatus()); + } + } + public String toString() { + return "DsfSequence \"" + fTaskName + "\", result for executing step #" + fStepIdx + " = " + getStatus(); + } + }); } else { - if (fDoneQC != null) getExecutor().submit(fDoneQC); + finish(); } - }}); + } } /** * To be called only by the step implementation. Tells the sequence to * roll back next step. */ - public void stepRolledBack() { - getExecutor().submit(new DsfRunnable() { public void run() { - fProgressMonitor.worked(getSteps()[fCurrentStepIdx].getTicks()); - fCurrentStepIdx--; - if (fCurrentStepIdx >= 0) { - fSteps[fCurrentStepIdx].rollBack(); - } else { - if (fDoneQC != null) getExecutor().submit(fDoneQC); - } - }}); + private void rollBackStep(int stepIdx) { + if (stepIdx >= 0) { + fCurrentStepIdx = stepIdx; + getSteps()[fCurrentStepIdx].rollBack(new Done() { + final private int fStepIdx = fCurrentStepIdx; + public void run() { + // Check if we're still the correct step. + assert fStepIdx == fCurrentStepIdx; + + // Proceed to the next step. + if (getStatus().isOK()) { + fProgressMonitor.worked(getSteps()[fStepIdx].getTicks()); + rollBackStep(fStepIdx - 1); + } else { + abortRollBack(getStatus()); + } + }; + @Override + public String toString() { + return "DsfSequence \"" + fTaskName + "\", result for rolling back step #" + fStepIdx + " = " + getStatus(); + } + }); + } else { + finish(); + } } - + /** - * To be called only by step implementation. Tells the sequence - * that its execution is to be aborted and it should start rolling back - * the sequence as if it was cancelled by user. - * @param error + * Tells the sequence that its execution is to be aborted and it + * should start rolling back the sequence as if it was cancelled by user. */ - public void abort(final IStatus error) { - getExecutor().submit(new DsfRunnable() { public void run() { - if (fRollbackTaskName != null) { - fProgressMonitor.subTask(fRollbackTaskName); - } - fDoneQC.setStatus(error); - fCurrentStepIdx--; - if (fCurrentStepIdx >= 0) { - fSteps[fCurrentStepIdx].rollBack(); - } else { - if (fDoneQC != null) getExecutor().submit(fDoneQC); - } - }}); + private void cancelExecution() { + if (fRollbackTaskName != null) { + fProgressMonitor.subTask(fRollbackTaskName); + } + fStatus = new Status(IStatus.CANCEL, DsfPlugin.PLUGIN_ID, "Sequence \"" + fTaskName + "\" cancelled."); + if (fDone != null) { + fDone.setStatus(fStatus); + } + + /* + * No need to call fSync, it should have been taken care of by + * Future#cancel method. + * + * Note that we're rolling back starting with the current step, + * because the current step was fully executed. This is unlike + * abortExecution() where the current step caused the roll-back. + */ + rollBackStep(fCurrentStepIdx); + } + + /** + * Tells the sequence that its execution is to be aborted and it + * should start rolling back the sequence as if it was cancelled by user. + */ + private void abortExecution(final IStatus error) { + if (fRollbackTaskName != null) { + fProgressMonitor.subTask(fRollbackTaskName); + } + fStatus = error; + if (fDone != null) { + fDone.setStatus(error); + } + fSync.doAbort(new CoreException(error)); + + // Roll back starting with previous step, since current step failed. + rollBackStep(fCurrentStepIdx - 1); + } + + /** + * Tells the sequence that that is rolling back, to abort roll back, and + * notify the clients. + */ + private void abortRollBack(final IStatus error) { + if (fRollbackTaskName != null) { + fProgressMonitor.subTask(fRollbackTaskName); + } + + /* + * Compose new status based on previous status information and new + * error information. + */ + MultiStatus newStatus = + new MultiStatus(DsfPlugin.PLUGIN_ID, error.getCode(), + "Sequence \"" + fTaskName + "\" failed while rolling back.", null); + newStatus.merge(error); + newStatus.merge(fStatus); + fStatus = newStatus; + + if (fDone != null) { + fDone.setStatus(newStatus); + } + + finish(); } + private void finish() { + if (fDone != null) getExecutor().submit(fDone); + fSync.doFinish(); + } + + @SuppressWarnings("serial") + final class Sync extends AbstractQueuedSynchronizer { + private static final int STATE_RUNNING = 1; + private static final int STATE_FINISHED = 2; + private static final int STATE_ABORTING = 4; + private static final int STATE_ABORTED = 8; + private static final int STATE_CANCELLING = 16; + private static final int STATE_CANCELLED = 32; + + private Throwable fException; + + private boolean isFinished(int state) { + return (state & (STATE_FINISHED | STATE_CANCELLED | STATE_ABORTED)) != 0; + } + + protected int tryAcquireShared(int ignore) { + return doIsDone()? 1 : -1; + } + + protected boolean tryReleaseShared(int ignore) { + return true; + } + + boolean doIsCancelled() { + int state = getState(); + return (state & (STATE_CANCELLING | STATE_CANCELLED)) != 0; + } + + boolean doIsDone() { + return isFinished(getState()); + } + + void doGet() throws InterruptedException, ExecutionException { + acquireSharedInterruptibly(0); + if (getState() == STATE_CANCELLED) throw new CancellationException(); + if (fException != null) throw new ExecutionException(fException); + } + + void doGet(long nanosTimeout) throws InterruptedException, ExecutionException, TimeoutException { + if (!tryAcquireSharedNanos(0, nanosTimeout)) throw new TimeoutException(); + if (getState() == STATE_CANCELLED) throw new CancellationException(); + if (fException != null) throw new ExecutionException(fException); + } + + void doAbort(Throwable t) { + while(true) { + int s = getState(); + if (isFinished(s)) return; + if (compareAndSetState(s, STATE_ABORTING)) break; + } + fException = t; + } + + boolean doCancel() { + while(true) { + int s = getState(); + if (isFinished(s)) return false; + if (s == STATE_ABORTING) return false; + if (compareAndSetState(s, STATE_CANCELLING)) break; + } + return true; + } + + void doFinish() { + while(true) { + int s = getState(); + if (isFinished(s)) return; + if (s == STATE_ABORTING) { + if (compareAndSetState(s, STATE_ABORTED)) break; + } else if (s == STATE_CANCELLING) { + if (compareAndSetState(s, STATE_CANCELLED)) break; + } else { + if (compareAndSetState(s, STATE_FINISHED)) break; + } + } + releaseShared(0); + } + + boolean doRun() { + return compareAndSetState(0, STATE_RUNNING); + } + } + } diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/StackTraceWrapper.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/StackTraceWrapper.java new file mode 100644 index 00000000000..26352039d28 --- /dev/null +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/StackTraceWrapper.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2006 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; + +/** + * Untility class for easy pretty-printing stack traces. Local to the + * concurrent package. + */ +@Immutable +class StackTraceWrapper { + final StackTraceElement[] fStackTraceElements; + + StackTraceWrapper(StackTraceElement[] elements) { fStackTraceElements = elements; } + + public String toString() { + StringBuilder builder = new StringBuilder(fStackTraceElements.length * 30); + for (int i = 0; i < fStackTraceElements.length && i < 10; i++) { + builder.append(fStackTraceElements[i]); + if (i < fStackTraceElements.length && i < 10) builder.append("\n at "); + } + return builder.toString(); + } +}