From 75e6f8510f29794286ec1cc7550126d950337a33 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Tue, 3 Nov 2009 15:58:38 +0000 Subject: [PATCH] [280461] Introduce a way to add extra executor depths to preserve the order of MI events and MI command results --- .../cdt/dsf/gdb/service/GDBProcesses_7_0.java | 17 +- .../eclipse/cdt/dsf/mi/service/MIMemory.java | 13 +- .../cdt/dsf/mi/service/MIProcesses.java | 13 +- .../cdt/dsf/mi/service/MIRegisters.java | 15 +- .../cdt/dsf/mi/service/MIRunControl.java | 13 +- .../eclipse/cdt/dsf/mi/service/MIStack.java | 13 +- .../command/BufferedCommandControl.java | 149 ++++++++++++++++++ .../debug/service/command/CommandCache.java | 55 +++++-- 8 files changed, 266 insertions(+), 22 deletions(-) create mode 100644 dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/command/BufferedCommandControl.java diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_0.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_0.java index 3c0c372de60..837c0a00de1 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_0.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_0.java @@ -36,6 +36,7 @@ import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; +import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; import org.eclipse.cdt.dsf.debug.service.command.CommandCache; import org.eclipse.cdt.dsf.debug.service.command.IEventListener; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; @@ -428,12 +429,23 @@ public class GDBProcesses_7_0 extends AbstractDsfService fCommandControl = getServicesTracker().getService(IGDBControl.class); fBackend = getServicesTracker().getService(IGDBBackend.class); + BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(fCommandControl, getExecutor(), 2); - fContainerCommandCache = new CommandCache(getSession(), fCommandControl); + // These caches store the result of a command when received; also, these caches + // are manipulated when receiving events. Currently, events are received after + // three scheduling of the executor, while command results after only one. This + // can cause problems because command results might be processed before an event + // that actually arrived before the command result. + // To solve this, we use a bufferedCommandControl that will delay the command + // result by two scheduling of the executor. + // See bug 280461 + fContainerCommandCache = new CommandCache(getSession(), bufferedCommandControl); fContainerCommandCache.setContextAvailable(fCommandControl.getContext(), true); - fThreadCommandCache = new CommandCache(getSession(), fCommandControl); + fThreadCommandCache = new CommandCache(getSession(), bufferedCommandControl); fThreadCommandCache.setContextAvailable(fCommandControl.getContext(), true); + // No need to use the bufferedCommandControl for the listThreadGroups cache + // because it is not being affected by events. fListThreadGroupsAvailableCache = new CommandCache(getSession(), fCommandControl); fListThreadGroupsAvailableCache.setContextAvailable(fCommandControl.getContext(), true); @@ -757,7 +769,6 @@ public class GDBProcesses_7_0 extends AbstractDsfService final ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class); if (controlDmc != null) { - // Don't cache this command since the list can change at any time. fListThreadGroupsAvailableCache.execute( new MIListThreadGroups(controlDmc, true), new DataRequestMonitor(getExecutor(), rm) { diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIMemory.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIMemory.java index 38d6a079b8c..39f698d239f 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIMemory.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIMemory.java @@ -37,6 +37,7 @@ import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerSuspendedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.StateChangeReason; +import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; import org.eclipse.cdt.dsf.debug.service.command.CommandCache; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; @@ -127,7 +128,17 @@ public class MIMemory extends AbstractDsfService implements IMemory, ICachingSer private void doInitialize(final RequestMonitor requestMonitor) { // Create the command cache ICommandControlService commandControl = getServicesTracker().getService(ICommandControlService.class); - fCommandCache = new CommandCache(getSession(), commandControl); + BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(commandControl, getExecutor(), 2); + + // This cache stores the result of a command when received; also, this cache + // is manipulated when receiving events. Currently, events are received after + // three scheduling of the executor, while command results after only one. This + // can cause problems because command results might be processed before an event + // that actually arrived before the command result. + // To solve this, we use a bufferedCommandControl that will delay the command + // result by two scheduling of the executor. + // See bug 280461 + fCommandCache = new CommandCache(getSession(), bufferedCommandControl); fCommandCache.setContextAvailable(commandControl.getContext(), true); // Register this service diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIProcesses.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIProcesses.java index 3e6f067aed2..bef70cd0d9d 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIProcesses.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIProcesses.java @@ -28,6 +28,7 @@ import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; +import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; import org.eclipse.cdt.dsf.debug.service.command.CommandCache; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; @@ -347,7 +348,17 @@ public class MIProcesses extends AbstractDsfService implements IMIProcesses, ICa // new Hashtable()); fCommandControl = getServicesTracker().getService(ICommandControlService.class); - fContainerCommandCache = new CommandCache(getSession(), fCommandControl); + BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(fCommandControl, getExecutor(), 2); + + // This cache stores the result of a command when received; also, this cache + // is manipulated when receiving events. Currently, events are received after + // three scheduling of the executor, while command results after only one. This + // can cause problems because command results might be processed before an event + // that actually arrived before the command result. + // To solve this, we use a bufferedCommandControl that will delay the command + // result by two scheduling of the executor. + // See bug 280461 + fContainerCommandCache = new CommandCache(getSession(), bufferedCommandControl); fContainerCommandCache.setContextAvailable(fCommandControl.getContext(), true); getSession().addServiceEventListener(this, null); diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRegisters.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRegisters.java index 333c2c31d7f..301bc0670b9 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRegisters.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRegisters.java @@ -25,6 +25,7 @@ import org.eclipse.cdt.dsf.debug.service.IRunControl; import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.StateChangeReason; +import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; import org.eclipse.cdt.dsf.debug.service.command.CommandCache; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; @@ -172,8 +173,20 @@ public class MIRegisters extends AbstractDsfService implements IRegisters, ICach * Create the lower level register cache. */ ICommandControlService commandControl = getServicesTracker().getService(ICommandControlService.class); - fRegisterValueCache = new CommandCache(getSession(), commandControl); + BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(commandControl, getExecutor(), 2); + + // This cache stores the result of a command when received; also, this cache + // is manipulated when receiving events. Currently, events are received after + // three scheduling of the executor, while command results after only one. This + // can cause problems because command results might be processed before an event + // that actually arrived before the command result. + // To solve this, we use a bufferedCommandControl that will delay the command + // result by two scheduling of the executor. + // See bug 280461 + fRegisterValueCache = new CommandCache(getSession(), bufferedCommandControl); fRegisterValueCache.setContextAvailable(commandControl.getContext(), true); + + // This cache is not affected by events so does not need the bufferedCommandControl fRegisterNameCache = new CommandCache(getSession(), commandControl); fRegisterNameCache.setContextAvailable(commandControl.getContext(), true); diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRunControl.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRunControl.java index 0d53fa48d70..db7b3ed0c9d 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRunControl.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRunControl.java @@ -21,6 +21,7 @@ import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.datamodel.IDMEvent; import org.eclipse.cdt.dsf.debug.service.ICachingService; import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; +import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; import org.eclipse.cdt.dsf.debug.service.command.CommandCache; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent; @@ -288,7 +289,17 @@ public class MIRunControl extends AbstractDsfService implements IMIRunControl, I private void doInitialize(final RequestMonitor rm) { fConnection = getServicesTracker().getService(ICommandControlService.class); - fMICommandCache = new CommandCache(getSession(), fConnection); + BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(fConnection, getExecutor(), 2); + + // This cache stores the result of a command when received; also, this cache + // is manipulated when receiving events. Currently, events are received after + // three scheduling of the executor, while command results after only one. This + // can cause problems because command results might be processed before an event + // that actually arrived before the command result. + // To solve this, we use a bufferedCommandControl that will delay the command + // result by two scheduling of the executor. + // See bug 280461 + fMICommandCache = new CommandCache(getSession(), bufferedCommandControl); fMICommandCache.setContextAvailable(fConnection.getContext(), true); getSession().addServiceEventListener(this, null); rm.done(); diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIStack.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIStack.java index b163bba7bba..0bbeb664379 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIStack.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIStack.java @@ -31,6 +31,7 @@ import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.StateChangeReason; +import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; import org.eclipse.cdt.dsf.debug.service.command.CommandCache; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; @@ -190,7 +191,17 @@ public class MIStack extends AbstractDsfService private void doInitialize(RequestMonitor rm) { ICommandControlService commandControl = getServicesTracker().getService(ICommandControlService.class); - fMICommandCache = new CommandCache(getSession(), commandControl); + BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(commandControl, getExecutor(), 2); + + // This cache stores the result of a command when received; also, this cache + // is manipulated when receiving events. Currently, events are received after + // three scheduling of the executor, while command results after only one. This + // can cause problems because command results might be processed before an event + // that actually arrived before the command result. + // To solve this, we use a bufferedCommandControl that will delay the command + // result by two scheduling of the executor. + // See bug 280461 + fMICommandCache = new CommandCache(getSession(), bufferedCommandControl); fMICommandCache.setContextAvailable(commandControl.getContext(), true); fRunControl = getServicesTracker().getService(IRunControl.class); diff --git a/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/command/BufferedCommandControl.java b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/command/BufferedCommandControl.java new file mode 100644 index 00000000000..7ee804f25f1 --- /dev/null +++ b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/command/BufferedCommandControl.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * Copyright (c) 2009 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.cdt.dsf.debug.service.command; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.DsfExecutor; +import org.eclipse.cdt.dsf.concurrent.DsfRunnable; +import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; + +/** + * A command control which delays the results of commands + * sent to a command control, as well as events from the command control. + * The delay is specified in the constructor using a number of executor + * dispatch cycles. + * + * @since 2.1 + */ +public class BufferedCommandControl implements ICommandControl { + + private DsfExecutor fExecutor; + private ICommandControl fControlDelegate; + private int fDepth; + + private ICommandListener fCommandListener = new ICommandListener() { + public void commandQueued(ICommandToken token) { + for (ICommandListener processor : fCommandProcessors) { + processor.commandQueued(token); + } + }; + + public void commandRemoved(ICommandToken token) { + for (ICommandListener processor : fCommandProcessors) { + processor.commandRemoved(token); + } + }; + + public void commandSent(final ICommandToken token) { + for (ICommandListener processor : fCommandProcessors) { + processor.commandSent(token); + } + }; + + public void commandDone(final ICommandToken token, final ICommandResult result) { + buffer(fDepth, new DsfRunnable() { + public void run() { + for (ICommandListener processor : fCommandProcessors) { + processor.commandDone(token, result); + } + }; + }); + }; + }; + + private IEventListener fEventListener = new IEventListener() { + public void eventReceived(final Object output) { + buffer(fDepth, new DsfRunnable() { + public void run() { + for (IEventListener processor : fEventProcessors) { + processor.eventReceived(output); + } + }; + }); + } + }; + + private final List fCommandProcessors = new ArrayList(); + private final List fEventProcessors = new ArrayList(); + + public BufferedCommandControl(ICommandControl controlDelegate, DsfExecutor executor, int depth) { + fControlDelegate = controlDelegate; + fExecutor = executor; + fDepth = depth; + assert fDepth > 0; + } + + public void addCommandListener(ICommandListener listener) { + if (fCommandProcessors.isEmpty()) { + fControlDelegate.addCommandListener(fCommandListener); + } + fCommandProcessors.add(listener); + } + + + public void removeCommandListener(ICommandListener listener) { + fCommandProcessors.remove(listener); + if (fCommandProcessors.isEmpty()) { + fControlDelegate.removeCommandListener(fCommandListener); + } + } + + public void addEventListener(IEventListener listener) { + if (fEventProcessors.isEmpty()) { + fControlDelegate.addEventListener(fEventListener); + } + fEventProcessors.add(listener); + } + + public void removeEventListener(IEventListener listener) { + fEventProcessors.remove(listener); + if (fEventProcessors.isEmpty()) { + fControlDelegate.removeEventListener(fEventListener); + } + } + + public ICommandToken queueCommand(final ICommand command, final DataRequestMonitor rm) { + return fControlDelegate.queueCommand( + command, + new DataRequestMonitor(ImmediateExecutor.getInstance(), rm) { + @Override + protected void handleCompleted() { + buffer(fDepth, new DsfRunnable() { + public void run() { + rm.setData(getData()); + rm.setStatus(getStatus()); + rm.done(); + } + }); + } + }); + } + + public void removeCommand(ICommandToken token) { + fControlDelegate.removeCommand(token); + } + + private void buffer(final int depth, final DsfRunnable runnable) { + if (depth == 0) { + runnable.run(); + } else { + fExecutor.execute(new DsfRunnable() { + public void run() { + buffer(depth - 1, runnable); + } + }); + } + } + +} diff --git a/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/command/CommandCache.java b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/command/CommandCache.java index ba5497756ca..a21c655c22f 100644 --- a/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/command/CommandCache.java +++ b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/command/CommandCache.java @@ -22,7 +22,9 @@ import java.util.Map; import java.util.Set; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.DsfRunnable; import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants; +import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.internal.DsfPlugin; @@ -118,6 +120,13 @@ public class CommandCache implements ICommandListener private DsfSession fSession; + /** + * The command control to be used to send commands to the backend. + * In certain cases, a {@link BufferedCommandControl} should be used + * to artificially delay the processing of command results; these + * artificial delays are important to keep events and command results + * ordered as received by the backend. + */ private ICommandControl fCommandControl; /* @@ -384,7 +393,24 @@ public class CommandCache implements ICommandListener finalCachedCmd.fToken = fCommandControl.queueCommand( finalCachedCmd.getCommand(), - new DataRequestMonitor(fSession.getExecutor(), null) { + new DataRequestMonitor(ImmediateExecutor.getInstance(), null) { + @Override + public synchronized void done() { + // protect against the cache being called in non-session thread, but at + // the same time avoid adding extra dispatch cycles to command processing. + if (fSession.getExecutor().isInExecutorThread()) {; + super.done(); + } else { + fSession.getExecutor().execute(new DsfRunnable() { + public void run() { + superDone(); + } + }); + } + } + private void superDone() { + super.done(); + } @Override public void handleCompleted() { @@ -462,20 +488,21 @@ public class CommandCache implements ICommandListener } } } else { - /* - * This is an original request which completed. Indicate success or - * failure to the original requesters. - */ - CommandResultInfo resultInfo = new CommandResultInfo(result, status); + // Save the command result in cache, but only if the command's context + // is still available. Otherwise an error may get cached incorrectly. + if (isTargetAvailable(context)) { + CommandResultInfo resultInfo = new CommandResultInfo(result, status); - if (fCachedContexts.get(context) != null){ - fCachedContexts.get(context).put(finalCachedCmd, resultInfo); - } else { - HashMap map = new HashMap(); - map.put(finalCachedCmd, resultInfo); - fCachedContexts.put(context, map); - } - + if (fCachedContexts.get(context) != null){ + fCachedContexts.get(context).put(finalCachedCmd, resultInfo); + } else { + HashMap map = new HashMap(); + map.put(finalCachedCmd, resultInfo); + fCachedContexts.put(context, map); + } + } + // This is an original request which completed. Indicate success or + // failure to the original requesters. if (!isSuccess()) { /* * We had some form of error with the original command. So notify the