blob: ad8b76ce30a55935c6292138b136d9a8364bee70 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2015 Wind River Systems, Nokia 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:
* Nokia - initial API and implementation with some code moved from GDBControl.
* Wind River System
* Ericsson
* Marc Khouzam (Ericsson) - Use the new IMIBackend2 interface (Bug 350837)
* Mark Bozeman (Mentor Graphics) - Report GDB start failures (Bug 376203)
* Iulia Vasii (Freescale Semiconductor) - Separate GDB command from its arguments (Bug 445360)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.cdt.core.parser.util.StringUtil;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch;
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.IMIBackend2;
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.CommandLineUtil;
import org.eclipse.cdt.utils.spawner.ProcessFactory;
import org.eclipse.cdt.utils.spawner.Spawner;
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.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
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, IMIBackend2 {
private final ILaunchConfiguration fLaunchConfiguration;
/*
* Parameters for launching GDB.
*/
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);
this.fLaunchConfiguration = lc;
fBackendId = "gdb[" + Integer.toString(fgInstanceCounter++) + "]"; //$NON-NLS-1$//$NON-NLS-2$
}
@Override
public void initialize(final RequestMonitor requestMonitor) {
super.initialize(new ImmediateRequestMonitor(requestMonitor) {
@Override
protected void handleSuccess() {
doInitialize(requestMonitor);
}
});
}
private void doInitialize(final RequestMonitor requestMonitor) {
getExecutor().execute(getStartupSequence(requestMonitor));
}
/** @since 5.0 */
protected Sequence getStartupSequence(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), };
return new Sequence(getExecutor(), requestMonitor) {
@Override
public Step[] getSteps() {
return initializeSteps;
}
};
}
@Override
public void shutdown(final RequestMonitor requestMonitor) {
getExecutor().execute(getShutdownSequence(new RequestMonitor(getExecutor(), requestMonitor) {
@Override
protected void handleCompleted() {
GDBBackend.super.shutdown(requestMonitor);
}
}));
}
/** @since 5.0 */
protected Sequence getShutdownSequence(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), };
return new Sequence(getExecutor(), requestMonitor) {
@Override
public Step[] getSteps() {
return shutdownSteps;
}
};
}
/** @since 5.2 */
protected GdbLaunch getGDBLaunch() {
return (GdbLaunch) getSession().getModelAdapter(ILaunch.class);
}
/** @since 4.0 */
protected IPath getGDBPath() {
return getGDBLaunch().getGDBPath();
}
/**
* Options for GDB process. Returns the GDB command and its arguments as an
* array. Allow subclass to override.
*
* @since 4.6
* @deprecated Replaced by getDebuggerCommandLine()
*/
@Deprecated
protected String[] getGDBCommandLineArray() {
// The goal here is to keep options to an absolute minimum.
// All configuration should be done in the final launch sequence
// to allow for more flexibility.
String cmd = getGDBPath().toOSString() + " --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.
" mi2" + //$NON-NLS-1$
// Don't read the gdbinit file here. It is read explicitly in
// the LaunchSequence to make it easier to customize.
" --nx"; //$NON-NLS-1$
// Parse to properly handle spaces and such things (bug 458499)
return CommandLineUtil.argumentsToArray(cmd);
}
/**
* Returns the GDB command and its arguments as an array.
* Allow subclass to override.
* @since 5.2
*/
// This method replaces getGDBCommandLineArray() because we need
// to override it for GDB 7.12 even if an extender has overridden
// getGDBCommandLineArray().
// Here is the scenario:
// An extender has overridden getGDBCommandLineArray() to launch
// GDB in MI mode but with extra parameters. Once GDBBackend_7_12
// is released, the extender may likely point their extension to
// GDBBackend_7_12 instead of GDBBackend (which will even happen
// automatically if the extender extends GDBBackend_HEAD).
// In such a case, they would override the changes in
// GDBBackend_7_12.getGDBCommandLineArray() and the debug session
// is likely to fail since with GDBBackend_7_12, we launch GDB
// in CLI mode.
//
// Instead, we use getDebuggerCommandLine() and override that method in
// GDBBackend_7_12. That way an extender will not override it
// without noticing (since it didn't exist before). Then we can call
// the overridden getGDBCommandLineArray() and work with that to
// make it work with the new way to launch GDB of GDBBackend_7_12
//
// Note that we didn't name this method getGDBCommandLine() because
// this name had been used in CDT 8.8 and could still be part of
// extenders' code.
protected String[] getDebuggerCommandLine() {
// Call the old method in case it was overridden
return getGDBCommandLineArray();
}
@Override
public String getGDBInitFile() throws CoreException {
return getGDBLaunch().getGDBInitFile();
}
@Override
public IPath getGDBWorkingDirectory() throws CoreException {
return getGDBLaunch().getGDBWorkingDirectory();
}
@Override
public String getProgramArguments() throws CoreException {
return getGDBLaunch().getProgramArguments();
}
@Override
public IPath getProgramPath() {
try {
return new Path(getGDBLaunch().getProgramPath());
} catch (CoreException e) {
return new Path(""); //$NON-NLS-1$
}
}
@Override
public List<String> getSharedLibraryPaths() throws CoreException {
return getGDBLaunch().getSharedLibraryPaths();
}
/** @since 3.0 */
@Override
public Properties getEnvironmentVariables() throws CoreException {
return getGDBLaunch().getEnvironmentVariables();
}
/** @since 3.0 */
@Override
public boolean getClearEnvironment() throws CoreException {
return getGDBLaunch().getClearEnvironment();
}
/** @since 3.0 */
@Override
public boolean getUpdateThreadListOnSuspend() throws CoreException {
return getGDBLaunch().getUpdateThreadListOnSuspend();
}
/**
* Launch GDB process. Allow subclass to override.
*
* @since 5.2
*/
// Again, we create a new method that we know has not been already
// overridden. That way, even if extenders have overridden the
// original launchGDBProcess(String[]), we will instead use
// the GDBBackend_7_12#launchGDBProcess() method when appropriate.
// This is important because if we didn't, the new console would
// not work properly.
//
// Of course, in that case, we won't call the extenders overridden
// launchGDBProcess(String[]) and therefore will not get their
// specialized code. I feel this is still a lower risk than
// not starting the full GDB console properly.
protected Process launchGDBProcess() throws CoreException {
// Call the old method in case it was overridden
return launchGDBProcess(getDebuggerCommandLine());
}
/**
* Launch GDB process with command and arguments. Allow subclass to
* override.
*
* @since 4.6
* @deprecated Replace by launchGDBProcess()
*/
@Deprecated
protected Process launchGDBProcess(String[] commandLine) throws CoreException {
Process proc = null;
try {
proc = ProcessFactory.getFactory().exec(commandLine, getGDBLaunch().getLaunchEnvironment());
} catch (IOException e) {
String message = "Error while launching command: " + StringUtil.join(commandLine, " "); //$NON-NLS-1$ //$NON-NLS-2$
throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, message, e));
}
return proc;
}
@Override
public Process getProcess() {
return fProcess;
}
@Override
public OutputStream getMIOutputStream() {
return fProcess.getOutputStream();
}
@Override
public InputStream getMIInputStream() {
return fProcess.getInputStream();
}
/** @since 4.1 */
@Override
public InputStream getMIErrorStream() {
return fProcess.getErrorStream();
}
@Override
public String getId() {
return fBackendId;
}
@Override
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
*/
@Override
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
*/
@Override
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();
}
}
@Override
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();
}
}
@Override
public State getState() {
return fBackendState;
}
@Override
public int getExitCode() {
return fGDBExitValue;
}
@Override
public SessionType getSessionType() {
if (fSessionType == null) {
fSessionType = LaunchUtils.getSessionType(fLaunchConfiguration);
}
return fSessionType;
}
@Override
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) {
doGDBProcessStep(requestMonitor);
}
@Override
protected void shutdown(final RequestMonitor requestMonitor) {
undoGDBProcessStep(requestMonitor);
}
}
protected class MonitorJobStep extends InitializationShutdownStep {
MonitorJobStep(Direction direction) {
super(direction);
}
@Override
public void initialize(final RequestMonitor requestMonitor) {
doMonitorJobStep(requestMonitor);
}
@Override
protected void shutdown(RequestMonitor requestMonitor) {
undoMonitorJobStep(requestMonitor);
}
}
protected class RegisterStep extends InitializationShutdownStep {
RegisterStep(Direction direction) {
super(direction);
}
@Override
public void initialize(RequestMonitor requestMonitor) {
doRegisterStep(requestMonitor);
}
@Override
protected void shutdown(RequestMonitor requestMonitor) {
undoRegisterStep(requestMonitor);
}
}
/** @since 5.0 */
protected void doGDBProcessStep(final RequestMonitor requestMonitor) {
class GDBLaunchMonitor {
boolean fLaunched = false;
boolean fTimedOut = false;
public ScheduledFuture<?> fTimeoutFuture;
}
final GDBLaunchMonitor fGDBLaunchMonitor = new GDBLaunchMonitor();
final RequestMonitor gdbLaunchRequestMonitor = new RequestMonitor(getExecutor(), requestMonitor) {
@Override
protected void handleCompleted() {
if (!fGDBLaunchMonitor.fTimedOut) {
fGDBLaunchMonitor.fLaunched = true;
fGDBLaunchMonitor.fTimeoutFuture.cancel(false);
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;
}
try {
fProcess = launchGDBProcess();
// Need to do this on the executor for thread-safety
getExecutor().submit(new DsfRunnable() {
@Override
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;
}
BufferedReader inputReader = null;
BufferedReader errorReader = null;
boolean success = false;
try {
// Must call getMIInputStream() because we always want to read from the MI stream,
// which is not always the same as the input stream of fProcess. They are
// different when we use the full GDB console
InputStream inputStream = getMIInputStream();
// Read initial GDB prompt
inputReader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = inputReader.readLine()) != null) {
line = line.trim();
if (line.endsWith("(gdb)")) { //$NON-NLS-1$
success = true;
break;
}
}
// Failed to read initial prompt, check for error
if (!success) {
// Don't call getMIErrorStream() because it can be overridden with a
// dummy stream in the case of the full GDB console.
// Instead, make sure we read the error from the process itself.
InputStream errorStream = fProcess.getErrorStream();
errorReader = new BufferedReader(new InputStreamReader(errorStream));
String errorInfo = errorReader.readLine();
if (errorInfo == null) {
errorInfo = "GDB prompt not read"; //$NON-NLS-1$
}
gdbLaunchRequestMonitor
.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, errorInfo, null));
}
} catch (IOException e) {
success = false;
gdbLaunchRequestMonitor.setStatus(
new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, "Error reading GDB output", e)); //$NON-NLS-1$
}
// In the case of failure, close the MI streams so
// they are not leaked.
if (!success) {
if (inputReader != null) {
try {
inputReader.close();
} catch (IOException e) {
}
}
if (errorReader != null) {
try {
errorReader.close();
} catch (IOException e) {
}
}
}
gdbLaunchRequestMonitor.done();
return Status.OK_STATUS;
}
};
startGdbJob.schedule();
fGDBLaunchMonitor.fTimeoutFuture = getExecutor().schedule(() -> {
// 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();
}
destroy();
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);
}
/** @since 5.0 */
protected void undoGDBProcessStep(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() {
@Override
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.
// throws exception if process not exited
fGDBExitValue = fProcess.exitValue();
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();
}
/** @since 5.0 */
protected void doMonitorJobStep(final RequestMonitor requestMonitor) {
fMonitorJob = new MonitorJob(fProcess, new DsfRunnable() {
@Override
public void run() {
requestMonitor.done();
}
});
fMonitorJob.schedule();
}
/** @since 5.0 */
protected void undoMonitorJobStep(RequestMonitor requestMonitor) {
if (fMonitorJob != null) {
fMonitorJob.kill();
}
requestMonitor.done();
}
/** @since 5.0 */
protected void doRegisterStep(RequestMonitor requestMonitor) {
register(new String[] { IMIBackend.class.getName(), IMIBackend2.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();
}
/** @since 5.0 */
protected void undoRegisterStep(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() {
@Override
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() {
@Override
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;
}
}
}