From 4edc3e01e081fcfa7636cf8c24a4ac1d4d2ef98e Mon Sep 17 00:00:00 2001 From: Pawel Piech Date: Mon, 11 Aug 2008 17:53:08 +0000 Subject: [PATCH] [243794] - [update policy] VM Cache can save updates after they were canceled. --- plugins/org.eclipse.dd.dsf.ui/.options | 5 + .../dd/dsf/internal/ui/DsfUIPlugin.java | 15 ++ .../dsf/ui/viewmodel/AbstractVMProvider.java | 144 +++++++++--- .../DefaultVMModelProxyStrategy.java | 29 +-- .../ui/viewmodel/VMChildrenCountUpdate.java | 5 + .../dd/dsf/ui/viewmodel/VMChildrenUpdate.java | 33 +-- .../dsf/ui/viewmodel/VMHasChildrenUpdate.java | 5 + .../update/AbstractCachingVMProvider.java | 220 ++++++++++++++---- .../dd/dsf/concurrent/RequestMonitor.java | 5 +- 9 files changed, 329 insertions(+), 132 deletions(-) create mode 100644 plugins/org.eclipse.dd.dsf.ui/.options diff --git a/plugins/org.eclipse.dd.dsf.ui/.options b/plugins/org.eclipse.dd.dsf.ui/.options new file mode 100644 index 00000000000..1a35ae98fbc --- /dev/null +++ b/plugins/org.eclipse.dd.dsf.ui/.options @@ -0,0 +1,5 @@ +org.eclipse.dd.dsf.ui/debug = false +org.eclipse.dd.dsf.ui/debug/vm/contentProvider = false +org.eclipse.dd.dsf.ui/debug/vm/delta = false +org.eclipse.dd.dsf.ui/debug/vm/cache = false +org.eclipse.dd.dsf.ui/debug/vm/presentationId = diff --git a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/internal/ui/DsfUIPlugin.java b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/internal/ui/DsfUIPlugin.java index 833a3c3d614..878a2b2c394 100644 --- a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/internal/ui/DsfUIPlugin.java +++ b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/internal/ui/DsfUIPlugin.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.dd.dsf.internal.ui; +import org.eclipse.core.runtime.Platform; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext; @@ -26,6 +27,8 @@ public class DsfUIPlugin extends AbstractUIPlugin { private static BundleContext fgBundleContext; + public static boolean DEBUG = false; + /** * The constructor */ @@ -41,6 +44,7 @@ public class DsfUIPlugin extends AbstractUIPlugin { public void start(BundleContext context) throws Exception { fgBundleContext = context; super.start(context); + DEBUG = "true".equals(Platform.getDebugOption("org.eclipse.dd.dsf.ui/debug")); //$NON-NLS-1$//$NON-NLS-2$ } /* @@ -66,5 +70,16 @@ public class DsfUIPlugin extends AbstractUIPlugin { public static BundleContext getBundleContext() { return fgBundleContext; } + + /** + * If the debug flag is set the specified message is printed to the console + * @param message + */ + public static void debug(String message) { + if (DEBUG) { + System.out.println(message); + } + } + } diff --git a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/AbstractVMProvider.java b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/AbstractVMProvider.java index 7f9be5304ff..a51c081379b 100644 --- a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/AbstractVMProvider.java +++ b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/AbstractVMProvider.java @@ -15,15 +15,16 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; +import org.eclipse.core.runtime.Platform; import org.eclipse.dd.dsf.concurrent.CountingRequestMonitor; import org.eclipse.dd.dsf.concurrent.DataRequestMonitor; import org.eclipse.dd.dsf.concurrent.IDsfStatusConstants; import org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.internal.ui.DsfUIPlugin; import org.eclipse.dd.dsf.ui.concurrent.SimpleDisplayExecutor; import org.eclipse.dd.dsf.ui.concurrent.ViewerDataRequestMonitor; import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate; @@ -63,7 +64,23 @@ import org.eclipse.swt.widgets.Display; @SuppressWarnings("restriction") abstract public class AbstractVMProvider implements IVMProvider, IVMEventListener { - + // debug flags + public static String DEBUG_PRESENTATION_ID = null; + public static boolean DEBUG_CONTENT_PROVIDER = false; + public static boolean DEBUG_DELTA = false; + + static { + DEBUG_PRESENTATION_ID = Platform.getDebugOption("org.eclipse.dd.dsf.ui/debug/vm/presentationId"); //$NON-NLS-1$ + if (!DsfUIPlugin.DEBUG || "".equals(DEBUG_PRESENTATION_ID)) { //$NON-NLS-1$ + DEBUG_PRESENTATION_ID = null; + } + DEBUG_CONTENT_PROVIDER = DsfUIPlugin.DEBUG && "true".equals( //$NON-NLS-1$ + Platform.getDebugOption("org.eclipse.dd.dsf.ui/debug/vm/contentProvider")); //$NON-NLS-1$ + + DEBUG_DELTA = DsfUIPlugin.DEBUG && "true".equals( //$NON-NLS-1$ + Platform.getDebugOption("org.eclipse.dd.dsf.ui/debug/vm/delta")); //$NON-NLS-1$ + } + /** Reference to the VM adapter that owns this provider */ private final AbstractVMAdapter fVMAdapter; @@ -126,12 +143,22 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene */ private IRootVMNode fRootNode; - private class ModelProxyEventQueue { - private boolean fProcessingEvent = false; - private List fEventQueue = new LinkedList(); + private class EventInfo { + EventInfo(Object event, RequestMonitor rm) { + fEvent = event; + fClientRm = rm; + } + Object fEvent; + RequestMonitor fClientRm; } - private Map fEventQueues = new HashMap(); + private class ModelProxyEventQueue { + EventInfo fCurrentEvent = null; + RequestMonitor fCurrentRm = null; + List fEventQueue = new LinkedList(); + } + + private Map fProxyEventQueues = new HashMap(); /** * Constructs the view model provider for given DSF session. The @@ -220,25 +247,47 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene for (final IVMModelProxy proxyStrategy : activeModelProxies) { if (proxyStrategy.isDeltaEvent(event)) { - if (!fEventQueues.containsKey(proxyStrategy)) { - fEventQueues.put(proxyStrategy, new ModelProxyEventQueue()); + if (DEBUG_DELTA && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("eventReceived(proxyRoot = " + proxyStrategy .getRootElement() + ", event = " + event + ")" ); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ } - final ModelProxyEventQueue queue = fEventQueues.get(proxyStrategy); - if (queue.fProcessingEvent) { - if (!queue.fEventQueue.isEmpty()) { - for (ListIterator itr = queue.fEventQueue.listIterator(queue.fEventQueue.size() - 1); itr.hasPrevious();) { - Object eventToSkip = itr.previous(); - if (canSkipHandlingEvent(event, eventToSkip)) { - itr.remove(); - } else { - break; + if (!fProxyEventQueues.containsKey(proxyStrategy)) { + fProxyEventQueues.put(proxyStrategy, new ModelProxyEventQueue()); + if (DEBUG_DELTA && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("eventQueued(proxyRoot = " + proxyStrategy.getRootElement() + ", event = " + event + ")" ); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + } + } + final ModelProxyEventQueue queue = fProxyEventQueues.get(proxyStrategy); + if (queue.fCurrentEvent != null) { + assert queue.fCurrentRm != null; + // Iterate through the events in the queue and check if + // they can be skipped. If they can be skipped, then just + // mark their RM as done. Stop iterating through the queue + // if an event that cannot be skipped is encountered. + while (!queue.fEventQueue.isEmpty()) { + EventInfo eventToSkipInfo = queue.fEventQueue.get(queue.fEventQueue.size() - 1); + + if (canSkipHandlingEvent(event, eventToSkipInfo.fEvent)) { + if (DEBUG_DELTA && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("eventSkipped(proxyRoot = " + proxyStrategy.getRootElement() + ", event = " + eventToSkipInfo + ")" ); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ } + queue.fEventQueue.remove(queue.fEventQueue.size() - 1); + eventToSkipInfo.fClientRm.done(); + } else { + break; } } - crm.done(); - queue.fEventQueue.add(event); + // If the queue is empty check if the current event + // being processed can be skipped. If so, cancel its + // processing + if (queue.fEventQueue.isEmpty() && canSkipHandlingEvent(event, queue.fCurrentEvent.fEvent)) { + if (DEBUG_DELTA && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("eventCancelled(proxyRoot = " + proxyStrategy.getRootElement() + ", event = " + queue.fCurrentEvent + ")" ); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + } + queue.fCurrentRm.cancel(); + } + queue.fEventQueue.add(new EventInfo(event, crm)); } else { - doHandleEvent(queue, proxyStrategy, event, crm); + doHandleEvent(queue, proxyStrategy, new EventInfo(event, crm)); } } else { crm.done(); @@ -247,28 +296,30 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene // Clean up model proxies that were removed. List activeProxies = getActiveModelProxies(); - for (Iterator itr = fEventQueues.keySet().iterator(); itr.hasNext();) { + for (Iterator itr = fProxyEventQueues.keySet().iterator(); itr.hasNext();) { if (!activeProxies.contains(itr.next())) { itr.remove(); } } } - private void doHandleEvent(final ModelProxyEventQueue queue, final IVMModelProxy proxyStrategy, final Object event, final RequestMonitor rm) { - queue.fProcessingEvent = true; - handleEvent( - proxyStrategy, event, - new RequestMonitor(getExecutor(), null) { - @Override - protected void handleCompleted() { - queue.fProcessingEvent = false; - if (!queue.fEventQueue.isEmpty()) { - doHandleEvent(queue, proxyStrategy, queue.fEventQueue.remove(0), rm); - } else { - rm.done(); - } - } - }); + private void doHandleEvent(final ModelProxyEventQueue queue, final IVMModelProxy proxyStrategy, final EventInfo eventInfo) { + assert queue.fCurrentEvent == null && queue.fCurrentRm == null; + + queue.fCurrentEvent = eventInfo; + queue.fCurrentRm = new RequestMonitor(getExecutor(), null) { + @Override + protected void handleCompleted() { + queue.fCurrentEvent = null; + queue.fCurrentRm = null; + if (!queue.fEventQueue.isEmpty()) { + EventInfo eventInfo = queue.fEventQueue.remove(0); + doHandleEvent(queue, proxyStrategy, eventInfo); + } + eventInfo.fClientRm.done(); + } + }; + handleEvent(proxyStrategy, eventInfo.fEvent, queue.fCurrentRm); } /** @@ -287,6 +338,9 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene */ protected void handleEvent(final IVMModelProxy proxyStrategy, final Object event, RequestMonitor rm) { if (!proxyStrategy.isDisposed()) { + if (DEBUG_DELTA && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("eventProcessing(proxyRoot = " + proxyStrategy.getRootElement() + ", event = " + event + ")" ); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + } proxyStrategy.createDelta( event, new DataRequestMonitor(getExecutor(), rm) { @@ -294,6 +348,9 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene public void handleCompleted() { if (isSuccess()) { proxyStrategy.fireModelChanged(getData()); + if (DEBUG_DELTA && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("eventDeltaFired(proxyRoot = " + proxyStrategy.getRootElement() + ", event = " + event + ")" ); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + } } super.handleCompleted(); } @@ -439,6 +496,9 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene IHasChildrenUpdate[] updateProxies = new IHasChildrenUpdate[updates.length]; for (int i = 0; i < updates.length; i++) { final IHasChildrenUpdate update = updates[i]; + if (DEBUG_CONTENT_PROVIDER && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("updateNodeHasChildren(node = " + node + ", update = " + update + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } updateProxies[i] = new VMHasChildrenUpdate( update, new ViewerDataRequestMonitor(getExecutor(), updates[i]) { @@ -451,6 +511,9 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene @Override protected void handleErrorOrWarning() { if (getStatus().getCode() == IDsfStatusConstants.NOT_SUPPORTED) { + if (DEBUG_CONTENT_PROVIDER && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("not-supported:updateNodeHasChildren(node = " + node + ", update = " + update + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } updateNode( node, new VMChildrenUpdate( @@ -484,6 +547,9 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene * a cache. */ public void updateNode(final IVMNode node, final IChildrenCountUpdate update) { + if (DEBUG_CONTENT_PROVIDER && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("updateNodeChildCount(node = " + node + ", update = " + update + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } node.update(new IChildrenCountUpdate[] { new VMChildrenCountUpdate( update, @@ -497,6 +563,9 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene @Override protected void handleErrorOrWarning() { if (getStatus().getCode() == IDsfStatusConstants.NOT_SUPPORTED) { + if (DEBUG_CONTENT_PROVIDER && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("not-supported:updateNodeChildCount(node = " + node + ", update = " + update + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } updateNode( node, new VMChildrenUpdate( @@ -527,6 +596,9 @@ abstract public class AbstractVMProvider implements IVMProvider, IVMEventListene * a cache. */ public void updateNode(IVMNode node, IChildrenUpdate update) { + if (DEBUG_CONTENT_PROVIDER && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("updateNodeChildren(node = " + node + ", update = " + update + ")"); + } node.update(new IChildrenUpdate[] { update }); } diff --git a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/DefaultVMModelProxyStrategy.java b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/DefaultVMModelProxyStrategy.java index 3a6cd4db2a7..47acd1beb6d 100644 --- a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/DefaultVMModelProxyStrategy.java +++ b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/DefaultVMModelProxyStrategy.java @@ -17,7 +17,6 @@ import java.util.Map; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.ListenerList; -import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.dd.dsf.concurrent.ConfinedToDsfExecutor; import org.eclipse.dd.dsf.concurrent.CountingRequestMonitor; @@ -63,16 +62,6 @@ public class DefaultVMModelProxyStrategy implements IVMModelProxy { private ListenerList fListeners = new ListenerList(); private IDoubleClickListener fDoubleClickListener; - /** - * Debug flag indicating whether the deltas should be traced in stdout. - */ - private static boolean DEBUG_DELTAS = false; - - static { - DEBUG_DELTAS = DebugUIPlugin.DEBUG && "true".equals( //$NON-NLS-1$ - Platform.getDebugOption("org.eclipse.debug.ui/debug/viewers/deltas")); //$NON-NLS-1$ - } - /** * Creates this model proxy strategy for the given provider. */ @@ -122,9 +111,6 @@ public class DefaultVMModelProxyStrategy implements IVMModelProxy { public void fireModelChanged(IModelDelta delta) { final IModelDelta root = getRootDelta(delta); Object[] listeners = getListeners(); - if (DEBUG_DELTAS) { - DebugUIPlugin.debug("FIRE DELTA: " + delta.toString()); //$NON-NLS-1$ - } for (int i = 0; i < listeners.length; i++) { final IModelChangedListener listener = (IModelChangedListener) listeners[i]; ISafeRunnable safeRunnable = new ISafeRunnable() { @@ -353,6 +339,8 @@ public class DefaultVMModelProxyStrategy implements IVMModelProxy { // super-class to resort to the default behavior which may add a // delta for every element in this node. buildChildDeltasForAllContexts(node, event, parentDelta, nodeOffset, rm); + } else { + super.handleCompleted(); } } }); @@ -388,13 +376,13 @@ public class DefaultVMModelProxyStrategy implements IVMModelProxy { node, new VMChildrenUpdate( parentDelta, getVMProvider().getPresentationContext(), -1, -1, - new DataRequestMonitor>(getVMProvider().getExecutor(), null) { + new DataRequestMonitor>(getVMProvider().getExecutor(), requestMonitor) { @Override - protected void handleCompleted() { + protected void handleSuccess() { // Check for an empty list of elements. If it's empty then we // don't have to call the children nodes, so return here. // No need to propagate error, there's no means or need to display it. - if (!isSuccess() || getData().isEmpty()) { + if (getData().isEmpty()) { requestMonitor.done(); return; } @@ -490,13 +478,12 @@ public class DefaultVMModelProxyStrategy implements IVMModelProxy { parentDelta, getVMProvider().getPresentationContext(), -1, -1, new DataRequestMonitor>(getVMProvider().getExecutor(), requestMonitor) { @Override - protected void handleCompleted() { + protected void handleSuccess() { if (fDisposed) return; // Check for an empty list of elements. If it's empty then we // don't have to call the children nodes, so return here. - // No need to propagate error, there's no means or need to display it. - if (!isSuccess() || getData().size() == 0) { + if (getData().size() == 0) { requestMonitor.done(); return; } @@ -556,7 +543,7 @@ public class DefaultVMModelProxyStrategy implements IVMModelProxy { node, delta, calculateOffsets, new DataRequestMonitor>(getVMProvider().getExecutor(), requestMonitor) { @Override - protected void handleCompleted() { + protected void handleSuccess() { final CountingRequestMonitor multiRm = new CountingRequestMonitor(getVMProvider().getExecutor(), requestMonitor); int multiRmCount = 0; diff --git a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMChildrenCountUpdate.java b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMChildrenCountUpdate.java index d769057235a..bcf579e5632 100644 --- a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMChildrenCountUpdate.java +++ b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMChildrenCountUpdate.java @@ -45,6 +45,11 @@ public class VMChildrenCountUpdate extends VMViewerUpdate implements IChildrenCo fCountRequestMonitor.setData(count); } + @Override + public String toString() { + return "VMChildrenCountUpdate: " + getElement(); //$NON-NLS-1$ + } + @Override public void done() { assert isCanceled() || fCountRequestMonitor.getData() != null || !fCountRequestMonitor.isSuccess(); diff --git a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMChildrenUpdate.java b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMChildrenUpdate.java index 3afaed1633a..1bce8e1cb54 100644 --- a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMChildrenUpdate.java +++ b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMChildrenUpdate.java @@ -13,11 +13,7 @@ package org.eclipse.dd.dsf.ui.viewmodel; import java.util.ArrayList; import java.util.List; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; import org.eclipse.dd.dsf.concurrent.DataRequestMonitor; -import org.eclipse.dd.dsf.concurrent.IDsfStatusConstants; -import org.eclipse.dd.dsf.internal.ui.DsfUIPlugin; import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta; import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext; @@ -92,7 +88,7 @@ public class VMChildrenUpdate extends VMViewerUpdate implements IChildrenUpdate @Override public String toString() { - return "VMElementsUpdate for elements under parent = " + getElement() + ", in range " + getOffset() + " -> " + (getOffset() + getLength()); //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ + return "VMChildrenUpdate:" + getElement() + " {"+ getOffset() + "->" + (getOffset() + getLength()) + "}"; //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$ } @Override @@ -105,32 +101,21 @@ public class VMChildrenUpdate extends VMViewerUpdate implements IChildrenUpdate * A flexible hierarchy bug/optimization causes query with incorrect * IChildrenUpdate[] array length. * - * We found this while deleting a register node. Example: - * - * the register view displays: + * The problem manifests itself while deleting a register node. + * For example, if the register view displays: * PC * EAX * EBX * ECX * EDX - * - * we delete EBX and force a context refresh. - * - * flexible hierarchy queries for IChildrenUpdate[5] and IChildrenCountUpdate at - * the same time. - * - * VMElementsUpdate, used by VMCache to wrap the IChildrenUpdate, generates an - * IStatus.ERROR with message "Incomplete elements of updates" when fElements - * count (provided by service) does not match the length provided by the original - * update query. - * - * Workaround, always set the elements array in the request monitor, but still set - * the error status. + * And EBX is deleted, forcing a refresh, the viewer will query + * for IChildrenUpdate[5] and IChildrenCountUpdate at the same time. + * + * To avoid this problem do not generate an error if the list of + * children is smaller than the list of requested indexes. Also, + * do not check if any of the elements are null. */ rm.setData(fElements); - if (rm.isSuccess() && fLength != -1 && fElements.size() != fLength) { - rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "Incomplete elements of updates", null)); //$NON-NLS-1$ - } super.done(); } diff --git a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMHasChildrenUpdate.java b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMHasChildrenUpdate.java index 6d3ac59cc6d..3a4d84b997a 100644 --- a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMHasChildrenUpdate.java +++ b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/VMHasChildrenUpdate.java @@ -46,6 +46,11 @@ public class VMHasChildrenUpdate extends VMViewerUpdate implements IHasChildrenU fHasElemsRequestMonitor.setData(hasChildren); } + @Override + public String toString() { + return "VMHasChildrenUpdate: " + getElement(); //$NON-NLS-1$ + } + @Override public void done() { assert isCanceled() || fHasElemsRequestMonitor.getData() != null || !fHasElemsRequestMonitor.isSuccess(); diff --git a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/update/AbstractCachingVMProvider.java b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/update/AbstractCachingVMProvider.java index e1cd5d58933..50fff44089b 100644 --- a/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/update/AbstractCachingVMProvider.java +++ b/plugins/org.eclipse.dd.dsf.ui/src/org/eclipse/dd/dsf/ui/viewmodel/update/AbstractCachingVMProvider.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.concurrent.Executor; import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; import org.eclipse.dd.dsf.concurrent.CountingRequestMonitor; import org.eclipse.dd.dsf.concurrent.DataRequestMonitor; import org.eclipse.dd.dsf.concurrent.DsfRunnable; @@ -27,6 +28,7 @@ import org.eclipse.dd.dsf.concurrent.RequestMonitor; import org.eclipse.dd.dsf.datamodel.IDMContext; import org.eclipse.dd.dsf.datamodel.IDMData; import org.eclipse.dd.dsf.datamodel.IDMService; +import org.eclipse.dd.dsf.internal.ui.DsfUIPlugin; import org.eclipse.dd.dsf.ui.concurrent.ViewerCountingRequestMonitor; import org.eclipse.dd.dsf.ui.concurrent.ViewerDataRequestMonitor; import org.eclipse.dd.dsf.ui.viewmodel.AbstractVMAdapter; @@ -53,6 +55,14 @@ import org.eclipse.jface.viewers.TreePath; @SuppressWarnings("restriction") public class AbstractCachingVMProvider extends AbstractVMProvider implements ICachingVMProvider { + // debug flags + public static boolean DEBUG_CACHE = false; + + static { + DEBUG_CACHE = DsfUIPlugin.DEBUG && "true".equals( //$NON-NLS-1$ + Platform.getDebugOption("org.eclipse.dd.dsf.ui/debug/vm/cache")); //$NON-NLS-1$ + } + private static final int MAX_CACHE_SIZE = 1000; /** @@ -139,14 +149,60 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa super(key); } + /** + * Counter of flush operations performed on this entry. It is used + * by caching update operations to make sure that an update which + * was issued for a given entry is still valid for that entry when + * it is completed by the node. + */ + int fFlushCounter = 0; + + /** + * Indicates that the data in this cache entry is out of date with + * the data on the target. + */ Boolean fDirty = false; + + /** + * Cached {@link IHasChildrenUpdate} result. + */ Boolean fHasChildren = null; + + /** + * Cached {@link IChildrenCountUpdate} result. + */ Integer fChildrenCount = null; + + /** + * Flag indicating that all the children of the given element are + * alredy cached. + */ boolean fAllChildrenKnown = false; + + /** + * Map containing children of this element, keyed by child index. + */ Map fChildren = null; + + /** + * Map of IDMData objects, keyed by the DM context. + */ Map fDataOrStatus = new HashMap(1); + + /** + * Previous known value of the DM data objects. + */ Map fArchiveData = new HashMap(1);; + void ensureChildrenMap() { + if (fChildren == null) { + Integer childrenCount = fChildrenCount; + childrenCount = childrenCount != null ? childrenCount : 0; + int capacity = Math.max((childrenCount.intValue() * 4)/3, 32); + fChildren = new HashMap(capacity); + } + } + @Override public String toString() { return fKey.toString() + " = " + //$NON-NLS-1$ @@ -315,23 +371,38 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa } @Override - public void updateNode(IVMNode node, IHasChildrenUpdate[] updates) { + public void updateNode(final IVMNode node, IHasChildrenUpdate[] updates) { LinkedList missUpdates = new LinkedList(); for(final IHasChildrenUpdate update : updates) { + // Find or create the cache entry for the element of this update. ElementDataKey key = makeEntryKey(node, update); final ElementDataEntry entry = getElementDataEntry(key); + if (entry.fHasChildren != null) { + // Cache Hit! Just return the value. + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cacheHitHasChildren(node = " + node + ", update = " + update + ", " + entry.fHasChildren + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } update.setHasChilren(entry.fHasChildren.booleanValue()); update.done(); } else { + // Cache miss! Save the flush counter of the entry and create a proxy update. + final int flushCounter = entry.fFlushCounter; missUpdates.add( new VMHasChildrenUpdate( update, new ViewerDataRequestMonitor(getExecutor(), update) { @Override protected void handleCompleted() { + // Update completed. Write value to cache only if update successed + // and the cache entry wasn't flushed in the mean time. if(isSuccess()) { - entry.fHasChildren = this.getData(); + if (flushCounter == entry.fFlushCounter) { + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cacheSavedHasChildren(node = " + node + ", update = " + update + ", " + getData() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + entry.fHasChildren = this.getData(); + } update.setHasChilren(getData()); } else { update.setStatus(getStatus()); @@ -342,26 +413,42 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa } } + // Issue all the update proxies with one call. if (!missUpdates.isEmpty()) { super.updateNode(node, missUpdates.toArray(new IHasChildrenUpdate[missUpdates.size()])); } } @Override - public void updateNode(IVMNode node, final IChildrenCountUpdate update) { + public void updateNode(final IVMNode node, final IChildrenCountUpdate update) { + // Find or create the cache entry for the element of this update. ElementDataKey key = makeEntryKey(node, update); final ElementDataEntry entry = getElementDataEntry(key); + if(entry.fChildrenCount != null) { + // Cache Hit! Just return the value. + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cacheHitChildrenCount(node = " + node + ", update = " + update + ", " + entry.fChildrenCount + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } update.setChildCount(entry.fChildrenCount.intValue()); update.done(); } else { + // Cache miss! Save the flush counter of the entry and create a proxy update. + final int flushCounter = entry.fFlushCounter; IChildrenCountUpdate updateProxy = new VMChildrenCountUpdate( update, new ViewerDataRequestMonitor(getExecutor(), update) { @Override protected void handleCompleted() { + // Update completed. Write value to cache only if update successed + // and the cache entry wasn't flushed in the mean time. if(isSuccess()) { - entry.fChildrenCount = this.getData(); + if (flushCounter == entry.fFlushCounter) { + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cacheSavedChildrenCount(node = " + node + ", update = " + update + ", " + getData() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + entry.fChildrenCount = this.getData(); + } update.setChildCount(getData()); } else { update.setStatus(getStatus()); @@ -374,61 +461,70 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa } @Override - public void updateNode(IVMNode node, final IChildrenUpdate update) { - + public void updateNode(final IVMNode node, final IChildrenUpdate update) { + // Find or create the cache entry for the element of this update. ElementDataKey key = makeEntryKey(node, update); - final ElementDataEntry entry = getElementDataEntry(key); + + final int flushCounter = entry.fFlushCounter; if (entry.fChildren == null || (update.getOffset() < 0 && !entry.fAllChildrenKnown)) { - // We need to retrieve all the children if we don't have any children information. - // Or if the client requested all children (offset = -1, length -1) and we have not - // retrieved that before. + // Need to retrieve all the children if there is no children information yet. + // Or if the client requested all children (offset = -1, length -1) and all + // the children are not yet known. IChildrenUpdate updateProxy = new VMChildrenUpdate( update, update.getOffset(), update.getLength(), new ViewerDataRequestMonitor>(getExecutor(), update){ @Override - protected void handleCompleted() - { - // Workaround for a bug caused by an optimization in the viewer: - // The viewer may request more children then there are at a given level. - // This causes the update to return with an error. - // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=202109 - // Instead of checking isSuccess(), check getData() != null. - if(getData() != null && !isCanceled()) { - // Check if the udpate retrieved all children by specifying "offset = -1, length = -1" - int updateOffset = update.getOffset(); - if (updateOffset < 0) { - updateOffset = 0; + protected void handleSuccess() { + // Check if the update retrieved all children by specifying "offset = -1, length = -1" + int updateOffset = update.getOffset(); + if (updateOffset < 0) + { + updateOffset = 0; + if (entry.fFlushCounter == flushCounter) { entry.fAllChildrenKnown = true; } - - // Estimate size of children map. - Integer childrenCount = entry.fChildrenCount; - childrenCount = childrenCount != null ? childrenCount : 0; - int capacity = Math.max((childrenCount.intValue() * 4)/3, 32); - // Create a new map, but only if it hasn't been created yet by another update. - if (entry.fChildren == null) { - entry.fChildren = new HashMap(capacity); - } - - // Set the children to map and update. - for(int j = 0; j < getData().size(); j++) { - int offset = updateOffset + j; - Object child = getData().get(j); - if (child != null) { + } + + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cacheSavedChildren(node = " + node + ", update = " + update + ", children = {" + updateOffset + "->" + (updateOffset + getData().size()) + "})"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + } + + if (flushCounter == entry.fFlushCounter) { + entry.ensureChildrenMap(); + } + + // Set the children to map and update. + for(int j = 0; j < getData().size(); j++) { + int offset = updateOffset + j; + Object child = getData().get(j); + if (child != null) { + if (flushCounter == entry.fFlushCounter) { entry.fChildren.put(offset, child); - update.setChild(child, offset); } + update.setChild(child, offset); } } update.done(); } + + @Override + protected void handleCancel() { + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cacheCanceledChildren(node = " + node + ", update = " + update + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + super.handleCancel(); + } }); super.updateNode(node, updateProxy); } else if (update.getOffset() < 0 ) { // The update requested all children. Fill in all children assuming that // the children array is complete. - + + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cacheHitChildren(node = " + node + ", update = " + update + ", children = " + entry.fChildren.keySet() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + // The following assert should never fail given the first if statement. assert entry.fAllChildrenKnown; @@ -438,13 +534,15 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa } update.done(); } else { - // Make the list of missing children. If we've retrieved the + // Update for a partial list of children was requested. + // Iterate through the known children and make a list of missing + // indexes. List childrenMissingFromCache = new LinkedList(); for (int i = update.getOffset(); i < update.getOffset() + update.getLength(); i++) { childrenMissingFromCache.add(i); } - // Fill in the known children from cache. + // Write known children from cache into the update. for(Integer position = update.getOffset(); position < update.getOffset() + update.getLength(); position++) { Object child = entry.fChildren.get(position); if (child != null) { @@ -453,13 +551,15 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa } } + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cachePartialHitChildren(node = " + node + ", update = " + update + ", missing = " + childrenMissingFromCache + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + if (childrenMissingFromCache.size() > 0) { - // perform a partial update; we only have some of the children of the update request - + // Some children were not found in the cache, create separate + // proxy updates for the continuous ranges of missing children. List partialUpdates = new ArrayList(2); - final CountingRequestMonitor multiRm = new ViewerCountingRequestMonitor(getExecutor(), update); - while(childrenMissingFromCache.size() > 0) { final int offset = childrenMissingFromCache.get(0); @@ -475,10 +575,22 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa update, offset, length, new DataRequestMonitor>(getExecutor(), multiRm) { @Override - protected void handleCompleted() { - if (getData() != null) { - for (int i = 0; i < getData().size(); i++) { + protected void handleSuccess() { + // Only save the children to the cahce if the entry wasn't flushed. + if (flushCounter == entry.fFlushCounter) { + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cachePartialSaveChildren(node = " + node + ", update = " + update + ", saved = {" + offset + "->" + (offset + getData().size()) + "})"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + } + entry.ensureChildrenMap(); + } + + for (int i = 0; i < getData().size(); i++) { + if (getData().get(i) != null) { update.setChild(getData().get(i), offset + i); + if (flushCounter == entry.fFlushCounter) { + // Only save the children to the cahce if the entry wasn't flushed. + entry.fChildren.put(offset + i, getData().get(i)); + } } } multiRm.done(); @@ -491,7 +603,7 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa } multiRm.setDoneCount(partialUpdates.size()); } else { - // we have all of the children in cache; return from cache + // All children were found in cache. Compelte the update. update.done(); } } @@ -506,6 +618,9 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa * @param archive */ private void flush(FlushMarkerKey flushKey) { + if (DEBUG_CACHE && (DEBUG_PRESENTATION_ID == null || getPresentationContext().getId().equals(DEBUG_PRESENTATION_ID))) { + DsfUIPlugin.debug("cacheFlushing(" + flushKey + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + } // For each entry that has the given context as a parent, perform the flush. // Iterate through the cache entries backwards. This means that we will be // iterating in order of most-recently-used to least-recently-used. @@ -528,6 +643,7 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa // now. if (entryFlushKey.includes(flushKey)) { break; + } } else if (entry instanceof ElementDataEntry) { @@ -561,9 +677,11 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa entry.remove(); } } + elementDataEntry.fFlushCounter++; elementDataEntry.fHasChildren = null; elementDataEntry.fChildrenCount = null; elementDataEntry.fChildren = null; + elementDataEntry.fAllChildrenKnown = false; elementDataEntry.fDirty = false; } else if ((updateFlags & IVMUpdatePolicy.DIRTY) != 0) { elementDataEntry.fDirty = true; @@ -754,7 +872,9 @@ public class AbstractCachingVMProvider extends AbstractVMProvider implements ICa entry.fDataOrStatus.put(dmc, getData()); rm.setData(getData()); } else { - entry.fDataOrStatus.put(dmc, getStatus()); + if (!isCanceled()) { + entry.fDataOrStatus.put(dmc, getStatus()); + } rm.setStatus(getStatus()); } rm.done(); diff --git a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/RequestMonitor.java b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/RequestMonitor.java index 68ada40780a..7d7186264b4 100644 --- a/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/RequestMonitor.java +++ b/plugins/org.eclipse.dd.dsf/src/org/eclipse/dd/dsf/concurrent/RequestMonitor.java @@ -136,7 +136,10 @@ public class RequestMonitor { * Sets the status of the result of the request. If status is OK, this * method does not need to be called. */ - public synchronized void setStatus(IStatus status) { fStatus = status; } + public synchronized void setStatus(IStatus status) { + assert isCanceled() || status.getSeverity() != IStatus.CANCEL; + fStatus = status; + } /** Returns the status of the completed method. */ public synchronized IStatus getStatus() {