/******************************************************************************* * Copyright (c) 2000, 2010 QNX Software Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ package org.eclipse.cdt.debug.mi.core; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PushbackInputStream; import org.eclipse.cdt.utils.spawner.ProcessFactory; import org.eclipse.cdt.utils.spawner.Spawner; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; /** */ public class MIProcessAdapter implements MIProcess { Process fGDBProcess; InputStream gdbInputStream; private static final int ONE_SECOND = 1000; private long commandTimeout; public MIProcessAdapter(String[] args, IProgressMonitor monitor) throws IOException { this(args, 0, monitor); } public MIProcessAdapter(String[] args, int launchTimeout, IProgressMonitor monitor) throws IOException { fGDBProcess = getGDBProcess(args, launchTimeout, monitor); commandTimeout = MIPlugin.getCommandTimeout(); } /** * Do some basic synchronisation, gdb may take some time to load for * whatever reasons and we need to be able to let the user bailout. * * @param args * @return Process * @throws IOException */ protected Process getGDBProcess(String[] args, int launchTimeout, IProgressMonitor monitor) throws IOException { final Process pgdb = createGDBProcess(args); Thread syncStartup = new Thread("GDB Start") { //$NON-NLS-1$ public void run() { try { PushbackInputStream pb = new PushbackInputStream(pgdb.getInputStream()); gdbInputStream = pb; pb.unread(pb.read()); // actually read something, then return it } catch (Exception e) { // Do nothing, ignore the errors } } }; syncStartup.start(); int timepass = 0; if (launchTimeout <= 0) { // Simulate we are waiting forever. launchTimeout = Integer.MAX_VALUE; } // To respect the IProgressMonitor we can not use wait/notify // instead we have to loop and check for the monitor to allow to cancel the thread. // The monitor is check every 1 second delay; for (timepass = 0; timepass < launchTimeout; timepass += ONE_SECOND) { if (syncStartup.isAlive() && !monitor.isCanceled()) { try { Thread.sleep(ONE_SECOND); } catch (InterruptedException e) { // ignore } } else { break; } } try { syncStartup.interrupt(); syncStartup.join(ONE_SECOND); } catch (InterruptedException e) { // ignore } if (monitor.isCanceled()) { pgdb.destroy(); throw new OperationCanceledException(); } else if (timepass >= launchTimeout) { pgdb.destroy(); String message = MIPlugin.getResourceString("src.GDBDebugger.Error_launch_timeout"); //$NON-NLS-1$ throw new IOException(message); } return pgdb; } /** * Basic process creation hook. Subclasses may override to create the process some other way, * for example by setting the child process's environment. * * @param args * the <tt>gdb</tt> command-line * @return the <tt>gdb</tt> process * @throws IOException * on failure to create the child process * * @since 7.0 */ protected Process createGDBProcess(String[] args) throws IOException { return ProcessFactory.getFactory().exec(args); } public boolean canInterrupt(MIInferior inferior) { return fGDBProcess instanceof Spawner; } public void interrupt(MIInferior inferior) { if (fGDBProcess instanceof Spawner) { if (inferior.isRunning()) { Spawner gdbSpawner = (Spawner) fGDBProcess; if (inferior.isAttachedInferior() && !inferior.isRemoteInferior()) { // not all gdb versions forward the interrupt to an attached // local inferior, so interrupt the inferior directly interruptInferior(inferior); } else { // standard case (gdb launches process) and remote case (gdbserver) gdbSpawner.interrupt(); } waitForInterrupt(inferior); } } } protected boolean waitForInterrupt(MIInferior inferior) { synchronized (inferior) { // Allow MI command timeout for the interrupt to propagate. long maxSec = commandTimeout / ONE_SECOND + 1; for (int i = 0; inferior.isRunning() && i < maxSec; i++) { try { inferior.wait(ONE_SECOND); } catch (InterruptedException e) { } } return inferior.isRunning(); } } /** * Send an interrupt to the inferior process. * * @param inferior */ protected void interruptInferior(MIInferior inferior) { if (fGDBProcess instanceof Spawner) { Spawner gdbSpawner = (Spawner) fGDBProcess; gdbSpawner.raise(inferior.getInferiorPID(), gdbSpawner.INT); } } public int exitValue() { return fGDBProcess.exitValue(); } public int waitFor() throws InterruptedException { return fGDBProcess.waitFor(); } public void destroy() { // We are responsible for closing the streams we have used or else // we will leak pipes. // Bug 345164 try { getErrorStream().close(); } catch (IOException e) {} try { getInputStream().close(); } catch (IOException e) {} try { getOutputStream().close(); } catch (IOException e) {} fGDBProcess.destroy(); } public InputStream getErrorStream() { return fGDBProcess.getErrorStream(); } public InputStream getInputStream() { return gdbInputStream; } public OutputStream getOutputStream() { return fGDBProcess.getOutputStream(); } }