/*******************************************************************************
 * Copyright (c) 2006, 2016 Wind River Systems and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *     Ericsson	AB		  - Modified for handling of multiple threads
 *     Indel AG           - [369622] fixed moveToLine using MinGW
 *     Marc Khouzam (Ericsson) - Support for operations on multiple execution contexts (bug 330974)
 *     Alvaro Sanchez-Leon (Ericsson AB) - Support for Step into selection (bug 244865)
 *     Alvaro Sanchez-Leon (Ericsson AB) - Bug 415362
 *     Marc Khouzam (Ericsson) - Wait for *stopped event when suspending (bug 429621)
 *     Xavier Raynaud (Kalray) - Bug 438635
 *******************************************************************************/

package org.eclipse.cdt.dsf.gdb.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.cdt.core.IAddress;
import org.eclipse.cdt.core.model.IFunctionDeclaration;
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
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.ImmediateCountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Immutable;
import org.eclipse.cdt.dsf.concurrent.MultiRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.concurrent.Sequence.Step;
import org.eclipse.cdt.dsf.datamodel.AbstractDMEvent;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.datamodel.IDMEvent;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpointsExtension.IBreakpointHitDMEvent;
import org.eclipse.cdt.dsf.debug.service.ICachingService;
import org.eclipse.cdt.dsf.debug.service.IMultiRunControl;
import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext;
import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.IRunControl2;
import org.eclipse.cdt.dsf.debug.service.IRunControl3;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup.ISourceLookupDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommand;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.internal.service.command.events.MITracepointSelectedEvent;
import org.eclipse.cdt.dsf.gdb.internal.service.control.StepIntoSelectionActiveOperation;
import org.eclipse.cdt.dsf.gdb.internal.service.control.StepIntoSelectionUtils;
import org.eclipse.cdt.dsf.mi.service.IMIBreakpointPathAdjuster;
import org.eclipse.cdt.dsf.mi.service.IMICommandControl;
import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIProcesses;
import org.eclipse.cdt.dsf.mi.service.IMIRunControl;
import org.eclipse.cdt.dsf.mi.service.MIBreakpointDMData;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.MIBreakpointDMContext;
import org.eclipse.cdt.dsf.mi.service.MIRunControl;
import org.eclipse.cdt.dsf.mi.service.MIStack;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.events.IMIDMEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MICatchpointHitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIErrorEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIFunctionFinishedEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIInferiorExitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIRunningEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MISharedLibEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MISignalEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MISteppingRangeEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadCreatedEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadExitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointTriggerEvent;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakInsertInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIFrame;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIStackInfoDepthInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIThread;
import org.eclipse.cdt.dsf.mi.service.command.output.MIThreadInfoInfo;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugException;
import org.osgi.framework.BundleContext;

/**
 * Implementation note: This class implements event handlers for the events that
 * are generated by this service itself. When the event is dispatched, these
 * handlers will be called first, before any of the clients. These handlers
 * update the service's internal state information to make them consistent with
 * the events being issued. Doing this in the handlers as opposed to when the
 * events are generated, guarantees that the state of the service will always be
 * consistent with the events. The purpose of this pattern is to allow clients
 * that listen to service events and track service state, to be perfectly in
 * sync with the service state.
 * @since 1.1
 */
