1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-31 12:55:40 +02:00

Bug 467833 - Wait Shell initialization and writer thread leak

- Introduce RSE_SHELL_READY_PING to optionally ping the target every
  specified milliseconds after connect (maximum 10 times), until an
  "echo" command actually gets executed.
- Fix a shell writer thread leak

Change-Id: Ibc4632b59fd8802ffdd73a3563d3c5aea5b1192f
Signed-off-by: Teodor Madan <teodor.madan@freescale.com>
This commit is contained in:
Teodor Madan 2015-05-22 18:59:36 +03:00
parent 6e72cc76ea
commit 135dc73077
3 changed files with 151 additions and 7 deletions

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2006, 2011 IBM Corporation and others.
* Copyright (c) 2006, 2015 IBM Corporation 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
@ -23,6 +23,7 @@
* Rob Stryker (JBoss) - [335059] TerminalServiceShellOutputReader logs error when hostShell.exit() is called
* Martin Oberhuber (Wind River) - [356132] wait for initial output
* Ioana Grigoropol (Intel) - [411343] Provide access to readers in host shell
* Teodor Madan (Freescale) - [467833] Wait shell initialization
*******************************************************************************/
package org.eclipse.rse.internal.services.shells;
@ -35,8 +36,15 @@ import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.rse.internal.services.Activator;
import org.eclipse.rse.services.clientserver.PathUtility;
import org.eclipse.rse.services.shells.AbstractHostShell;
import org.eclipse.rse.services.shells.IHostOutput;
import org.eclipse.rse.services.shells.IHostShellChangeEvent;
import org.eclipse.rse.services.shells.IHostShellOutputListener;
import org.eclipse.rse.services.shells.IHostShellOutputReader;
import org.eclipse.rse.services.terminals.ITerminalShell;
@ -44,6 +52,8 @@ import org.eclipse.rse.services.terminals.ITerminalShell;
* @since 3.1
*/
public class TerminalServiceHostShell extends AbstractHostShell {
private static final String RSE_SHELL_READY_PING = "RSE_SHELL_READY_PING"; //$NON-NLS-1$
private static final String ECLIPSE_TEST_KEY = "_ping"; //$NON-NLS-1$
public static final String SHELL_INVOCATION = ">"; //$NON-NLS-1$
@ -75,8 +85,6 @@ public class TerminalServiceHostShell extends AbstractHostShell {
fBufReader.read();
fBufReader.reset();
fStdoutHandler = new TerminalServiceShellOutputReader(this, fBufReader, false);
fStderrHandler = new TerminalServiceShellOutputReader(this, null, true);
OutputStream outputStream = fTerminalShell.getOutputStream();
if (encoding != null) {
// use specified encoding
@ -90,6 +98,14 @@ public class TerminalServiceHostShell extends AbstractHostShell {
fShellWriter = new TerminalServiceShellWriterThread(
outputWriter);
}
// if stdout stream is closed, pass the shell writer to be closed as well
fStdoutHandler = new TerminalServiceShellOutputReader(this, fBufReader, false, fShellWriter);
fStderrHandler = new TerminalServiceShellOutputReader(this, null, true);
int pingMsec = getReadyPingMsec();
if (SHELL_INVOCATION.equals(commandToRun) && pingMsec > 0) {
doReadyPing("echo " + ECLIPSE_TEST_KEY + "'>'", ECLIPSE_TEST_KEY, pingMsec, 10); //$NON-NLS-1$ //$NON-NLS-2$
}
if (initialWorkingDirectory != null
&& initialWorkingDirectory.length() > 0
@ -97,7 +113,7 @@ public class TerminalServiceHostShell extends AbstractHostShell {
&& !initialWorkingDirectory.equals("Command Shell") //$NON-NLS-1$ //FIXME workaround for bug 153047
) {
writeToShell("cd " + PathUtility.enQuoteUnix(initialWorkingDirectory)); //$NON-NLS-1$
}
}
if (SHELL_INVOCATION.equals(commandToRun)) {
writeToShell(getPromptCommand());
} else if (commandToRun != null && commandToRun.length() > 0) {
@ -123,6 +139,96 @@ public class TerminalServiceHostShell extends AbstractHostShell {
}
/**
* Perform a test if remote shell can execute commands. Will send <code>pingCmd</code> command to remote shell for execution
* until a line starting with <code>expectedResponse</code> is received. Ping command will be sent up to <code>maxPing</code>
* times waiting <code>ttywait</code> milliseconds between pings.
*
* @param pingCmd - ping command. e.g. <code>"echo test"</code>
* @param expectedResponse - exepcted result, e.g. <code>"test"</code>
* @param msecPerPing milliseconds to wait between each ping command
* @param maxPing maximum number of attempts
*
* @return <code>true</code> if expected response has been received or <code>false</code> if timeout was raised before receiving
* expected response.
* <br>When method returns <code>false</code>, ping commands still might have been executed and the response can arrive later
*/
public boolean doReadyPing(String pingCmd, final String expectedResponse, int msecPerPing, int maxPing) {
// wait for handshake:
// send repeatable commands
// --> echo <eclipse_key>
// until receiving a line that starts with the key,
// <-- <eclipse_key>
// this differentiates from a plan echo that will contain "echo" command as well.
final boolean[] received_handshake = new boolean[1];
received_handshake[0] = false;
final Object lock = new Object();
IHostShellOutputListener echoListener = new IHostShellOutputListener() {
public void shellOutputChanged(IHostShellChangeEvent event) {
IHostOutput[] lines = event.getLines();
for (int i = lines.length-1; i>=0; i--) {
String line = lines[i].getString();
if (line.startsWith(expectedResponse)) {
synchronized (lock) {
// we are done waiting;
received_handshake[0] = true;
lock.notifyAll();
break;
}
}
}
}
};
fStdoutHandler.addOutputListener(echoListener);
int ping = 1;
do {
// send periodically the handshake:
writeToShell(pingCmd);
synchronized (lock) {
try {
lock.wait(msecPerPing);
} catch (InterruptedException ex) {
break;
}
if (received_handshake[0]) {
break;
}
}
// limit number of pings in case of fundamental issue
} while (!fStdoutHandler.isFinished() && ping++ <= maxPing);
// remove echo listener from output handler
fStdoutHandler.removeOutputListener(echoListener);
return received_handshake[0];
}
/**
* @return msec to wait between shell ready ping commands; value 0 is for no ping
*/
protected int getReadyPingMsec() {
int ttyWait = 0; //default is to not wait after receiving characters
// See bug 467899:
// Until an API is created to read RSE service properties use system properties to enable the behavior
String waitVal = System.getProperty(RSE_SHELL_READY_PING);
if (waitVal != null) {
try {
ttyWait = Integer.parseInt(waitVal);
// limit the lower limit of the ping to avoid spamming target ssh server.
if (ttyWait < 200) {
ttyWait = 200;
}
} catch (NumberFormatException e) {
// ignore invalid value
IStatus status = new Status(IStatus.WARNING, Activator.PLUGIN_ID,
RSE_SHELL_READY_PING + " property should be an integer. Actually is '" + waitVal + "'", null); //$NON-NLS-1$ //$NON-NLS-2$
Activator.getDefault().getLog().log(status);
}
}
return ttyWait;
}
public void exit() {
if (fShellWriter != null) {
fShellWriter.stopThread();

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2006, 2011 IBM Corporation and others.
* Copyright (c) 2006, 2015 IBM Corporation 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
@ -17,17 +17,20 @@
* Anna Dushistova (MontaVista) - adapted from SshShellOutputReader
* Anna Dushistova (MontaVista) - [240523] [rseterminals] Provide a generic adapter factory that adapts any ITerminalService to an IShellService
* Rob Stryker (JBoss) - [335059] TerminalServiceShellOutputReader logs error when hostShell.exit() is called
* Teodor Madan (Freescale) - [467833] Fix leaking shell writer thread
*******************************************************************************/
package org.eclipse.rse.internal.services.shells;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import org.eclipse.rse.internal.services.Activator;
import org.eclipse.rse.services.shells.AbstractHostShellOutputReader;
import org.eclipse.rse.services.shells.IHostOutput;
import org.eclipse.rse.services.shells.IHostShell;
import org.eclipse.rse.services.shells.IHostShellOutputListener;
import org.eclipse.rse.services.shells.SimpleHostOutput;
/**
@ -39,10 +42,17 @@ public class TerminalServiceShellOutputReader extends
private volatile Thread fReaderThread = null;
private volatile boolean isCanceled = false;
private String fPromptChars = ">$%#]"; //Characters we accept as the end of a prompt //$NON-NLS-1$;
private Closeable closable;
public TerminalServiceShellOutputReader(IHostShell hostShell,
BufferedReader reader, boolean isErrorReader) {
this(hostShell, reader, isErrorReader, null);
}
public TerminalServiceShellOutputReader(IHostShell hostShell,
BufferedReader reader, boolean isErrorReader, Closeable closable) {
super(hostShell, isErrorReader);
this.closable = closable;
setName("Terminal Service ShellOutputReader" + getName()); //$NON-NLS-1$
fReader = reader;
}
@ -174,4 +184,25 @@ public class TerminalServiceShellOutputReader extends
fReaderThread.interrupt();
}
}
/* (non-Javadoc)
* @see org.eclipse.rse.services.shells.AbstractHostShellOutputReader#dispose()
*/
public void dispose() {
super.dispose();
if (closable != null)
try {
closable.close();
} catch (IOException e) {
Activator.getDefault().logException(e);
}
}
/**
* Remove a registered output listener.
* @param listener to be removed.
*/
public void removeOutputListener(IHostShellOutputListener listener) {
_listeners.remove(listener);
}
}

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2006, 2011 Wind River Systems, Inc. and others.
* Copyright (c) 2006, 2015 Wind River Systems, Inc. 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
@ -10,15 +10,18 @@
* Anna Dushistova (MontaVista) - adapted from SshShellWriterThread
* Anna Dushistova (MontaVista) - [240523] [rseterminals] Provide a generic adapter factory that adapts any ITerminalService to an IShellService
* Rob Stryker (JBoss) - [335059] TerminalServiceShellOutputReader logs error when hostShell.exit() is called
* Teodor Madan (Freescale) - [467833] Fix leaking shell writer thread
*******************************************************************************/
package org.eclipse.rse.internal.services.shells;
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @since 3.1
*/
public class TerminalServiceShellWriterThread extends Thread {
public class TerminalServiceShellWriterThread extends Thread implements Closeable{
private PrintWriter fOutputWriter;
private String fNextCommand;
private volatile boolean fIsCancelled;
@ -99,4 +102,8 @@ public class TerminalServiceShellWriterThread extends Thread {
}
}
public void close() throws IOException {
stopThread();
}
}