/*******************************************************************************
* Copyright (c) 2006, 2010 Wind River Systems, Nokia and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nokia - initial API and implementation with some code moved from GDBControl.
* Wind River System
* Ericsson
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.launching.LaunchUtils;
import org.eclipse.cdt.dsf.gdb.service.command.GDBControl.InitializationShutdownStep;
import org.eclipse.cdt.dsf.mi.service.IMIBackend;
import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.utils.spawner.ProcessFactory;
import org.eclipse.cdt.utils.spawner.Spawner;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.osgi.framework.BundleContext;
/**
* Implementation of {@link IGDBBackend} for the common case where GDB is launched
* in local file system on host PC where Eclipse runs. This also manages some GDB parameters
* from a given launch configuration.<br>
* <br>
* You can subclass for you special needs.
*
* @since 1.1
*/
public class GDBBackend extends AbstractDsfService implements IGDBBackend {
private final ILaunchConfiguration fLaunchConfiguration;
/*
* Parameters for launching GDB.
*/
private IPath fProgramPath;
private IPath fGDBWorkingDirectory;
private String fGDBInitFile;
private List<String> fSharedLibPaths;
private String fProgramArguments;
private Properties fEnvVariables;
private SessionType fSessionType;
private Boolean fAttach;
private State fBackendState = State.NOT_INITIALIZED;
/**
* Unique ID of this service instance.
*/
private final String fBackendId;
private static int fgInstanceCounter = 0;
/*
* Service state parameters.
*/
private MonitorJob fMonitorJob;
private Process fProcess;
private int fGDBExitValue;
private int fGDBLaunchTimeout = 30;
/**
* A Job that will set a failed status
* in the proper request monitor, if the interrupt
* did not succeed after a certain time.
*/
private MonitorInterruptJob fInterruptFailedJob;
public GDBBackend(DsfSession session, ILaunchConfiguration lc) {
super(session);
fBackendId = "gdb[" +Integer.toString(fgInstanceCounter++) + "]"; //$NON-NLS-1$//$NON-NLS-2$
fLaunchConfiguration = lc;
try {
// Don't call verifyCProject, because the JUnit tests are not setting a project
ICProject cproject = LaunchUtils.getCProject(lc);
fProgramPath = LaunchUtils.verifyProgramPath(lc, cproject);
} catch (CoreException e) {
fProgramPath = new Path(""); //$NON-NLS-1$
}
}
@Override
public void initialize(final RequestMonitor requestMonitor) {
super.initialize( new RequestMonitor(ImmediateExecutor.getInstance(), requestMonitor) {
@Override
protected void handleSuccess() {
doInitialize(requestMonitor);
}
});
}
private void doInitialize(final RequestMonitor requestMonitor) {
final Sequence.Step[] initializeSteps = new Sequence.Step[] {
new GDBProcessStep(InitializationShutdownStep.Direction.INITIALIZING),
new MonitorJobStep(InitializationShutdownStep.Direction.INITIALIZING),
new RegisterStep(InitializationShutdownStep.Direction.INITIALIZING),
};
Sequence startupSequence = new Sequence(getExecutor(), requestMonitor) {
@Override public Step[] getSteps() { return initializeSteps; }
};
getExecutor().execute(startupSequence);
}
@Override
public void shutdown(final RequestMonitor requestMonitor) {
final Sequence.Step[] shutdownSteps = new Sequence.Step[] {
new RegisterStep(InitializationShutdownStep.Direction.SHUTTING_DOWN),
new MonitorJobStep(InitializationShutdownStep.Direction.SHUTTING_DOWN),
new GDBProcessStep(InitializationShutdownStep.Direction.SHUTTING_DOWN),
};
Sequence shutdownSequence =
new Sequence(getExecutor(),
new RequestMonitor(getExecutor(), requestMonitor) {
@Override
protected void handleCompleted() {
GDBBackend.super.shutdown(requestMonitor);
}
}) {
@Override public Step[] getSteps() { return shutdownSteps; }
};
getExecutor().execute(shutdownSequence);
}
/** @since 4.0 */
protected IPath getGDBPath() {
return LaunchUtils.getGDBPath(fLaunchConfiguration);
}
/*
* Options for GDB process.
* Allow subclass to override.
*/
protected String getGDBCommandLine() {
StringBuffer gdbCommandLine = new StringBuffer(getGDBPath().toOSString());
// The goal here is to keep options to an absolute minimum.
// All configuration should be done in the launch sequence
// to allow for more flexibility.
gdbCommandLine.append(" --interpreter"); //$NON-NLS-1$
// We currently work with MI version 2. Don't use just 'mi' because it
// points to the latest MI version, while we want mi2 specifically.
gdbCommandLine.append(" mi2"); //$NON-NLS-1$
// Don't read the gdbinit file here. It is read explicitly in
// the LaunchSequence to make it easier to customize.
gdbCommandLine.append(" --nx"); //$NON-NLS-1$
return gdbCommandLine.toString();
}
public String getGDBInitFile() throws CoreException {
if (fGDBInitFile == null) {
String defaultGdbInit = Platform.getPreferencesService().getString(GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_DEFAULT_GDB_INIT,
IGDBLaunchConfigurationConstants.DEBUGGER_GDB_INIT_DEFAULT, null);
fGDBInitFile = fLaunchConfiguration.getAttribute(IGDBLaunchConfigurationConstants.ATTR_GDB_INIT, defaultGdbInit);
}
return fGDBInitFile;
}
public IPath getGDBWorkingDirectory() throws CoreException {
if (fGDBWorkingDirectory == null) {
// First try to use the user-specified working directory for the debugged program.
// This is fine only with local debug.
// For remote debug, the working dir of the debugged program will be on remote device
// and hence not applicable. In such case we may just use debugged program path on host
// as the working dir for GDB.
// However, we cannot find a standard/common way to distinguish remote debug from local
// debug. For instance, a local debug may also use gdbserver+gdb. So it's up to each
// debugger implementation to make the distinction.
//
IPath path = null;
String location = fLaunchConfiguration.getAttribute(ICDTLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, (String)null);
if (location != null) {
String expandedLocation = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(location);
if (expandedLocation.length() > 0) {
path = new Path(expandedLocation);
}
}
if (path != null) {
// Some validity check. Should have been done by UI code.
if (path.isAbsolute()) {
File dir = new File(path.toPortableString());
if (! dir.isDirectory())
path = null;
} else {
IResource res = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
if (res instanceof IContainer && res.exists()) {
path = res.getLocation();
}
else
// Relative but not found in workspace.
path = null;
}
}
if (path == null) {
// default working dir is the project if this config has a project
ICProject cp = LaunchUtils.getCProject(fLaunchConfiguration);
if (cp != null) {
IProject p = cp.getProject();
path = p.getLocation();
}
else {
// no meaningful value found. Just return null.
}
}
fGDBWorkingDirectory = path;
}
return fGDBWorkingDirectory;
}
public String getProgramArguments() throws CoreException {
if (fProgramArguments == null) {
fProgramArguments = fLaunchConfiguration.getAttribute(
ICDTLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
(String)null);
if (fProgramArguments != null) {
fProgramArguments = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(fProgramArguments);
}
}
return fProgramArguments;
}
public IPath getProgramPath() {
return fProgramPath;
}
@SuppressWarnings("unchecked")
public List<String> getSharedLibraryPaths() throws CoreException {
if (fSharedLibPaths == null) {
fSharedLibPaths = fLaunchConfiguration.getAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_SOLIB_PATH,
new ArrayList<String>(0));
}
return fSharedLibPaths;
}
/** @since 3.0 */
public Properties getEnvironmentVariables() throws CoreException {
if (fEnvVariables == null) {
fEnvVariables = new Properties();
// if the attribute ATTR_APPEND_ENVIRONMENT_VARIABLES is set,
// the LaunchManager will return both the new variables and the existing ones.
// That would force us to delete all the variables in GDB, and then re-create then all
// that is not very efficient. So, let's fool the LaunchManager into returning just the
// list of new variables.
boolean append = fLaunchConfiguration.getAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, true);
String[] properties;
if (append) {
ILaunchConfigurationWorkingCopy wc = fLaunchConfiguration.copy(""); //$NON-NLS-1$
// Don't save this change, it is just temporary, and in just a copy of our launchConfig.
wc.setAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, false);
properties = DebugPlugin.getDefault().getLaunchManager().getEnvironment(wc);
} else {
// We're getting rid of the environment anyway, so this call will only yield the new variables.
properties = DebugPlugin.getDefault().getLaunchManager().getEnvironment(fLaunchConfiguration);
}
if (properties == null) {
properties = new String[0];
}
for (String property : properties) {
int idx = property.indexOf('=');
if (idx != -1) {
String key = property.substring(0, idx);
String value = property.substring(idx + 1);
fEnvVariables.setProperty(key, value);
} else {
fEnvVariables.setProperty(property, ""); //$NON-NLS-1$
}
}
}
return fEnvVariables;
}
/** @since 3.0 */
public boolean getClearEnvironment() throws CoreException {
return !fLaunchConfiguration.getAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, true);
}
/** @since 3.0 */
public boolean getUpdateThreadListOnSuspend() throws CoreException {
return fLaunchConfiguration
.getAttribute(
IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_UPDATE_THREADLIST_ON_SUSPEND,
IGDBLaunchConfigurationConstants.DEBUGGER_UPDATE_THREADLIST_ON_SUSPEND_DEFAULT);
}
/*
* Launch GDB process.
* Allow subclass to override.
*/
protected Process launchGDBProcess(String commandLine) throws CoreException {
Process proc = null;
try {
proc = ProcessFactory.getFactory().exec(commandLine, LaunchUtils.getLaunchEnvironment(fLaunchConfiguration));
} catch (IOException e) {
String message = "Error while launching command " + commandLine; //$NON-NLS-1$
throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, message, e));
}
return proc;
}
public Process getProcess() {
return fProcess;
}
public OutputStream getMIOutputStream() {
return fProcess.getOutputStream();
};
public InputStream getMIInputStream() {
return fProcess.getInputStream();
};
public String getId() {
return fBackendId;
}
public void interrupt() {
if (fProcess instanceof Spawner) {
Spawner gdbSpawner = (Spawner) fProcess;
// Cygwin gdb 6.8 is capricious when it comes to interrupting the
// target. The same logic here will work with MinGW, though. And on
// linux it's irrelevant since interruptCTRLC()==interrupt(). So,
// one odd size fits all.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=304096#c54
if (getSessionType() == SessionType.REMOTE) {
gdbSpawner.interrupt();
}
else {
gdbSpawner.interruptCTRLC();
}
}
}
/**
* @since 3.0
*/
public void interruptAndWait(int timeout, RequestMonitor rm) {
if (fProcess instanceof Spawner) {
Spawner gdbSpawner = (Spawner) fProcess;
// Cygwin gdb 6.8 is capricious when it comes to interrupting the
// target. The same logic here will work with MinGW, though. And on
// linux it's irrelevant since interruptCTRLC()==interrupt(). So,
// one odd size fits all.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=304096#c54
if (getSessionType() == SessionType.REMOTE) {
gdbSpawner.interrupt();
}
else {
gdbSpawner.interruptCTRLC();
}
fInterruptFailedJob = new MonitorInterruptJob(timeout, rm);
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Cannot interrupt.", null)); //$NON-NLS-1$
rm.done();
}
}
/**
* @since 3.0
*/
public void interruptInferiorAndWait(long pid, int timeout, RequestMonitor rm) {
if (fProcess instanceof Spawner) {
Spawner gdbSpawner = (Spawner) fProcess;
gdbSpawner.raise((int)pid, gdbSpawner.INT);
fInterruptFailedJob = new MonitorInterruptJob(timeout, rm);
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Cannot interrupt.", null)); //$NON-NLS-1$
rm.done();
}
}
public void destroy() {
// Don't close the streams ourselves as it may be too early.
// Wait for the actual user of the streams to close it.
// Bug 339379
// destroy() should be supported even if it's not spawner.
if (getState() == State.STARTED) {
fProcess.destroy();
}
}
public State getState() {
return fBackendState;
}
public int getExitCode() {
return fGDBExitValue;
}
public SessionType getSessionType() {
if (fSessionType == null) {
fSessionType = LaunchUtils.getSessionType(fLaunchConfiguration);
}
return fSessionType;
}
public boolean getIsAttachSession() {
if (fAttach == null) {
fAttach = LaunchUtils.getIsAttach(fLaunchConfiguration);
}
return fAttach;
}
@Override
protected BundleContext getBundleContext() {
return GdbPlugin.getBundleContext();
}
protected class GDBProcessStep extends InitializationShutdownStep {
GDBProcessStep(Direction direction) { super(direction); }
@Override
public void initialize(final RequestMonitor requestMonitor) {
class GDBLaunchMonitor {
boolean fLaunched = false;
boolean fTimedOut = false;
}
final GDBLaunchMonitor fGDBLaunchMonitor = new GDBLaunchMonitor();
final RequestMonitor gdbLaunchRequestMonitor = new RequestMonitor(getExecutor(), requestMonitor) {
@Override
protected void handleCompleted() {
if (!fGDBLaunchMonitor.fTimedOut) {
fGDBLaunchMonitor.fLaunched = true;
if (!isSuccess()) {
requestMonitor.setStatus(getStatus());
}
requestMonitor.done();
}
}
};
final Job startGdbJob = new Job("Start GDB Process Job") { //$NON-NLS-1$
{
setSystem(true);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
if (gdbLaunchRequestMonitor.isCanceled()) {
gdbLaunchRequestMonitor.setStatus(new Status(IStatus.CANCEL, GdbPlugin.PLUGIN_ID, -1, "Canceled starting GDB", null)); //$NON-NLS-1$
gdbLaunchRequestMonitor.done();
return Status.OK_STATUS;
}
String commandLine = getGDBCommandLine();
try {
fProcess = launchGDBProcess(commandLine);
// Need to do this on the executor for thread-safety
getExecutor().submit(
new DsfRunnable() {
public void run() { fBackendState = State.STARTED; }
});
// Don't send the backendStarted event yet. We wait until we have registered this service
// so that other services can have access to it.
} catch(CoreException e) {
gdbLaunchRequestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, e.getMessage(), e));
gdbLaunchRequestMonitor.done();
return Status.OK_STATUS;
}
try {
Reader r = new InputStreamReader(getMIInputStream());
BufferedReader reader = new BufferedReader(r);
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.endsWith("(gdb)")) { //$NON-NLS-1$
break;
}
}
} catch (IOException e) {
gdbLaunchRequestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, "Error reading GDB STDOUT", e)); //$NON-NLS-1$
gdbLaunchRequestMonitor.done();
return Status.OK_STATUS;
}
gdbLaunchRequestMonitor.done();
return Status.OK_STATUS;
}
};
startGdbJob.schedule();
getExecutor().schedule(new Runnable() {
public void run() {
// Only process the event if we have not finished yet (hit the breakpoint).
if (!fGDBLaunchMonitor.fLaunched) {
fGDBLaunchMonitor.fTimedOut = true;
Thread jobThread = startGdbJob.getThread();
if (jobThread != null) {
jobThread.interrupt();
}
requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, "Timed out trying to launch GDB.", null)); //$NON-NLS-1$
requestMonitor.done();
}
}},
fGDBLaunchTimeout, TimeUnit.SECONDS);
}
@Override
protected void shutdown(final RequestMonitor requestMonitor) {
if (getState() != State.STARTED) {
// gdb not started yet or already killed, don't bother starting
// a job to kill it
requestMonitor.done();
return;
}
new Job("Terminating GDB process.") { //$NON-NLS-1$
{
setSystem(true);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
// Need to do this on the executor for thread-safety
// And we should wait for it to complete since we then
// check if the killing of GDB worked.
getExecutor().submit(
new DsfRunnable() {
public void run() {
destroy();
if (fMonitorJob.fMonitorExited) {
// Now that we have destroyed the process,
// and that the monitoring thread was killed,
// we need to set our state and send the event
fBackendState = State.TERMINATED;
getSession().dispatchEvent(
new BackendStateChangedEvent(getSession().getId(), getId(), State.TERMINATED),
getProperties());
}
}
}).get();
} catch (InterruptedException e1) {
} catch (ExecutionException e1) {
}
int attempts = 0;
while (attempts < 10) {
try {
// Don't know if we really need the exit value... but what the heck.
fGDBExitValue = fProcess.exitValue(); // throws exception if process not exited
requestMonitor.done();
return Status.OK_STATUS;
} catch (IllegalThreadStateException ie) {
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
attempts++;
}
requestMonitor.setStatus(new Status(
IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "GDB terminate failed", null)); //$NON-NLS-1$
requestMonitor.done();
return Status.OK_STATUS;
}
}.schedule();
}
}
protected class MonitorJobStep extends InitializationShutdownStep {
MonitorJobStep(Direction direction) { super(direction); }
@Override
public void initialize(final RequestMonitor requestMonitor) {
fMonitorJob = new MonitorJob(
fProcess,
new DsfRunnable() {
public void run() {
requestMonitor.done();
}
});
fMonitorJob.schedule();
}
@Override
protected void shutdown(RequestMonitor requestMonitor) {
if (fMonitorJob != null) {
fMonitorJob.kill();
}
requestMonitor.done();
}
}
protected class RegisterStep extends InitializationShutdownStep {
RegisterStep(Direction direction) { super(direction); }
@Override
public void initialize(final RequestMonitor requestMonitor) {
register(
new String[]{ IMIBackend.class.getName(),
IGDBBackend.class.getName() },
new Hashtable<String,String>());
getSession().addServiceEventListener(GDBBackend.this, null);
/*
* This event is not consumed by any one at present, instead it's
* the GDBControlInitializedDMEvent that's used to indicate that GDB
* back end is ready for MI commands. But we still fire the event as
* it does no harm and may be needed sometime.... 09/29/2008
*
* We send the event in the register step because that is when
* other services have access to it.
*/
getSession().dispatchEvent(
new BackendStateChangedEvent(getSession().getId(), getId(), State.STARTED),
getProperties());
requestMonitor.done();
}
@Override
protected void shutdown(RequestMonitor requestMonitor) {
unregister();
getSession().removeServiceEventListener(GDBBackend.this);
requestMonitor.done();
}
}
/**
* Monitors a system process, waiting for it to terminate, and
* then notifies the associated runtime process.
*/
private class MonitorJob extends Job {
boolean fMonitorExited = false;
DsfRunnable fMonitorStarted;
Process fMonProcess;
@Override
protected IStatus run(IProgressMonitor monitor) {
synchronized(fMonProcess) {
getExecutor().submit(fMonitorStarted);
try {
fMonProcess.waitFor();
fGDBExitValue = fMonProcess.exitValue();
// Need to do this on the executor for thread-safety
getExecutor().submit(
new DsfRunnable() {
public void run() {
destroy();
fBackendState = State.TERMINATED;
getSession().dispatchEvent(
new BackendStateChangedEvent(getSession().getId(), getId(), State.TERMINATED),
getProperties());
}
});
} catch (InterruptedException ie) {
// clear interrupted state
Thread.interrupted();
}
fMonitorExited = true;
}
return Status.OK_STATUS;
}
MonitorJob(Process process, DsfRunnable monitorStarted) {
super("GDB process monitor job."); //$NON-NLS-1$
fMonProcess = process;
fMonitorStarted = monitorStarted;
setSystem(true);
}
void kill() {
synchronized(fMonProcess) {
if (!fMonitorExited) {
getThread().interrupt();
}
}
}
}
/**
* Stores the request monitor that must be dealt with for
* the result of the interrupt operation. If the interrupt
* successfully suspends the backend, the request monitor can
* be retrieved and completed successfully, and then this job
* should be canceled. If this job is not canceled before
* the time is up, it will imply the interrupt did not
* successfully suspend the backend, and the current job will
* indicate this in the request monitor.
*
* The specified timeout is used to indicate how many milliseconds
* this job should wait for. INTERRUPT_TIMEOUT_DEFAULT indicates
* to use the default of 5 seconds. The default is also use if the
* timeout value is 0 or negative.
*
* @since 3.0
*/
protected class MonitorInterruptJob 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;
public MonitorInterruptJob(int timeout, RequestMonitor rm) {
super("Interrupt monitor job."); //$NON-NLS-1$
setSystem(true);
fRequestMonitor = rm;
if (timeout == INTERRUPT_TIMEOUT_DEFAULT || timeout <= 0) {
timeout = TIMEOUT_DEFAULT_VALUE; // default of 5 seconds
}
schedule(timeout);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
getExecutor().submit(
new DsfRunnable() {
public void run() {
fInterruptFailedJob = null;
fRequestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "Interrupt failed.", null)); //$NON-NLS-1$
fRequestMonitor.done();
}
});
return Status.OK_STATUS;
}
public RequestMonitor getRequestMonitor() { return fRequestMonitor; }
}
/**
* We use this handler to determine if the SIGINT we sent to GDB has been
* effective. We must listen for an MI event and not a higher-level
* ISuspendedEvent. The reason is that some ISuspendedEvent are not sent
* when the target stops, in cases where we don't want to views to update.
* For example, if we want to interrupt the target to set a breakpoint, this
* interruption is done silently; we will receive the MI event though.
*
* <p>
* Though we send a SIGINT, we may not specifically get an MISignalEvent.
* Typically we will, but not always, so wait for an MIStoppedEvent. See
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=305178#c21
*
* @since 3.0
*
*/
@DsfServiceEventHandler
public void eventDispatched(final MIStoppedEvent e) {
if (fInterruptFailedJob != null) {
if (fInterruptFailedJob.cancel()) {
fInterruptFailedJob.getRequestMonitor().done();
}
fInterruptFailedJob = null;
}
}
}