public class GDBRunControl_7_0_NS extends AbstractDsfService
		implements IMIRunControl, IMultiRunControl, ICachingService, IRunControl3 {
	// /////////////////////////////////////////////////////////////////////////
	// CONSTANTS
	// /////////////////////////////////////////////////////////////////////////

	@Immutable
	private static class ExecutionData implements IExecutionDMData2 {
		private final StateChangeReason fReason;
		private final String fDetails;

		ExecutionData(StateChangeReason reason, String details) {
			fReason = reason;
			fDetails = details;
		}

		@Override
		public StateChangeReason getStateChangeReason() {
			return fReason;
		}

		@Override
		public String getDetails() {
			return fDetails;
		}
	}

	/**
	 * Base class for events generated by the MI Run Control service.  Most events
	 * generated by the MI Run Control service are directly caused by some MI event.
	 * Other services may need access to the extended MI data carried in the event.
	 *
	 * @param <V> DMC that this event refers to
	 * @param <T> MIInfo object that is the direct cause of this event
	 * @see MIRunControl
	 */
	@Immutable
	private static class RunControlEvent<V extends IDMContext, T extends MIEvent<? extends IDMContext>>
			extends AbstractDMEvent<V> implements IMIDMEvent {
		final private T fMIInfo;

		public RunControlEvent(V dmc, T miInfo) {
			super(dmc);
			fMIInfo = miInfo;
		}

		@Override
		public T getMIEvent() {
			return fMIInfo;
		}
	}

	/**
	 * Indicates that the given thread has been suspended.
	 * @since 4.0
	 */
	@Immutable
	protected static class SuspendedEvent extends RunControlEvent<IExecutionDMContext, MIStoppedEvent>
			implements ISuspendedDMEvent {
		SuspendedEvent(IExecutionDMContext ctx, MIStoppedEvent miInfo) {
			super(ctx, miInfo);
		}

		@Override
		public StateChangeReason getReason() {
			if (getMIEvent() instanceof MICatchpointHitEvent) { // must precede MIBreakpointHitEvent
				return StateChangeReason.EVENT_BREAKPOINT;
			} else if (getMIEvent() instanceof MITracepointSelectedEvent) { // must precede MIBreakpointHitEvent
				return StateChangeReason.UNKNOWN; // Don't display anything here, the details will take care of it
			} else if (getMIEvent() instanceof MIBreakpointHitEvent) {
				return StateChangeReason.BREAKPOINT;
			} else if (getMIEvent() instanceof MISteppingRangeEvent) {
				return StateChangeReason.STEP;
			} else if (getMIEvent() instanceof MIFunctionFinishedEvent) {
				return StateChangeReason.STEP;
			} else if (getMIEvent() instanceof MISharedLibEvent) {
				return StateChangeReason.SHAREDLIB;
			} else if (getMIEvent() instanceof MISignalEvent) {
				return StateChangeReason.SIGNAL;
			} else if (getMIEvent() instanceof MIWatchpointTriggerEvent) {
				return StateChangeReason.WATCHPOINT;
			} else if (getMIEvent() instanceof MIErrorEvent) {
				return StateChangeReason.ERROR;
			} else {
				return StateChangeReason.USER_REQUEST;
			}
		}

		public String getDetails() {
			MIStoppedEvent event = getMIEvent();
			if (event instanceof MICatchpointHitEvent) { // must precede MIBreakpointHitEvent
				return ((MICatchpointHitEvent) event).getReason();
			} else if (event instanceof MITracepointSelectedEvent) { // must precede MIBreakpointHitEvent
				return ((MITracepointSelectedEvent) event).getReason();
			} else if (event instanceof MISharedLibEvent) {
				return ((MISharedLibEvent) event).getLibrary();
			} else if (event instanceof MISignalEvent) {
				return ((MISignalEvent) event).getName() + ':' + ((MISignalEvent) event).getMeaning();
			} else if (event instanceof MIWatchpointTriggerEvent) {
				return ((MIWatchpointTriggerEvent) event).getExpression();
			} else if (event instanceof MIErrorEvent) {
				return ((MIErrorEvent) event).getMessage();
			}

			return null;
		}
	}

	/**
	 * Indicates that the given thread has been suspended on a breakpoint.
	 * @since 4.0
	 */
	@Immutable
	protected static class BreakpointHitEvent extends SuspendedEvent implements IBreakpointHitDMEvent {
		final private IBreakpointDMContext[] fBreakpoints;

		BreakpointHitEvent(IExecutionDMContext ctx, MIBreakpointHitEvent miInfo, IBreakpointDMContext bpCtx) {
			super(ctx, miInfo);

			fBreakpoints = new IBreakpointDMContext[] { bpCtx };
		}

		@Override
		public IBreakpointDMContext[] getBreakpoints() {
			return fBreakpoints;
		}
	}

	/**
	 * @since 4.0
	 */
	@Immutable
	protected static class ResumedEvent extends RunControlEvent<IExecutionDMContext, MIRunningEvent>
			implements IResumedDMEvent {
		ResumedEvent(IExecutionDMContext ctx, MIRunningEvent miInfo) {
			super(ctx, miInfo);
		}

		@Override
		public StateChangeReason getReason() {
			if (getMIEvent() != null) {
				switch (getMIEvent().getType()) {
				case MIRunningEvent.CONTINUE:
					return StateChangeReason.USER_REQUEST;
				case MIRunningEvent.NEXT:
				case MIRunningEvent.NEXTI:
					return StateChangeReason.STEP;
				case MIRunningEvent.STEP:
				case MIRunningEvent.STEPI:
					return StateChangeReason.STEP;
				case MIRunningEvent.FINISH:
					return StateChangeReason.STEP;
				case MIRunningEvent.UNTIL:
				case MIRunningEvent.RETURN:
					break;
				}
			}
			return StateChangeReason.UNKNOWN;
		}
	}

	/**
	 * @since 4.0
	 */
	@Immutable
	protected static class StartedDMEvent extends RunControlEvent<IExecutionDMContext, MIThreadCreatedEvent>
			implements IStartedDMEvent {
		StartedDMEvent(IMIExecutionDMContext executionDmc, MIThreadCreatedEvent miInfo) {
			super(executionDmc, miInfo);
		}
	}

	/**
	 * @since 4.0
	 */
	@Immutable
	protected static class ExitedDMEvent extends RunControlEvent<IExecutionDMContext, MIThreadExitEvent>
			implements IExitedDMEvent {
		ExitedDMEvent(IMIExecutionDMContext executionDmc, MIThreadExitEvent miInfo) {
			super(executionDmc, miInfo);
		}
	}

	protected class MIThreadRunState {
		// State flags
		boolean fSuspended = false;
		boolean fResumePending = false;
		boolean fStepping = false;
		RunControlEvent<IExecutionDMContext, ?> fLatestEvent = null;

		/**
		 * What caused the state change. E.g., a signal was thrown.
		 */
		StateChangeReason fStateChangeReason;

		/**
		 * Further detail on what caused the state change. E.g., the specific signal
		 * that was throw was a SIGINT. The exact string comes from gdb in the mi
		 * event. May be null, as not all types of state change have additional
		 * detail of interest.
		 */
		String fStateChangeDetails;
	}

	/**
	 * @since 4.0
	 */
	protected static class RunToLineActiveOperation {
		private IMIExecutionDMContext fThreadContext;
		private String fBpId;
		private String fFileLocation;
		private String fAddrLocation;
		private boolean fSkipBreakpoints;

		/** @since 5.0 */
		public RunToLineActiveOperation(IMIExecutionDMContext threadContext, String bpId, String fileLoc, String addr,
				boolean skipBreakpoints) {
			fThreadContext = threadContext;
			fBpId = bpId;
			fFileLocation = fileLoc;
			fAddrLocation = addr;
			fSkipBreakpoints = skipBreakpoints;
		}

		public IMIExecutionDMContext getThreadContext() {
			return fThreadContext;
		}

		/** @since 5.0 */
		public String getBreakpointId() {
			return fBpId;
		}

		public String getFileLocation() {
			return fFileLocation;
		}

		public String getAddrLocation() {
			return fAddrLocation;
		}

		public boolean shouldSkipBreakpoints() {
			return fSkipBreakpoints;
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// MIRunControlNS
	///////////////////////////////////////////////////////////////////////////

	private ICommandControlService fConnection;
	private CommandFactory fCommandFactory;
	private IGDBProcesses fProcessService;

	private boolean fTerminated = false;

	// ThreadStates indexed by the execution context
	protected Map<IMIExecutionDMContext, MIThreadRunState> fThreadRunStates = new HashMap<>();

	private RunToLineActiveOperation fRunToLineActiveOperation = null;

	private StepIntoSelectionActiveOperation fStepInToSelectionActiveOperation = null;

	/** @since 4.0 */
	protected RunToLineActiveOperation getRunToLineActiveOperation() {
		return fRunToLineActiveOperation;
	}

	/** @since 4.0 */
	protected void setRunToLineActiveOperation(RunToLineActiveOperation operation) {
		fRunToLineActiveOperation = operation;
	}

	/**
	 * Set of threads for which the next MIRunning event should be silenced.
	 */
	private Set<IMIExecutionDMContext> fDisableNextRunningEventDmcSet = new HashSet<>();
	/**
	 * Set of threads for which the next MISignal (MIStopped) event should be silenced.
	 */
	private Set<IMIExecutionDMContext> fDisableNextSignalEventDmcSet = new HashSet<>();
	/**
	 * Map that stores the silenced MIStopped event for the specified thread, in case we need to use it for a failure.
	 */
	private Map<IMIExecutionDMContext, MIStoppedEvent> fSilencedSignalEventMap = new HashMap<>();

	/**
	 * This variable allows us to know if run control operation
	 * should be enabled or disabled.  Run control operations are
	 * always enabled except when visualizing tracepoints.
	 */
	private boolean fRunControlOperationsEnabled = true;

	///////////////////////////////////////////////////////////////////////////
	// Initialization and shutdown
	///////////////////////////////////////////////////////////////////////////

	public GDBRunControl_7_0_NS(DsfSession session) {
		super(session);
	}

	@Override
	public void initialize(final RequestMonitor rm) {
		super.initialize(new ImmediateRequestMonitor(rm) {
			@Override
			protected void handleSuccess() {
				doInitialize(rm);
			}
		});
	}

	private void doInitialize(final RequestMonitor rm) {
		register(
				new String[] { IRunControl.class.getName(), IRunControl2.class.getName(), IMIRunControl.class.getName(),
						IMultiRunControl.class.getName(), IRunControl3.class.getName() },
				new Hashtable<String, String>());
		fConnection = getServicesTracker().getService(ICommandControlService.class);
		fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory();
		fProcessService = getServicesTracker().getService(IGDBProcesses.class);

		getSession().addServiceEventListener(this, null);
		rm.done();
	}

	@Override
	public void shutdown(final RequestMonitor rm) {
		unregister();
		getSession().removeServiceEventListener(this);
		super.shutdown(rm);
	}

	/** @since 4.1 */
	protected boolean getRunControlOperationsEnabled() {
		return fRunControlOperationsEnabled;
	}

	/** @since 4.1 */
	protected void setRunControlOperationsEnabled(boolean runControlEnabled) {
		fRunControlOperationsEnabled = runControlEnabled;
	}

	///////////////////////////////////////////////////////////////////////////
	// AbstractDsfService
	///////////////////////////////////////////////////////////////////////////

	@Override
	protected BundleContext getBundleContext() {
		return GdbPlugin.getBundleContext();
	}

	///////////////////////////////////////////////////////////////////////////
	// IRunControl
	///////////////////////////////////////////////////////////////////////////

	// ------------------------------------------------------------------------
	// Suspend
	// ------------------------------------------------------------------------

	@Override
	public boolean isSuspended(IExecutionDMContext context) {

		// Thread case
		if (context instanceof IMIExecutionDMContext) {
			MIThreadRunState threadState = fThreadRunStates.get(context);
			return (threadState == null) ? false : !fTerminated && threadState.fSuspended;
		}

		// Process case.  The process is considered suspended as long
		// as one of its thread is suspended
		if (context instanceof IMIContainerDMContext) {
			boolean hasThread = false;
			for (IMIExecutionDMContext threadContext : fThreadRunStates.keySet()) {
				if (DMContexts.isAncestorOf(threadContext, context)) {
					hasThread = true;
					if (isSuspended(threadContext))
						return true;
				}
			}
			// If this container does not have any threads, it means it wasn't started
			// yet or it was terminated, so we can consider it suspended
			if (hasThread == false)
				return true;
		}

		// Default case
		return false;
	}

	@Override
	public void canSuspend(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) {
		if (fRunControlOperationsEnabled == false) {
			rm.done(false);
			return;
		}

		rm.done(doCanSuspend(context));
	}

	/**
	 * @since 4.5
	 */
	protected boolean doCanSuspend(IExecutionDMContext context) {
		// Thread case
		if (context instanceof IMIExecutionDMContext) {
			MIThreadRunState threadState = fThreadRunStates.get(context);
			return (threadState == null) ? false : !fTerminated && !threadState.fSuspended;
		}

		// Process case
		if (context instanceof IMIContainerDMContext) {
			for (IMIExecutionDMContext threadContext : fThreadRunStates.keySet()) {
				if (DMContexts.isAncestorOf(threadContext, context)) {
					if (doCanSuspend(threadContext)) {
						return true;
					}
				}
			}
			return false;
		}

		// Default case
		return false;
	}

	@Override
	public void suspend(IExecutionDMContext context, final RequestMonitor rm) {

		assert context != null;

		// Thread case
		IMIExecutionDMContext thread = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
		if (thread != null) {
			doSuspend(thread, rm);
			return;
		}

		// Process case
		IMIContainerDMContext container = DMContexts.getAncestorOfType(context, IMIContainerDMContext.class);
		if (container != null) {
			doSuspend(container, rm);
			return;
		}

		// Default case
		rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Invalid context type.", null)); //$NON-NLS-1$
		rm.done();
	}

	/**
	 * Request the suspend for a single thread and wait for a proper *stopped event before
	 * indicating success.
	 */
	private void doSuspend(final IMIExecutionDMContext context, final RequestMonitor rm) {
		if (!doCanSuspend(context)) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
					"Given context: " + context + ", is already suspended.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		// Start the job before sending the interrupt command
		// to make sure we don't miss the *stopped event
		final MonitorSuspendJob monitorJob = new MonitorSuspendJob(context, 0, rm);
		fConnection.queueCommand(fCommandFactory.createMIExecInterrupt(context),
				new ImmediateDataRequestMonitor<MIInfo>() {
					@Override
					protected void handleSuccess() {
						// Nothing to do in the case of success, the monitoring job
						// will take care of completing the RM once it gets the
						// *stopped event.
					}

					@Override
					protected void handleFailure() {
						// In case of failure, we must cancel the monitoring job
						// and indicate the failure in the rm.
						monitorJob.cleanAndCancel();
						rm.done(getStatus());
					}
				});
	}

	/**
	 * Request the suspend for a process.  In this case we don't wait for any *stopped events explicitly
	 * because we would need to wait for one per thread and manage all those events.  It is not necessary.
	 * @since 4.5
	 */
	protected void doSuspend(IMIContainerDMContext context, final RequestMonitor rm) {
		if (!doCanSuspend(context)) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
					"Given context: " + context + ", is already suspended.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		String groupId = context.getGroupId();
		fConnection.queueCommand(fCommandFactory.createMIExecInterrupt(context, groupId),
				new DataRequestMonitor<MIInfo>(getExecutor(), rm));
	}

	/**
	 * Job that waits for a *stopped event after a suspend operation on a thread.
	 *
	 * If the suspend operation receives its corresponding *stopped event in time,
	 * the job will mark the RM with a success status.  If the event is not received
	 * before the timeout, the job will fail the request monitor.
	 *
	 * @since 4.5
	 */
	protected class MonitorSuspendJob extends Job {
		// Bug 310274.  Until we have a preference to configure timeouts,
		// we need a large enough default timeout to accommodate slow
		// remote sessions.
		private final static int TIMEOUT_DEFAULT_VALUE = 5000;

		private final RequestMonitor fRequestMonitor;
		private final IMIExecutionDMContext fThread;

		public MonitorSuspendJob(IMIExecutionDMContext dmc, int timeout, RequestMonitor rm) {
			super("Suspend monitor job."); //$NON-NLS-1$
			setSystem(true);
			fThread = dmc;
			fRequestMonitor = rm;

			if (timeout <= 0) {
				timeout = TIMEOUT_DEFAULT_VALUE; // default of 5 seconds
			}

			// Register to listen for the stopped event
			getSession().addServiceEventListener(this, null);

			schedule(timeout);
		}

		/**
		 * Cleanup job and cancel it.
		 * This method is required because super.canceling() is only called
		 * if the job is actually running.
		 */
		public boolean cleanAndCancel() {
			if (getExecutor().isInExecutorThread()) {
				getSession().removeServiceEventListener(this);
			} else {
				getExecutor().submit(new DsfRunnable() {
					@Override
					public void run() {
						getSession().removeServiceEventListener(MonitorSuspendJob.this);
					}
				});
			}
			return cancel();
		}

		@DsfServiceEventHandler
		public void eventDispatched(MIStoppedEvent e) {
			if (fThread.equals(e.getDMContext())) {
				// The thread we were waiting for did stop
				if (cleanAndCancel()) {
					fRequestMonitor.done();
				}
			}
		}

		@Override
		protected IStatus run(IProgressMonitor monitor) {
			// This will be called when the timeout is hit and no *stopped event was received
			getExecutor().submit(new DsfRunnable() {
				@Override
				public void run() {
					getSession().removeServiceEventListener(MonitorSuspendJob.this);
					fRequestMonitor.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID,
							IDsfStatusConstants.REQUEST_FAILED, "Suspend operation timeout.", null)); //$NON-NLS-1$
				}
			});
			return Status.OK_STATUS;
		}
	}

	// ------------------------------------------------------------------------
	// Resume
	// ------------------------------------------------------------------------

	@Override
	public void canResume(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) {
		if (fRunControlOperationsEnabled == false) {
			rm.done(false);
			return;
		}

		rm.done(doCanResume(context));
	}

	/** @since 5.0 */
	protected boolean doCanResume(IExecutionDMContext context) {
		// Thread case
		if (context instanceof IMIExecutionDMContext) {
			MIThreadRunState threadState = fThreadRunStates.get(context);
			return (threadState == null) ? false
					: !fTerminated && threadState.fSuspended && !threadState.fResumePending;
		}

		// Process case
		if (context instanceof IMIContainerDMContext) {
			for (IMIExecutionDMContext threadContext : fThreadRunStates.keySet()) {
				if (DMContexts.isAncestorOf(threadContext, context)) {
					if (doCanResume(threadContext)) {
						return true;
					}
				}
			}
			return false;
		}

		// Default case
		return false;
	}

	@Override
	public void resume(IExecutionDMContext context, final RequestMonitor rm) {

		assert context != null;

		// Thread case
		IMIExecutionDMContext thread = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
		if (thread != null) {
			doResume(thread, rm);
			return;
		}

		// Container case
		IMIContainerDMContext container = DMContexts.getAncestorOfType(context, IMIContainerDMContext.class);
		if (container != null) {
			doResume(container, rm);
			return;
		}

		// Default case
		rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Invalid context type.", null)); //$NON-NLS-1$
		rm.done();
	}

	private void doResume(IMIExecutionDMContext context, final RequestMonitor rm) {
		if (!doCanResume(context)) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
					"Given context: " + context + ", is already running.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		final MIThreadRunState threadState = fThreadRunStates.get(context);
		if (threadState == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
					"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		threadState.fResumePending = true;
		fConnection.queueCommand(fCommandFactory.createMIExecContinue(context),
				new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
					@Override
					protected void handleFailure() {
						threadState.fResumePending = false;
						super.handleFailure();
					}
				});
	}

	/** @since 5.0 */
	protected void doResume(IMIContainerDMContext context, final RequestMonitor rm) {
		if (!doCanResume(context)) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
					"Given context: " + context + ", is already running.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		String groupId = context.getGroupId();
		fConnection.queueCommand(fCommandFactory.createMIExecContinue(context, groupId),
				new DataRequestMonitor<MIInfo>(getExecutor(), rm));
	}

	// ------------------------------------------------------------------------
	// Step
	// ------------------------------------------------------------------------

	@Override
	public boolean isStepping(IExecutionDMContext context) {

		// If it's a thread, just look it up
		if (context instanceof IMIExecutionDMContext) {
			MIThreadRunState threadState = fThreadRunStates.get(context);
			return (threadState == null) ? false : !fTerminated && threadState.fStepping;
		}

		// Default case
		return false;
	}

	@Override
	public void canStep(final IExecutionDMContext context, StepType stepType, final DataRequestMonitor<Boolean> rm) {
		if (fRunControlOperationsEnabled == false) {
			rm.done(false);
			return;
		}

		// If it's a thread, just look it up
		if (context instanceof IMIExecutionDMContext) {
			if (stepType == StepType.STEP_RETURN) {
				// A step return will always be done in the top stack frame.
				// If the top stack frame is the only stack frame, it does not make sense
				// to do a step return since GDB will reject it.
				MIStack stackService = getServicesTracker().getService(MIStack.class);
				if (stackService != null) {
					// Check that the stack is at least two deep.
					stackService.getStackDepth(context, 2, new DataRequestMonitor<Integer>(getExecutor(), rm) {
						@Override
						public void handleCompleted() {
							if (isSuccess() && getData() == 1) {
								rm.done(false);
							} else {
								canResume(context, rm);
							}
						}
					});
					return;
				}
			}

			canResume(context, rm);
			return;
		}

		// If it's a container, then we don't want to step it
		rm.done(false);
	}

	@Override
	public void step(IExecutionDMContext context, StepType stepType, final RequestMonitor rm) {
		step(context, stepType, true, rm);
	}

	private void step(IExecutionDMContext context, StepType stepType, boolean checkCanResume, final RequestMonitor rm) {

		assert context != null;

		IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
		if (dmc == null) {
			rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
					"Given context: " + context + " is not an MI execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			return;
		}

		if (checkCanResume && !doCanResume(dmc)) {
			rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$
			return;
		}

		final MIThreadRunState threadState = fThreadRunStates.get(context);
		if (threadState == null) {
			rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
					"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			return;
		}

		ICommand<MIInfo> cmd = null;
		switch (stepType) {
		case STEP_INTO:
			cmd = fCommandFactory.createMIExecStep(dmc);
			break;
		case STEP_OVER:
			cmd = fCommandFactory.createMIExecNext(dmc);
			break;
		case STEP_RETURN:
			// The -exec-finish command operates on the selected stack frame, but here we always
			// want it to operate on the stop stack frame. So we manually create a top-frame
			// context to use with the MI command.
			// We get a local instance of the stack service because the stack service can be shut
			// down before the run control service is shut down. So it is possible for the
			// getService() request below to return null.
			MIStack stackService = getServicesTracker().getService(MIStack.class);
			if (stackService != null) {
				IFrameDMContext topFrameDmc = stackService.createFrameDMContext(dmc, 0);
				cmd = fCommandFactory.createMIExecFinish(topFrameDmc);
			} else {
				rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
						"Cannot create context for command, stack service not available.", null)); //$NON-NLS-1$
				return;
			}
			break;
		case INSTRUCTION_STEP_INTO:
			cmd = fCommandFactory.createMIExecStepInstruction(dmc);
			break;
		case INSTRUCTION_STEP_OVER:
			cmd = fCommandFactory.createMIExecNextInstruction(dmc);
			break;
		default:
			rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Given step type not supported", //$NON-NLS-1$
					null));
			return;
		}

		threadState.fResumePending = true;
		threadState.fStepping = true;
		fConnection.queueCommand(cmd, new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
			@Override
			public void handleFailure() {
				threadState.fResumePending = false;
				threadState.fStepping = false;

				super.handleFailure();
			}
		});
	}

	// ------------------------------------------------------------------------
	// Run to line
	// ------------------------------------------------------------------------

	private void runToLocation(final IExecutionDMContext context, final String location, final boolean skipBreakpoints,
			final RequestMonitor rm) {

		assert context != null;

		final IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
		if (dmc == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
					"Given context: " + context + " is not an MI execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		if (!doCanResume(dmc)) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$
			rm.done();
			return;
		}

		MIThreadRunState threadState = fThreadRunStates.get(dmc);
		if (threadState == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
					"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(context, IBreakpointsTargetDMContext.class);
		fConnection.queueCommand(
				fCommandFactory.createMIBreakInsert(bpDmc, true, false, null, 0, location, dmc.getThreadId()),
				new DataRequestMonitor<MIBreakInsertInfo>(getExecutor(), rm) {
					@Override
					public void handleSuccess() {
						// We must set are RunToLineActiveOperation *before* we do the resume
						// or else we may get the stopped event, before we have set this variable.
						String bpId = getData().getMIBreakpoints()[0].getNumber();
						String addr = getData().getMIBreakpoints()[0].getAddress();
						fRunToLineActiveOperation = new RunToLineActiveOperation(dmc, bpId, location, addr,
								skipBreakpoints);

						resume(dmc, new RequestMonitor(getExecutor(), rm) {
							@Override
							public void handleFailure() {
								IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(
										fRunToLineActiveOperation.getThreadContext(),
										IBreakpointsTargetDMContext.class);
								String bpId = fRunToLineActiveOperation.getBreakpointId();

								fConnection.queueCommand(
										fCommandFactory.createMIBreakDelete(bpDmc, new String[] { bpId }),
										new DataRequestMonitor<MIInfo>(getExecutor(), null));
								fRunToLineActiveOperation = null;
								fStepInToSelectionActiveOperation = null;

								super.handleFailure();
							}
						});
					}
				});

	}

	// ------------------------------------------------------------------------
	// Step into Selection
	// ------------------------------------------------------------------------
	private void stepIntoSelection(final IExecutionDMContext context, final int baseLine, final String baseLineLocation,
			final boolean skipBreakpoints, final IFunctionDeclaration targetFunction, final RequestMonitor rm) {

		assert context != null;

		final IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
		if (dmc == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
					"Given context: " + context + " is not an MI execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		if (!doCanResume(dmc)) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$
			rm.done();
			return;
		}

		MIThreadRunState threadState = fThreadRunStates.get(dmc);
		if (threadState == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
					"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		if (threadState.fLatestEvent == null || !(threadState.fLatestEvent instanceof SuspendedEvent)) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
					"Given context: " + context + " invalid suspended event.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		SuspendedEvent suspendedEvent = (SuspendedEvent) threadState.fLatestEvent;
		final MIFrame currentFrame = suspendedEvent.getMIEvent().getFrame();
		if (currentFrame == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
					"Given event: " + suspendedEvent + " invalid frame in suspended event.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		getStackDepth(dmc, new DataRequestMonitor<Integer>(getExecutor(), rm) {
			@Override
			public void handleSuccess() {
				if (getData() != null) {
					final int framesSize = getData().intValue();

					// make sure the operation is removed upon
					// failure detection
					final RequestMonitor rms = new RequestMonitor(getExecutor(), rm) {
						@Override
						protected void handleFailure() {
							fStepInToSelectionActiveOperation = null;
							super.handleFailure();
						}
					};

					if ((currentFrame.getFile() + ":" + currentFrame.getLine()).endsWith(baseLineLocation)) { //$NON-NLS-1$
						// Save the step into selection information
						fStepInToSelectionActiveOperation = new StepIntoSelectionActiveOperation(dmc, baseLine,
								targetFunction, framesSize, currentFrame);
						// Ready to step into a function selected
						// within a current line
						step(dmc, StepType.STEP_INTO, rms);
					} else {
						// Save the step into selection information
						fStepInToSelectionActiveOperation = new StepIntoSelectionActiveOperation(dmc, baseLine,
								targetFunction, framesSize, null);
						// Pointing to a line different than the current line
						// Needs to RunToLine before stepping to the selection
						runToLocation(dmc, baseLineLocation, skipBreakpoints, rms);
					}
				} else {
					rm.done();
				}
			}
		});
	}

	// ------------------------------------------------------------------------
	// Resume at location
	// ------------------------------------------------------------------------
	private void resumeAtLocation(IExecutionDMContext context, String location, RequestMonitor rm) {
		assert context != null;

		final IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
		if (dmc == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
					"Given context: " + context + " is not an MI execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		if (!doCanResume(dmc)) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$
			rm.done();
			return;
		}

		final MIThreadRunState threadState = fThreadRunStates.get(dmc);
		if (threadState == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
					"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		threadState.fResumePending = true;
		fConnection.queueCommand(fCommandFactory.createMIExecJump(dmc, location),
				new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
					@Override
					protected void handleFailure() {
						threadState.fResumePending = false;
						super.handleFailure();
					}
				});
	}
	// ------------------------------------------------------------------------
	// Support functions
	// ------------------------------------------------------------------------

	@Override
	public void getExecutionContexts(final IContainerDMContext containerDmc,
			final DataRequestMonitor<IExecutionDMContext[]> rm) {
		IMIProcesses procService = getServicesTracker().getService(IMIProcesses.class);
		procService.getProcessesBeingDebugged(containerDmc, new DataRequestMonitor<IDMContext[]>(getExecutor(), rm) {
			@Override
			protected void handleSuccess() {
				if (getData() instanceof IExecutionDMContext[]) {
					rm.setData((IExecutionDMContext[]) getData());
				} else {
					rm.setStatus(
							new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid contexts", null)); //$NON-NLS-1$
				}
				rm.done();
			}
		});
	}

	@Override
	public void getExecutionData(IExecutionDMContext dmc, DataRequestMonitor<IExecutionDMData> rm) {
		MIThreadRunState threadState = fThreadRunStates.get(dmc);
		if (threadState == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
					"Given context: " + dmc + " is not a recognized execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
			rm.done();
			return;
		}

		if (dmc instanceof IMIExecutionDMContext) {
			rm.setData(new ExecutionData(threadState.fSuspended ? threadState.fStateChangeReason : null,
					threadState.fStateChangeDetails));
		} else {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
					"Given context: " + dmc + " is not a recognized execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
		}
		rm.done();
	}

	private IMIExecutionDMContext createMIExecutionContext(IContainerDMContext container, String threadId) {
		IMIProcesses procService = getServicesTracker().getService(IMIProcesses.class);

		IProcessDMContext procDmc = DMContexts.getAncestorOfType(container, IProcessDMContext.class);

		IThreadDMContext threadDmc = null;
		if (procDmc != null) {
			// For now, reuse the threadId as the OSThreadId
			threadDmc = procService.createThreadContext(procDmc, threadId);
		}

		return procService.createExecutionContext(container, threadDmc, threadId);
	}

	private void updateThreadState(IMIExecutionDMContext context, ResumedEvent event) {
		StateChangeReason reason = event.getReason();
		boolean isStepping = reason.equals(StateChangeReason.STEP);
		MIThreadRunState threadState = fThreadRunStates.get(context);
		if (threadState == null) {
			threadState = new MIThreadRunState();
			fThreadRunStates.put(context, threadState);
		}
		threadState.fSuspended = false;
		threadState.fResumePending = false;
		threadState.fStateChangeReason = reason;
		threadState.fStateChangeDetails = null; // we have no details of interest for a resume
		threadState.fStepping = isStepping;
		threadState.fLatestEvent = event;
	}

	private void updateThreadState(IMIExecutionDMContext context, SuspendedEvent event) {
		StateChangeReason reason = event.getReason();
		MIThreadRunState threadState = fThreadRunStates.get(context);
		if (threadState == null) {
			threadState = new MIThreadRunState();
			fThreadRunStates.put(context, threadState);
		}
		threadState.fSuspended = true;
		threadState.fResumePending = false;
		threadState.fStepping = false;
		threadState.fStateChangeReason = reason;
		threadState.fStateChangeDetails = event.getDetails();
		threadState.fLatestEvent = event;
	}

	/* ******************************************************************************
	 * Section to support making operations even when the target is unavailable.
	 *
	 * Although one would expect to be able to make commands all the time when
	 * in non-stop mode, it turns out that GDB has trouble with some commands
	 * like breakpoints.  The safe way to do it is to make sure we have at least
	 * one thread suspended.
	 *
	 * Basically, we must make sure one thread is suspended before making
	 * certain operations (currently breakpoints).  If that is not the case, we must
	 * first suspend one thread, then perform the specified operations,
	 * and finally resume that thread..
	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=242943
	 * and https://bugs.eclipse.org/bugs/show_bug.cgi?id=282273
	 *
	 * Note that for multi-process, we need to interrupt all processes
	 * that share the same binary before doing a breakpoint operation on any of
	 * those processes.  For simplicity, the logic below interrupts one thread of
	 * every process being debugged, without differentiating on the executable.
	 * Although it may seem wasteful to interrupt all processes when not necessary,
	 * in truth it is not so much; when making a breakpoint operation in Eclipse, that
	 * operation is propagated to all processes anyway, so they will all need to be
	 * interrupted.  The case where we are wasteful is when we start or stop debugging
	 * a process (starting a process, attaching to one, auto-attaching to one,
	 * detaching from one, terminating one); in those cases, we only want to apply the
	 * breakpoint operation to that one process and any other using the same binary.
	 * The wastefulness is not such a big deal for that case, and is worth the simpler
	 * solution.
	 * Of course, it can always be improved later on.
	 * See http://bugs.eclipse.org/337893
	 * ******************************************************************************/

	/**
	 * Utility class to store the parameters of the executeWithTargetAvailable() operations.
	 * @since 4.0
	 */
	protected static class TargetAvailableOperationInfo {
		public IDMContext ctx;
		public Sequence.Step[] steps;
		public RequestMonitor rm;

		public TargetAvailableOperationInfo(IDMContext ctx, Step[] steps, RequestMonitor rm) {
			super();
			this.ctx = ctx;
			this.steps = steps;
			this.rm = rm;
		}
	}

	// The set of threads that we will actually be suspended to make the containers suspended.
	private Set<IMIExecutionDMContext> fExecutionDmcToSuspendSet = new HashSet<>();

	// Do we currently have an executeWithTargetAvailable() operation ongoing?
	private boolean fOngoingOperation;
	// Are we currently executing steps passed into executeWithTargetAvailable()?
	// This allows us to know if we can add more steps to execute or if we missed
	// our opportunity
	private boolean fCurrentlyExecutingSteps;

	// MultiRequestMonitor that allows us to track all the different steps we are
	// executing.  Once all steps are executed, we can complete this MultiRM and
	// allow the global sequence to continue.
	// Note that we couldn't use a CountingRequestMonitor because that type of RM
	// needs to know in advance how many subRms it will track; the MultiRM allows us
	// to receive more steps to execute continuously, and be able to update the MultiRM.
	private MultiRequestMonitor<RequestMonitor> fExecuteQueuedOpsStepMonitor;
	// The number of batches of steps that are still being executing for potentially
	// concurrent executeWithTargetAvailable() operations.
	// Once this gets to zero, we know we have executed all the steps we were aware of
	// and we can complete the operation.
	private int fNumStepsStillExecuting;
	// Queue of executeWithTargetAvailable() operations that need to be processed.
	private LinkedList<TargetAvailableOperationInfo> fOperationsPending = new LinkedList<>();

	/**
	 * Returns whether there is currently an ExecuteWithTargetAvailable() operation ongoing.
	 * @since 4.0
	 */
	protected boolean isTargetAvailableOperationOngoing() {
		return fOngoingOperation;
	}

	/** @since 4.0 */
	protected void setTargetAvailableOperationOngoing(boolean ongoing) {
		fOngoingOperation = ongoing;
	}

	/**
	 * Returns whether we are current in the process of executing the steps
	 * that were passed to ExecuteWithTargetAvailable().
	 * When this value is true, we can send more steps to be executed.
	 * @since 4.0
	 */
	protected boolean isCurrentlyExecutingSteps() {
		return fCurrentlyExecutingSteps;
	}

	/** @since 4.0 */
	protected void setCurrentlyExecutingSteps(boolean executing) {
		fCurrentlyExecutingSteps = executing;
	}

	/**
	 * Returns the requestMonitor that will be run once all steps sent to
	 * ExecuteWithTargetAvailable() have been executed.
	 * @since 4.0
	 */
	protected MultiRequestMonitor<RequestMonitor> getExecuteQueuedStepsRM() {
		return fExecuteQueuedOpsStepMonitor;
	}

	/** @since 4.0 */
	protected void setExecuteQueuedStepsRM(MultiRequestMonitor<RequestMonitor> rm) {
		fExecuteQueuedOpsStepMonitor = rm;
	}

	/**
	 * Returns the number of batches of steps sent to ExecuteWithTargetAvailable()
	 * that are still executing.  Once this number reaches zero, we can complete
	 * the overall ExecuteWithTargetAvailable() operation.
	 * @since 4.0
	 */
	protected int getNumStepsStillExecuting() {
		return fNumStepsStillExecuting;
	}

	/** @since 4.0 */
	protected void setNumStepsStillExecuting(int num) {
		fNumStepsStillExecuting = num;
	}

	/**
	 * Returns the queue of executeWithTargetAvailable() operations that still need to be processed
	 * @since 4.0
	 */
	protected LinkedList<TargetAvailableOperationInfo> getOperationsPending() {
		return fOperationsPending;
	}

	/**
	 * This method takes care of executing a batch of steps that were passed to
	 * ExecuteWithTargetAvailable().  The method is used to track the progress
	 * of all these batches of steps, so that we know exactly when all of them
	 * have been completed and the global sequence can be completed.
	 * @since 4.0
	 */
	protected void executeSteps(final TargetAvailableOperationInfo info) {
		fNumStepsStillExecuting++;

		// This RM propagates any error to the original rm of the actual steps.
		// Even in case of errors for these steps, we want to continue the overall sequence
		RequestMonitor stepsRm = new ImmediateRequestMonitor() {
			@Override
			protected void handleCompleted() {
				info.rm.setStatus(getStatus());
				// It is important to call rm.done() right away.
				// This is because some other operation we are performing might be waiting
				// for this one to be done.  If we try to wait for the entire sequence to be
				// done, then we will never finish because one monitor will never show as
				// done, waiting for the second one.
				info.rm.done();

				fExecuteQueuedOpsStepMonitor.requestMonitorDone(this);
				fNumStepsStillExecuting--;
				if (fNumStepsStillExecuting == 0) {
					fExecuteQueuedOpsStepMonitor.doneAdding();
				}
			}
		};

		fExecuteQueuedOpsStepMonitor.add(stepsRm);

		getExecutor().execute(new Sequence(getExecutor(), stepsRm) {
			@Override
			public Step[] getSteps() {
				return info.steps;
			}
		});
	}

	/**
	 * @since 3.0
	 */
	@Override
	public void executeWithTargetAvailable(IDMContext ctx, final Sequence.Step[] steps, final RequestMonitor rm) {
		if (!fOngoingOperation) {
			// We are the first operation of this kind currently requested
			// so we need to start the sequence
			fOngoingOperation = true;

			// We always go through our queue, even if we only have a single call to this method
			fOperationsPending.add(new TargetAvailableOperationInfo(ctx, steps, rm));

			// Steps that need to be executed to perform the operation
			final Step[] sequenceSteps = new Step[] { new IsTargetAvailableStep(ctx), new MakeTargetAvailableStep(),
					new ExecuteQueuedOperationsStep(), new RestoreTargetStateStep(), };

			// Once all the sequence is completed, we need to see if we have received
			// another request that we now need to process
			RequestMonitor sequenceCompletedRm = new RequestMonitor(getExecutor(), null) {
				@Override
				protected void handleSuccess() {
					fOngoingOperation = false;

					if (!fOperationsPending.isEmpty()) {
						// Darn, more operations came in.  Trigger their processing
						// by calling executeWithTargetAvailable() on the last one
						TargetAvailableOperationInfo info = fOperationsPending.removeLast();
						executeWithTargetAvailable(info.ctx, info.steps, info.rm);
					}
					// no other rm.done() needs to be called, they have all been handled already
				}

				@Override
				protected void handleFailure() {
					// If the sequence failed, we have to give up on the operation(s).
					// If we don't, we risk an infinite loop where we try, over and over
					// to perform an operation that keeps on failing.
					fOngoingOperation = false;

					// Complete each rm of the cancelled operations
					while (!fOperationsPending.isEmpty()) {
						RequestMonitor rm = fOperationsPending.poll().rm;
						rm.setStatus(getStatus());
						rm.done();
					}
					super.handleFailure();
				}
			};

			getExecutor().execute(new Sequence(getExecutor(), sequenceCompletedRm) {
				@Override
				public Step[] getSteps() {
					return sequenceSteps;
				}
			});
		} else {
			// We are currently already executing such an operation
			// If we are still in the process of executing steps, let's include this new set of steps.
			// This is important because some steps may depend on these new ones.
			if (fCurrentlyExecutingSteps) {
				executeSteps(new TargetAvailableOperationInfo(ctx, steps, rm));
			} else {
				// Too late to execute the new steps, so queue them for later
				fOperationsPending.add(new TargetAvailableOperationInfo(ctx, steps, rm));
			}
		}
	}

	/**
	 * This part of the sequence looks for all threads that will need to be suspended.
	 * @since 3.0
	 */
	protected class IsTargetAvailableStep extends Sequence.Step {
		final IDMContext fCtx;

		public IsTargetAvailableStep(IDMContext ctx) {
			fCtx = ctx;
		}

		private void getThreadToSuspend(IContainerDMContext containerDmc, final RequestMonitor rm) {
			// If the process is running, get its first thread which we will need to suspend
			fProcessService.getProcessesBeingDebugged(containerDmc, new ImmediateDataRequestMonitor<IDMContext[]>(rm) {
				@Override
				protected void handleSuccess() {
					IDMContext[] threads = getData();
					if (threads != null && threads.length > 0) {
						// Choose the first thread as the one to suspend
						fExecutionDmcToSuspendSet.add((IMIExecutionDMContext) threads[0]);
					}
					rm.done();
				}
			});
		}

		@Override
		public void execute(final RequestMonitor rm) {
			// Clear any old data before we start
			fExecutionDmcToSuspendSet.clear();

			// Get all processes being debugged to see which one are running
			// and need to be interrupted
			fProcessService.getProcessesBeingDebugged(fConnection.getContext(),
					new ImmediateDataRequestMonitor<IDMContext[]>(rm) {
						@Override
						protected void handleSuccess() {
							assert getData() != null;

							if (getData().length == 0) {
								// Happens at startup, starting with GDB 7.0.
								// This means the target is available.  Nothing to do.
								rm.done();
							} else {
								// Go through every process to see if it is running.
								// If it is running, get its first thread so we can interrupt it.
								CountingRequestMonitor crm = new ImmediateCountingRequestMonitor(rm);

								int numThreadsToSuspend = 0;
								for (IDMContext dmc : getData()) {
									IContainerDMContext containerDmc = (IContainerDMContext) dmc;
									if (!isSuspended(containerDmc)) {
										numThreadsToSuspend++;
										getThreadToSuspend(containerDmc, crm);
									}
								}
								crm.setDoneCount(numThreadsToSuspend);
							}
						}
					});
		}
	}

	/**
	 * Suspended all the threads we have selected.
	 * @since 3.0
	 */
	protected class MakeTargetAvailableStep extends Sequence.Step {

		/* public constructor required, so upper classes can override executeWithTargetAvailable */
		/** @since 4.5 */
		public MakeTargetAvailableStep() {
		}

		@Override
		public void execute(final RequestMonitor rm) {
			// Interrupt every first thread of the running processes
			CountingRequestMonitor crm = new ImmediateCountingRequestMonitor(rm);
			crm.setDoneCount(fExecutionDmcToSuspendSet.size());

			for (final IMIExecutionDMContext thread : fExecutionDmcToSuspendSet) {
				assert !fDisableNextRunningEventDmcSet.contains(thread);
				assert !fDisableNextSignalEventDmcSet.contains(thread);

				// Don't broadcast the next stopped signal event
				fDisableNextSignalEventDmcSet.add(thread);

				suspend(thread, new ImmediateRequestMonitor(crm) {
					@Override
					protected void handleFailure() {
						// We weren't able to suspend, so abort the operation
						fDisableNextSignalEventDmcSet.remove(thread);
						super.handleFailure();
					}
				});
			}
		}

		@Override
		public void rollBack(RequestMonitor rm) {
			Sequence.Step restoreStep = new RestoreTargetStateStep();
			restoreStep.execute(rm);
		}
	}

	/**
	 * This step of the sequence takes care of executing all the steps that
	 * were passed to ExecuteWithTargetAvailable().
	 * @since 4.0
	 */
	protected class ExecuteQueuedOperationsStep extends Sequence.Step {

		/* public constructor required, so upper classes can override executeWithTargetAvailable */
		/** @since 4.5 */
		public ExecuteQueuedOperationsStep() {
		}

		@Override
		public void execute(final RequestMonitor rm) {
			fCurrentlyExecutingSteps = true;

			// It is important to use an ImmediateExecutor for this RM, to make sure we don't risk getting a new
			// call to ExecuteWithTargetAvailable() when we just finished executing the steps.
			fExecuteQueuedOpsStepMonitor = new MultiRequestMonitor<RequestMonitor>(ImmediateExecutor.getInstance(),
					rm) {
				@Override
				protected void handleCompleted() {
					assert fOperationsPending.size() == 0;

					// We don't handle errors here.  Instead, we have already propagated any
					// errors to each rm for each set of steps

					fCurrentlyExecutingSteps = false;
					// Continue the sequence
					rm.done();
				}
			};
			// Tell the RM that we need to confirm when we are done adding sub-rms
			fExecuteQueuedOpsStepMonitor.requireDoneAdding();

			// All pending operations are independent of each other so we can
			// run them concurrently.
			while (!fOperationsPending.isEmpty()) {
				executeSteps(fOperationsPending.poll());
			}
		}
	}

	/**
	 * If the sequence had to interrupt the execution context of interest,
	 * this step will resume it again to reach the same state as when we started.
	 * @since 3.0
	 */
	protected class RestoreTargetStateStep extends Sequence.Step {

		/* public constructor required, so upper classes can override executeWithTargetAvailable */
		/** @since 4.5 */
		public RestoreTargetStateStep() {
		}

		@Override
		public void execute(final RequestMonitor rm) {
			// Resume every thread we had interrupted
			CountingRequestMonitor crm = new ImmediateCountingRequestMonitor(rm);
			crm.setDoneCount(fExecutionDmcToSuspendSet.size());

			for (final IMIExecutionDMContext thread : fExecutionDmcToSuspendSet) {

				assert !fDisableNextRunningEventDmcSet.contains(thread);
				fDisableNextRunningEventDmcSet.add(thread);

				// Can't use the resume() call because we 'silently' stopped
				// so resume() will not know we are actually stopped
				fConnection.queueCommand(fCommandFactory.createMIExecContinue(thread),
						new ImmediateDataRequestMonitor<MIInfo>(crm) {
							@Override
							protected void handleSuccess() {
								fSilencedSignalEventMap.remove(thread);
								super.handleSuccess();
							}

							@Override
							protected void handleFailure() {
								// Darn, we're unable to restart the target.  Must cleanup!
								fDisableNextRunningEventDmcSet.remove(thread);

								// We must also sent the Stopped event that we had kept silent
								MIStoppedEvent event = fSilencedSignalEventMap.remove(thread);
								if (event != null) {
									eventDispatched(event);
								} else {
									// Maybe the stopped event didn't arrive yet.
									// We don't want to silence it anymore
									fDisableNextSignalEventDmcSet.remove(thread);
								}

								super.handleFailure();
							}
						});
			}
		}
	}

	/* ******************************************************************************
	 * End of section to support operations even when the target is unavailable.
	 * ******************************************************************************/

	///////////////////////////////////////////////////////////////////////////
	// Event handlers
	///////////////////////////////////////////////////////////////////////////

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(final MIRunningEvent e) {
		if (fDisableNextRunningEventDmcSet.remove(e.getDMContext())) {
			// Don't broadcast the running event
			return;
		}

		MIThreadRunState threadState = fThreadRunStates.get(e.getDMContext());
		if (threadState != null && threadState.fLatestEvent instanceof IResumedDMEvent) {
			// Ignore multiple running events in a row.  They will only slow down the UI
			// for no added value.
			return;
		}

		if (fRunToLineActiveOperation == null && fStepInToSelectionActiveOperation == null) {
			// No special case here, i.e. send notification
			getSession().dispatchEvent(new ResumedEvent(e.getDMContext(), e), getProperties());
		} else {
			// Either RunToLine or StepIntoSelection operations are active
			if (threadState == null || threadState.fLatestEvent instanceof ISuspendedDMEvent) {
				// Need to send out Running event notification, only once per operation, then a stop event is expected
				// at the end of the operation
				getSession().dispatchEvent(new ResumedEvent(e.getDMContext(), e), getProperties());
			}
		}
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(final MIStoppedEvent e) {
		// A disabled signal event is due to interrupting the target
		// to set a breakpoint.  This can happen during a run-to-line
		// or step-into operation, so we need to check it first.
		IMIExecutionDMContext threadDmc = DMContexts.getAncestorOfType(e.getDMContext(), IMIExecutionDMContext.class);
		if (e instanceof MISignalEvent && fDisableNextSignalEventDmcSet.remove(threadDmc)) {
			fSilencedSignalEventMap.put(threadDmc, e);
			// Don't broadcast the stopped event
			return;
		}

		if (processRunToLineStoppedEvent(e)) {
			// If RunToLine is not completed
			return;
		}

		if (!processStepIntoSelection(e)) {
			//Step into Selection is not in progress
			broadcastStop(e);
		}
	}

	private void broadcastStop(final MIStoppedEvent e) {
		IDMEvent<?> event = null;
		MIBreakpointDMContext bp = null;
		if (e instanceof MIBreakpointHitEvent) {
			String bpId = ((MIBreakpointHitEvent) e).getNumber();
			IBreakpointsTargetDMContext bpsTarget = DMContexts.getAncestorOfType(e.getDMContext(),
					IBreakpointsTargetDMContext.class);
			if (bpsTarget != null && !bpId.isEmpty()) {
				bp = new MIBreakpointDMContext(getSession().getId(), new IDMContext[] { bpsTarget }, bpId);
				event = new BreakpointHitEvent(e.getDMContext(), (MIBreakpointHitEvent) e, bp);
			}
		}
		if (event == null) {
			event = new SuspendedEvent(e.getDMContext(), e);
		}

		getSession().dispatchEvent(event, getProperties());
	}

	private boolean processStepIntoSelection(final MIStoppedEvent e) {
		if (fStepInToSelectionActiveOperation == null) {
			return false;
		}

		// First check if it is the right thread that stopped
		final IMIExecutionDMContext threadDmc = DMContexts.getAncestorOfType(e.getDMContext(),
				IMIExecutionDMContext.class);
		if (fStepInToSelectionActiveOperation.getThreadContext().equals(threadDmc)) {
			final MIFrame frame = e.getFrame();

			assert (fRunToLineActiveOperation == null);

			if (fStepInToSelectionActiveOperation.getRunToLineFrame() == null) {
				assert (fStepInToSelectionActiveOperation.getLine() == frame.getLine());
				// Shall now be at the runToline location
				fStepInToSelectionActiveOperation.setRunToLineFrame(frame);
			}

			// Step - Not at the right place just yet
			// Initiate an async call chain parent
			getStackDepth(threadDmc, new DataRequestMonitor<Integer>(getExecutor(), null) {
				private int originalStackDepth = fStepInToSelectionActiveOperation.getOriginalStackDepth();

				@Override
				protected void handleSuccess() {
					int frameDepth = getStackDepth();

					if (frameDepth > originalStackDepth) {
						//shall be true as this is using stepinto step type vs instruction stepinto
						assert (frameDepth == originalStackDepth + 1);

						// Check for a match
						if (StepIntoSelectionUtils.sameSignature(frame, fStepInToSelectionActiveOperation)) {
							// Hit !!
							stopStepIntoSelection(e);
							return;
						}

						// Located deeper in the stack, Shall continue step / search
						// Step return
						continueStepping(e, StepType.STEP_RETURN);
					} else if (frameDepth == originalStackDepth) {
						// Continue step / search as long as
						// this is the starting base line for the search
						String currentLocation = frame.getFile() + ":" + frame.getLine(); //$NON-NLS-1$
						String searchLineLocation = fStepInToSelectionActiveOperation.getFileLocation();
						if (currentLocation.equals(searchLineLocation)) {
							continueStepping(e, StepType.STEP_INTO);
						} else {
							// We have moved to a line
							// different from the base
							// search line i.e. missed the
							// target function !!
							StepIntoSelectionUtils.missedSelectedTarget(fStepInToSelectionActiveOperation);
							stopStepIntoSelection(e);
						}
					} else {
						// missed the target point
						StepIntoSelectionUtils.missedSelectedTarget(fStepInToSelectionActiveOperation);
					}
				}

				@Override
				protected void handleFailure() {
					// log error
					if (getStatus() != null) {
						GdbPlugin.getDefault().getLog().log(getStatus());
					}

					stopStepIntoSelection(e);
				}

				private int getStackDepth() {
					Integer stackDepth = null;
					if (isSuccess() && getData() != null) {
						stackDepth = getData();
						// This is the base frame, the original stack depth shall be updated
						if (frame == fStepInToSelectionActiveOperation.getRunToLineFrame()) {
							fStepInToSelectionActiveOperation.setOriginalStackDepth(stackDepth);
							originalStackDepth = stackDepth;
						}
					}

					if (stackDepth == null) {
						// Unsuccessful resolution of stack depth, default to same stack depth to detect a change of line within the original frame
						return fStepInToSelectionActiveOperation.getOriginalStackDepth();
					}

					return stackDepth.intValue();
				}
			});

			//Processing step into selection
			return true;
		}

		//The thread related to this event is outside the scope of the step into selection context
		return false;
	}

	private void stopStepIntoSelection(final MIStoppedEvent e) {
		fStepInToSelectionActiveOperation = null;
		// Need to broadcast the stop
		broadcastStop(e);
	}

	private void continueStepping(final MIStoppedEvent event, StepType steptype) {
		step(fStepInToSelectionActiveOperation.getThreadContext(), steptype, false,
				new RequestMonitor(getExecutor(), null) {
					@Override
					protected void handleFailure() {
						// log error
						if (getStatus() != null) {
							GdbPlugin.getDefault().getLog().log(getStatus());
						}

						stopStepIntoSelection(event);
					}
				});
	}

	private boolean processRunToLineStoppedEvent(final MIStoppedEvent e) {
		if (fRunToLineActiveOperation == null) {
			return false;
		}

		// First check if it is the right thread that stopped
		IMIExecutionDMContext threadDmc = DMContexts.getAncestorOfType(e.getDMContext(), IMIExecutionDMContext.class);
		if (fRunToLineActiveOperation.getThreadContext().equals(threadDmc)) {
			String bpId = ""; //$NON-NLS-1$
			if (e instanceof MIBreakpointHitEvent) {
				bpId = ((MIBreakpointHitEvent) e).getNumber();
			}

			String fileLocation = e.getFrame().getFile() + ":" + e.getFrame().getLine(); //$NON-NLS-1$
			String addrLocation = e.getFrame().getAddress();

			// Here we check three different things to see if we are stopped at the right place
			// 1- The actual location in the file.  But this does not work for breakpoints that
			//    were set on non-executable lines
			// 2- The address where the breakpoint was set.  But this does not work for breakpoints
			//    that have multiple addresses (GDB returns <MULTIPLE>.)  I think that is for multi-process
			// 3- The breakpoint id that was hit.  But this does not work if another breakpoint
			//    was also set on the same line because GDB may return that breakpoint as being hit.
			//
			// So this works for the large majority of cases.  The case that won't work is when the user
			// does a runToLine to a line that is non-executable AND has another breakpoint AND
			// has multiple addresses for the breakpoint.  I'm mean, come on!
			if (fileLocation.equals(fRunToLineActiveOperation.getFileLocation())
					|| addrLocation.equals(fRunToLineActiveOperation.getAddrLocation())
					|| bpId.equals(fRunToLineActiveOperation.getBreakpointId())) {
				// We stopped at the right place. All is well.
				// Run to line completed
				fRunToLineActiveOperation = null;
			} else {
				// The right thread stopped but not at the right place yet
				if (fRunToLineActiveOperation.shouldSkipBreakpoints() && e instanceof MIBreakpointHitEvent) {
					fConnection.queueCommand(
							fCommandFactory.createMIExecContinue(fRunToLineActiveOperation.getThreadContext()),
							new DataRequestMonitor<MIInfo>(getExecutor(), null));

					// Continue i.e. Don't send the stop event since we are
					// resuming again.
					return true;
				} else {
					// Stopped for any other reasons. Just remove our temporary one
					// since we don't want it to hit later
					//
					// Note that in Non-stop, we don't cancel a run-to-line when a new
					// breakpoint is inserted. This is because the new breakpoint could
					// be for another thread altogether and should not affect the current thread.
					IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(
							fRunToLineActiveOperation.getThreadContext(), IBreakpointsTargetDMContext.class);

					fConnection.queueCommand(
							fCommandFactory.createMIBreakDelete(bpDmc,
									new String[] { fRunToLineActiveOperation.getBreakpointId() }),
							new DataRequestMonitor<MIInfo>(getExecutor(), null));
					fRunToLineActiveOperation = null;
					fStepInToSelectionActiveOperation = null;
				}
			}
		}

		return false;
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(final MIThreadCreatedEvent e) {
		IContainerDMContext containerDmc = e.getDMContext();
		IMIExecutionDMContext executionCtx = null;
		if (e.getStrId() != null) {
			executionCtx = createMIExecutionContext(containerDmc, e.getStrId());
		}
		getSession().dispatchEvent(new StartedDMEvent(executionCtx, e), getProperties());
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(final MIThreadExitEvent e) {
		IContainerDMContext containerDmc = e.getDMContext();
		IMIExecutionDMContext executionCtx = null;
		if (e.getStrId() != null) {
			executionCtx = createMIExecutionContext(containerDmc, e.getStrId());
		}
		getSession().dispatchEvent(new ExitedDMEvent(executionCtx, e), getProperties());
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(ResumedEvent e) {
		IExecutionDMContext ctx = e.getDMContext();
		if (ctx instanceof IMIExecutionDMContext) {
			updateThreadState((IMIExecutionDMContext) ctx, e);
		}
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(SuspendedEvent e) {
		IExecutionDMContext ctx = e.getDMContext();
		if (ctx instanceof IMIExecutionDMContext) {
			updateThreadState((IMIExecutionDMContext) ctx, e);
		}
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(StartedDMEvent e) {
		IExecutionDMContext executionCtx = e.getDMContext();
		if (executionCtx instanceof IMIExecutionDMContext) {
			if (fThreadRunStates.get(executionCtx) == null) {
				fThreadRunStates.put((IMIExecutionDMContext) executionCtx, new MIThreadRunState());
			}
		}
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(ExitedDMEvent e) {
		fThreadRunStates.remove(e.getDMContext());
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(ICommandControlShutdownDMEvent e) {
		fTerminated = true;
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 *
	 * @since 2.0
	 */
	@DsfServiceEventHandler
	public void eventDispatched(MIInferiorExitEvent e) {
		if (fRunToLineActiveOperation != null) {
			IBreakpointsTargetDMContext bpDmc = DMContexts
					.getAncestorOfType(fRunToLineActiveOperation.getThreadContext(), IBreakpointsTargetDMContext.class);
			String bpId = fRunToLineActiveOperation.getBreakpointId();

			fConnection.queueCommand(fCommandFactory.createMIBreakDelete(bpDmc, new String[] { bpId }),
					new DataRequestMonitor<MIInfo>(getExecutor(), null));
			fRunToLineActiveOperation = null;
		}
		fStepInToSelectionActiveOperation = null;
	}

	@Override
	public void flushCache(IDMContext context) {
		refreshThreadStates();
	}

	/**
	 * Gets the state of each thread from GDB and updates our internal map.
	 * @since 4.1
	 */
	protected void refreshThreadStates() {
		fConnection.queueCommand(fCommandFactory.createMIThreadInfo(fConnection.getContext()),
				new DataRequestMonitor<MIThreadInfoInfo>(getExecutor(), null) {
					@Override
					protected void handleSuccess() {
						MIThread[] threadList = getData().getThreadList();
						for (MIThread thread : threadList) {
							String threadId = thread.getThreadId();
							IMIContainerDMContext containerDmc = fProcessService
									.createContainerContextFromThreadId(fConnection.getContext(), threadId);
							IProcessDMContext processDmc = DMContexts.getAncestorOfType(containerDmc,
									IProcessDMContext.class);
							IThreadDMContext threadDmc = fProcessService.createThreadContext(processDmc, threadId);
							IMIExecutionDMContext execDmc = fProcessService.createExecutionContext(containerDmc,
									threadDmc, threadId);

							MIThreadRunState threadState = fThreadRunStates.get(execDmc);
							if (threadState != null) {
								// We may not know this thread.  This can happen when dealing with a remote
								// where thread events are not reported immediately.
								// However, the -thread-info command we just sent will make
								// GDB send those events.  Therefore, we can just ignore threads we don't
								// know about, and wait for those events.
								if (MIThread.MI_THREAD_STATE_RUNNING.equals(thread.getState())) {
									if (threadState.fSuspended == true) {
										// We missed a resumed event!  Send it now.
										IResumedDMEvent resumedEvent = new ResumedEvent(execDmc, null);
										fConnection.getSession().dispatchEvent(resumedEvent, getProperties());
									}
								} else if (MIThread.MI_THREAD_STATE_STOPPED.equals(thread.getState())) {
									if (threadState.fSuspended == false) {
										// We missed a suspend event!  Send it now.
										ISuspendedDMEvent suspendedEvent = new SuspendedEvent(execDmc, null);
										fConnection.getSession().dispatchEvent(suspendedEvent, getProperties());
									}
								} else {
									assert false : "Invalid thread state: " + thread.getState(); //$NON-NLS-1$
								}
							}
						}
					}
				});
	}

	private void moveToLocation(final IExecutionDMContext context, final String location,
			final Map<String, Object> bpAttributes, final RequestMonitor rm) {

		// first create a temporary breakpoint to stop the execution at
		// the location we are about to jump to
		IBreakpoints bpService = getServicesTracker().getService(IBreakpoints.class);
		IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(context, IBreakpointsTargetDMContext.class);
		if (bpService != null && bpDmc != null) {
			bpService.insertBreakpoint(bpDmc, bpAttributes,
					new DataRequestMonitor<IBreakpointDMContext>(getExecutor(), rm) {
						@Override
						protected void handleSuccess() {
							// Now resume at the proper location
							resumeAtLocation(context, location, rm);
						}
					});
		} else {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED,
					"Unable to set breakpoint", null)); //$NON-NLS-1$
			rm.done();
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.debug.service.IRunControl2#canRunToLine(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, java.lang.String, int, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
	 */
	/**
	 * @since 3.0
	 */
	@Override
	public void canRunToLine(IExecutionDMContext context, String sourceFile, int lineNumber,
			DataRequestMonitor<Boolean> rm) {
		canResume(context, rm);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.debug.service.IRunControl2#runToLine(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, java.lang.String, int, boolean, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
	 */
	/**
	 * @since 3.0
	 */
	@Override
	public void runToLine(final IExecutionDMContext context, String sourceFile, final int lineNumber,
			final boolean skipBreakpoints, final RequestMonitor rm) {

		determineDebuggerPath(context, sourceFile, new ImmediateDataRequestMonitor<String>(rm) {
			@Override
			protected void handleSuccess() {
				runToLocation(context, getData() + ":" + Integer.toString(lineNumber), skipBreakpoints, rm); //$NON-NLS-1$
			}
		});
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.debug.service.IRunControl2#canRunToAddress(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, org.eclipse.cdt.core.IAddress, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
	 */
	/**
	 * @since 3.0
	 */
	@Override
	public void canRunToAddress(IExecutionDMContext context, IAddress address, DataRequestMonitor<Boolean> rm) {
		canResume(context, rm);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.debug.service.IRunControl2#runToAddress(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, org.eclipse.cdt.core.IAddress, boolean, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
	 */
	/**
	 * @since 3.0
	 */
	@Override
	public void runToAddress(IExecutionDMContext context, IAddress address, boolean skipBreakpoints,
			RequestMonitor rm) {
		runToLocation(context, "*0x" + address.toString(16), skipBreakpoints, rm); //$NON-NLS-1$
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.debug.service.IRunControl2#canMoveToLine(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, java.lang.String, int, boolean, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
	 */
	/**
	 * @since 3.0
	 */
	@Override
	public void canMoveToLine(IExecutionDMContext context, String sourceFile, int lineNumber, boolean resume,
			DataRequestMonitor<Boolean> rm) {
		canResume(context, rm);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.debug.service.IRunControl2#moveToLine(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, java.lang.String, int, boolean, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
	 */
	/**
	 * @since 3.0
	 */
	@Override
	public void moveToLine(final IExecutionDMContext context, String sourceFile, final int lineNumber,
			final boolean resume, final RequestMonitor rm) {
		final IMIExecutionDMContext threadExecDmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
		if (threadExecDmc == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
					"Invalid thread context", null)); //$NON-NLS-1$
			rm.done();
		} else {
			determineDebuggerPath(context, sourceFile, new ImmediateDataRequestMonitor<String>(rm) {
				@Override
				protected void handleSuccess() {
					String debuggerPath = getData();

					String location = debuggerPath + ":" + lineNumber; //$NON-NLS-1$
					if (resume) {
						resumeAtLocation(context, location, rm);
					} else {
						// Create the breakpoint attributes
						Map<String, Object> attr = new HashMap<>();
						attr.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.BREAKPOINT);
						attr.put(MIBreakpoints.FILE_NAME, debuggerPath);
						attr.put(MIBreakpoints.LINE_NUMBER, lineNumber);
						attr.put(MIBreakpointDMData.IS_TEMPORARY, true);
						attr.put(MIBreakpointDMData.THREAD_ID, threadExecDmc.getThreadId());

						// Now do the operation
						moveToLocation(context, location, attr, rm);
					}
				}
			});
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.debug.service.IRunControl2#canMoveToAddress(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, org.eclipse.cdt.core.IAddress, boolean, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
	 */
	/**
	 * @since 3.0
	 */
	@Override
	public void canMoveToAddress(IExecutionDMContext context, IAddress address, boolean resume,
			DataRequestMonitor<Boolean> rm) {
		canResume(context, rm);
	}

	/** (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.debug.service.IRunControl2#moveToAddress(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, org.eclipse.cdt.core.IAddress, boolean, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
	 * @since 3.0
	 */
	@Override
	public void moveToAddress(IExecutionDMContext context, IAddress address, boolean resume, RequestMonitor rm) {
		IMIExecutionDMContext threadExecDmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
		if (threadExecDmc == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
					"Invalid thread context", null)); //$NON-NLS-1$
			rm.done();
		} else {
			String location = "*0x" + address.toString(16); //$NON-NLS-1$
			if (resume)
				resumeAtLocation(context, location, rm);
			else {
				// Create the breakpoint attributes
				Map<String, Object> attr = new HashMap<>();
				attr.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.BREAKPOINT);
				attr.put(MIBreakpoints.ADDRESS, "0x" + address.toString(16)); //$NON-NLS-1$
				attr.put(MIBreakpointDMData.IS_TEMPORARY, true);
				attr.put(MIBreakpointDMData.THREAD_ID, threadExecDmc.getThreadId());

				// Now do the operation
				moveToLocation(context, location, attr, rm);
			}
		}
	}

	/** @since 4.0 */
	@Override
	public IRunMode getRunMode() {
		return MIRunMode.NON_STOP;
	}

	/** @since 4.0 */
	@Override
	public boolean isTargetAcceptingCommands() {
		// Always accepting commands in non-stop mode
		return true;
	}

	/**
	 * Determine the path that should be sent to the debugger as per the source lookup service.
	 *
	 * @param dmc A context that can be used to obtain the sourcelookup context.
	 * @param hostPath The path of the file on the host, which must be converted.
	 * @param rm The result of the conversion.
	 */
	private void determineDebuggerPath(IDMContext dmc, String hostPath, final DataRequestMonitor<String> rm) {
		ISourceLookup sourceLookup = getServicesTracker().getService(ISourceLookup.class);
		ISourceLookupDMContext srcDmc = DMContexts.getAncestorOfType(dmc, ISourceLookupDMContext.class);
		if (sourceLookup == null || srcDmc == null) {
			// Source lookup not available for given context, use the host
			// path for the debugger path.
			// Hack around a MinGW bug; see 369622 (and also 196154 and 232415)
			rm.done(adjustDebuggerPath(hostPath));
			return;
		}

		sourceLookup.getDebuggerPath(srcDmc, hostPath, new DataRequestMonitor<String>(getExecutor(), rm) {
			@Override
			protected void handleSuccess() {
				// Hack around a MinGW bug; see 369622 (and also 196154 and 232415)
				rm.done(adjustDebuggerPath(getData()));
			}
		});
	}

	/**
	 * See bug 196154
	 *
	 * @param path
	 *            the absolute path to the source file
	 * @return the adjusted path provided by the breakpoints service
	 */
	private String adjustDebuggerPath(String path) {
		IBreakpoints breakpoints = getServicesTracker().getService(IBreakpoints.class);
		return (breakpoints instanceof IMIBreakpointPathAdjuster)
				? ((IMIBreakpointPathAdjuster) breakpoints).adjustDebuggerPath(path)
				: path;
	}

	///////////////////////////////////////////////////////////////////////////
	// IMultiRunControl implementation
	///////////////////////////////////////////////////////////////////////////

	// Although multi-process in only supported for GDB >= 7.2, it is simpler
	// to code for the multi-process case all the time, since it is a superset
	// of the single-process case.

	///////////////////////////////////////////////////////////////////////////
	// Multi-resume implementation:
	//
	// If one or more more threads of one or many processes are selected, we want to
	// resume each thread (once).
	//
	// If one or more more processes are selected, we want to resume each process (once).
	//
	// If a process is selected along with one or more threads of that same process,
	// what does the user want us to do?  Selecting the process will resume all its
	// threads, but what do we do with the selected threads?  Why are they
	// selected?  In an attempt to be user friendly, lets assume that the user
	// wants to resume the entire process, so we ignore the selected threads part of that
	// process since they will be resumed anyway.
	//
	// The same logic applies to multi-suspend.
	///////////////////////////////////////////////////////////////////////////

	/** @since 4.1 */
	@Override
	public void canResumeSome(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
		assert contexts != null;

		if (fRunControlOperationsEnabled == false) {
			rm.done(false);
			return;
		}

		List<IExecutionDMContext> execDmcToResumeList = extractContextsForOperation(contexts);

		// If any of the threads or processes can be resumed, we allow
		// the user to perform the operation.
		for (IExecutionDMContext execDmc : execDmcToResumeList) {
			if (doCanResume(execDmc)) {
				rm.done(true);
				return;
			}
		}

		// Didn't find anything that could be resumed.
		rm.done(false);
	}

	/** @since 4.1 */
	@Override
	public void canResumeAll(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
		assert contexts != null;

		if (fRunControlOperationsEnabled == false) {
			rm.done(false);
			return;
		}

		List<IExecutionDMContext> execDmcToResumeList = extractContextsForOperation(contexts);

		// If any of the threads or processes cannot be resumed, we don't allow
		// the user to perform the operation.
		for (IExecutionDMContext execDmc : execDmcToResumeList) {
			if (!doCanResume(execDmc)) {
				rm.done(false);
				return;
			}
		}

		// Everything can be resumed
		rm.done(true);
	}

	/**
	 * {@inheritDoc}
	 *
	 * For GDB, a separate resume command will be sent, one for each context
	 * that can be resumed.
	 * @since 4.1
	 */
	@Override
	public void resume(IExecutionDMContext[] contexts, RequestMonitor rm) {
		assert contexts != null;

		List<IExecutionDMContext> execDmcToResumeList = extractContextsForOperation(contexts);

		CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm);
		int count = 0;

		// Perform resume operation on each thread or process that can be resumed
		for (IExecutionDMContext execDmc : execDmcToResumeList) {
			if (doCanResume(execDmc)) {
				count++;
				resume(execDmc, crm);
			}
		}

		crm.setDoneCount(count);
	}

	///////////////////////////////////////////////////////////////////////////
	// Multi-suspend implementation:
	//  see details of the multi-resume implementation above.
	///////////////////////////////////////////////////////////////////////////

	/** @since 4.1 */
	@Override
	public void canSuspendSome(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
		assert contexts != null;

		if (fRunControlOperationsEnabled == false) {
			rm.done(false);
			return;
		}

		List<IExecutionDMContext> execDmcToSuspendList = extractContextsForOperation(contexts);

		// If any of the threads or processes can be suspended, we allow
		// the user to perform the operation.
		for (IExecutionDMContext execDmc : execDmcToSuspendList) {
			if (doCanSuspend(execDmc)) {
				rm.done(true);
				return;
			}
		}

		// Didn't find anything that could be suspended.
		rm.done(false);
	}

	/** @since 4.1 */
	@Override
	public void canSuspendAll(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
		assert contexts != null;

		if (fRunControlOperationsEnabled == false) {
			rm.done(false);
			return;
		}

		List<IExecutionDMContext> execDmcToSuspendList = extractContextsForOperation(contexts);

		// If any of the threads or processes cannot be suspended, we don't allow
		// the user to perform the operation.
		for (IExecutionDMContext execDmc : execDmcToSuspendList) {
			if (!doCanSuspend(execDmc)) {
				rm.done(false);
				return;
			}
		}

		// Everything can be suspended
		rm.done(true);
	}

	/** @since 4.1 */
	@Override
	public void isSuspendedSome(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
		assert contexts != null;

		List<IExecutionDMContext> execDmcSuspendedList = extractContextsForOperation(contexts);

		// Look for any thread or process that is suspended
		for (IExecutionDMContext execDmc : execDmcSuspendedList) {
			if (isSuspended(execDmc)) {
				rm.done(true);
				return;
			}
		}

		// Didn't find anything that was suspended.
		rm.done(false);
	}

	/** @since 4.1 */
	@Override
	public void isSuspendedAll(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
		assert contexts != null;

		List<IExecutionDMContext> execDmcSuspendedList = extractContextsForOperation(contexts);

		// Look for any thread or process that is not suspended
		for (IExecutionDMContext execDmc : execDmcSuspendedList) {
			if (!isSuspended(execDmc)) {
				rm.done(false);
				return;
			}
		}

		// Everything is suspended.
		rm.done(true);
	}

	/**
	 * {@inheritDoc}
	 *
	 * For GDB, a separate suspend command will be sent, one for each context
	 * that can be suspended.
	 * @since 4.1
	 */
	@Override
	public void suspend(IExecutionDMContext[] contexts, RequestMonitor rm) {
		assert contexts != null;

		List<IExecutionDMContext> execDmcToSuspendList = extractContextsForOperation(contexts);

		CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm);
		int count = 0;

		// Perform resume operation on each thread or process that can be resumed
		for (IExecutionDMContext execDmc : execDmcToSuspendList) {
			if (doCanSuspend(execDmc)) {
				count++;
				suspend(execDmc, crm);
			}
		}

		crm.setDoneCount(count);
	}

	///////////////////////////////////////////////////////////////////////////
	// Multi-step implementation.  Not implemented yet.  See bug 330974.
	///////////////////////////////////////////////////////////////////////////

	/** @since 4.1 */
	@Override
	public void canStepSome(IExecutionDMContext[] contexts, StepType stepType, DataRequestMonitor<Boolean> rm) {
		rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
				null));
	}

	/** @since 4.1 */
	@Override
	public void canStepAll(IExecutionDMContext[] contexts, StepType stepType, DataRequestMonitor<Boolean> rm) {
		rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
				null));
	}

	/** @since 4.1 */
	@Override
	public void isSteppingSome(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
		rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
				null));
	}

	/** @since 4.1 */
	@Override
	public void isSteppingAll(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
		rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
				null));
	}

	/** @since 4.1 */
	@Override
	public void step(IExecutionDMContext[] contexts, StepType stepType, RequestMonitor rm) {
		rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
				null));
	}

	/**
	 * Removes duplicates from the list of execution contexts, in case the same thread
	 * or process is present more than once.
	 *
	 * Also, remove any thread that is part of a process that is also present.  This is
	 * because an operation on the process will affect all its threads anyway.
	 */
	private List<IExecutionDMContext> extractContextsForOperation(IExecutionDMContext[] contexts) {
		// Remove duplicate contexts by using a set
		Set<IExecutionDMContext> specifiedExedDmcSet = new HashSet<>(Arrays.asList(contexts));

		// A list that ignores threads for which the process is also present
		List<IExecutionDMContext> execDmcForOperationList = new ArrayList<>(specifiedExedDmcSet.size());

		// Check for the case of a process selected along with some of its threads
		for (IExecutionDMContext execDmc : specifiedExedDmcSet) {
			if (execDmc instanceof IContainerDMContext) {
				// This is a process: it is automatically part of our list
				execDmcForOperationList.add(execDmc);
			} else {
				// Get the process for this thread
				IContainerDMContext containerDmc = DMContexts.getAncestorOfType(execDmc, IContainerDMContext.class);
				// Check if that process is also present
				if (specifiedExedDmcSet.contains(containerDmc) == false) {
					// This thread does not belong to a process that is selected, so we keep it.
					execDmcForOperationList.add(execDmc);
				}
			}
		}
		return execDmcForOperationList;
	}

	/**
	 * @since 4.2
	 */
	@Override
	public void canStepIntoSelection(IExecutionDMContext context, String sourceFile, int lineNumber,
			IFunctionDeclaration selectedFunction, DataRequestMonitor<Boolean> rm) {
		canStep(context, StepType.STEP_INTO, rm);
	}

	/**
	 * @since 4.2
	 */
	@Override
	public void stepIntoSelection(final IExecutionDMContext context, String sourceFile, final int lineNumber,
			final boolean skipBreakpoints, final IFunctionDeclaration selectedFunction, final RequestMonitor rm) {
		determineDebuggerPath(context, sourceFile, new ImmediateDataRequestMonitor<String>(rm) {
			@Override
			protected void handleSuccess() {
				stepIntoSelection(context, lineNumber, getData() + ":" + Integer.toString(lineNumber), skipBreakpoints, //$NON-NLS-1$
						selectedFunction, rm);
			}
		});
	}

	/**
	 * Help method used when the stopped event has not been broadcasted e.g. in the middle of step into selection
	 *
	 * @param dmc
	 * @param rm
	 */
	private void getStackDepth(final IMIExecutionDMContext dmc, final DataRequestMonitor<Integer> rm) {
		if (dmc != null) {
			fConnection.queueCommand(fCommandFactory.createMIStackInfoDepth(dmc),
					new DataRequestMonitor<MIStackInfoDepthInfo>(getExecutor(), rm) {
						@Override
						protected void handleSuccess() {
							rm.setData(getData().getDepth());
							rm.done();
						}
					});
		} else {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid context", null)); //$NON-NLS-1$
			rm.done();
		}
	}
}
