e1e758a7b4
Reviewed-by: coleenp, dholmes
754 lines
27 KiB
Java
754 lines
27 KiB
Java
/*
|
|
* Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package nsk.share.jpda;
|
|
|
|
import nsk.share.*;
|
|
|
|
import java.io.*;
|
|
import java.lang.ref.Cleaner;
|
|
import java.net.*;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* This class provides debugger with ability to launch
|
|
* debuggee VM and to make connection to it using JDI connector or
|
|
* JDWP transport.
|
|
* <p>
|
|
* The present version of <code>Binder</code> allows
|
|
* to launch debuggee VM either on local machine (<i>local</i> launch mode),
|
|
* or on remote host using <code>BindServer</code> utility
|
|
* (<i>remote</i> launch mode). Also there is an ability to launch
|
|
* debuggee VM manually as a separate process on local or remote machine
|
|
* (<i>manual</i> launch mode), which is usefull for debugging.
|
|
* All these launching modes are specified by command line option
|
|
* <code>-debugee.launch</code> recognized by <code>DebugeeArgumentHandler</code>.
|
|
* <p>
|
|
* <code>Binder</code> also makes it possible to establish TCP/IP
|
|
* connection between debugger and debuggee throw <code>IOPipe</code>
|
|
* object. This connection allows debugger to communicate with debuggee
|
|
* by exchanging with synchronization messages and data.
|
|
* <p>
|
|
* To launch debuggee VM and bind to it use <code>bindToDebugee()</code>
|
|
* method. This method construct mirror of debugee VM, represented by
|
|
* object of <code>DebugeeProcess</code> class or derived. This mirror object
|
|
* allows to control debuggee VM.
|
|
* <p>
|
|
* See also <code>nsk.share.jdi.Binder</code> and <code>nsk.share.jdwp.Binder</code>
|
|
* classes which provide launching and binding to debuggee VM using specific
|
|
* JDI and JDWP features.
|
|
*
|
|
* @see DebugeeArgumentHandler
|
|
* @see DebugeeProcess
|
|
* @see IOPipe
|
|
* @see BindServer
|
|
*
|
|
* @see nsk.share.jdi.Binder
|
|
* @see nsk.share.jdwp.Binder
|
|
*/
|
|
public class DebugeeBinder extends Log.Logger implements Finalizable {
|
|
|
|
private static final boolean IS_WINDOWS = System.getProperty("os.name")
|
|
.toLowerCase()
|
|
.startsWith("win");
|
|
|
|
public static int TRY_DELAY = 1000; // milliseconds
|
|
|
|
public static int CONNECT_TIMEOUT = 1 * 60 * 1000; // milliseconds
|
|
public static int CONNECT_TRY_DELAY = 2 * 1000; // milliseconds
|
|
public static int CONNECT_TRIES = CONNECT_TIMEOUT / CONNECT_TRY_DELAY;
|
|
|
|
public static int THREAD_TIMEOUT = 2 * CONNECT_TRY_DELAY; // milliseconds
|
|
public static int PING_TIMEOUT = 30 * 1000; // milliseconds
|
|
|
|
public static int SOCKET_TIMEOUT = 2 * 1000; // milliseconds
|
|
public static int SOCKET_LINGER = 1; // milliseconds
|
|
|
|
private static int TRACE_LEVEL_PACKETS = 10;
|
|
private static int TRACE_LEVEL_THREADS = 20;
|
|
private static int TRACE_LEVEL_ACTIONS = 30;
|
|
private static int TRACE_LEVEL_SOCKETS = 40;
|
|
private static int TRACE_LEVEL_IO = 50;
|
|
|
|
/**
|
|
* Default message prefix for <code>Binder</code> object.
|
|
*/
|
|
public static final String LOG_PREFIX = "binder> ";
|
|
|
|
private DebugeeArgumentHandler argumentHandler = null;
|
|
|
|
/**
|
|
* Get version string.
|
|
*/
|
|
public static String getVersion () {
|
|
return "@(#)Binder.java %I% %E%";
|
|
}
|
|
|
|
// -------------------------------------------------- //
|
|
|
|
private BindServerListener bindServerListener = null;
|
|
private ServerSocket pipeServerSocket = null;
|
|
|
|
// -------------------------------------------------- //
|
|
/**
|
|
* Incarnate new Binder obeying the given
|
|
* <code>argumentHandler</code>; and assign the given
|
|
* <code>log</code>.
|
|
*/
|
|
public DebugeeBinder (DebugeeArgumentHandler argumentHandler, Log log) {
|
|
super(log, LOG_PREFIX);
|
|
this.argumentHandler = argumentHandler;
|
|
|
|
registerCleanup();
|
|
}
|
|
|
|
/**
|
|
* Get argument handler of this binder object.
|
|
*/
|
|
DebugeeArgumentHandler getArgumentHandler() {
|
|
return argumentHandler;
|
|
}
|
|
|
|
// -------------------------------------------------- //
|
|
|
|
/**
|
|
* Wait for given thread finished for THREAD_TIMEOUT timeout and
|
|
* interrupt this thread if not finished.
|
|
*
|
|
* @param thr thread to wait for
|
|
* @param logger to write log messages to
|
|
*/
|
|
public static void waitForThread(Thread thr, Log.Logger logger) {
|
|
waitForThread(thr, THREAD_TIMEOUT, logger);
|
|
}
|
|
|
|
/**
|
|
* Wait for given thread finished for specified timeout and
|
|
* interrupt this thread if not finished.
|
|
*
|
|
* @param thr thread to wait for
|
|
* @param millisecs timeout in milliseconds
|
|
* @param logger to write log messages to
|
|
*/
|
|
public static void waitForThread(Thread thr, long millisecs, Log.Logger logger) {
|
|
if (thr != null) {
|
|
if (thr.isAlive()) {
|
|
try {
|
|
logger.trace(TRACE_LEVEL_THREADS, "Waiting for thread: " + thr.getName());
|
|
thr.join(millisecs);
|
|
} catch (InterruptedException e) {
|
|
e.printStackTrace(logger.getOutStream());
|
|
throw new Failure ("Thread interrupted while waiting for another thread:\n\t"
|
|
+ e);
|
|
} finally {
|
|
if (thr.isAlive()) {
|
|
logger.trace(TRACE_LEVEL_THREADS, "Interrupting not finished thread: " + thr);
|
|
thr.interrupt();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Make preperation for IOPipe connection before starting debugee VM process.
|
|
* May change options in the passed <code>argumentHandler</code>.
|
|
*/
|
|
public void prepareForPipeConnection(DebugeeArgumentHandler argumentHandler) {
|
|
if (argumentHandler.isTransportAddressDynamic()) {
|
|
try {
|
|
pipeServerSocket = new ServerSocket();
|
|
pipeServerSocket.setReuseAddress(false);
|
|
pipeServerSocket.bind(null);
|
|
|
|
} catch (IOException e) {
|
|
e.printStackTrace(getOutStream());
|
|
throw new Failure("Caught IOException while binding for IOPipe connection: \n\t"
|
|
+ e);
|
|
}
|
|
|
|
int port = pipeServerSocket.getLocalPort();
|
|
argumentHandler.setPipePortNumber(port);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return already bound ServerSocket for IOPipe connection or null.
|
|
*/
|
|
protected ServerSocket getPipeServerSocket() {
|
|
return pipeServerSocket;
|
|
}
|
|
|
|
/**
|
|
* Close ServerSocket used for IOPipeConnection if any.
|
|
*/
|
|
private void closePipeServerSocket() {
|
|
if (pipeServerSocket != null) {
|
|
try {
|
|
pipeServerSocket.close();
|
|
} catch (IOException e) {
|
|
println("# WARNING: Caught IOException while closing ServerSocket used for IOPipe connection: \n\t"
|
|
+ e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------- //
|
|
|
|
/**
|
|
* Make environment for launching JVM process.
|
|
*/
|
|
public String[] makeProcessEnvironment() {
|
|
/*
|
|
String env = new String[0];
|
|
return env;
|
|
*/
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Launch process by the specified command line.
|
|
*
|
|
* @throws IOException if I/O error occured while launching process
|
|
*/
|
|
public Process launchProcess(String cmdLine) throws IOException {
|
|
String env[] = makeProcessEnvironment();
|
|
return Runtime.getRuntime().exec(cmdLine, env);
|
|
}
|
|
|
|
/**
|
|
* Launch process by the arguments array.
|
|
*
|
|
* @throws IOException if I/O error occured while launching process
|
|
*/
|
|
public Process launchProcess(String[] args) throws IOException {
|
|
String env[] = makeProcessEnvironment();
|
|
return Runtime.getRuntime().exec(args, env);
|
|
}
|
|
|
|
/**
|
|
* Make string representation of debuggee VM transport address according
|
|
* to current command line options.
|
|
*/
|
|
public String makeTransportAddress() {
|
|
String address = null;
|
|
if (argumentHandler.isSocketTransport()) {
|
|
if (argumentHandler.isListeningConnector()) {
|
|
address = argumentHandler.getTestHost()
|
|
+ ":" + argumentHandler.getTransportPort();
|
|
} else {
|
|
address = argumentHandler.getTransportPort();
|
|
}
|
|
} else if (argumentHandler.isShmemTransport() ) {
|
|
address = argumentHandler.getTransportSharedName();
|
|
} else {
|
|
throw new TestBug("Undefined transport type: "
|
|
+ argumentHandler.getTransportType());
|
|
}
|
|
return address;
|
|
}
|
|
|
|
/**
|
|
* Make command line to launch debugee VM as a string using given quote symbol,
|
|
* using specified <code>transportAddress</code> for JDWP connection.
|
|
*/
|
|
public String makeCommandLineString(String classToExecute, String transportAddress, String quote) {
|
|
String[] args = makeCommandLineArgs(classToExecute, transportAddress);
|
|
return ArgumentParser.joinArguments(args, quote);
|
|
}
|
|
|
|
/**
|
|
* Make command line to launch debugee VM as a string using given quote symbol.
|
|
*/
|
|
public String makeCommandLineString(String classToExecute, String quote) {
|
|
return makeCommandLineString(classToExecute, makeTransportAddress(), quote);
|
|
}
|
|
|
|
/**
|
|
* Make command line to launch debugee VM as a string using default quote symbol,
|
|
* using specified <code>transportAddress</code> for JDWP connection.
|
|
*/
|
|
/*
|
|
public String makeCommandLineString(String classToExecute, String transportAddress) {
|
|
return makeCommandLineString(classToExecute, transportAddress, "\"");
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Make command line to launch debugee VM as a string using default quote symbol.
|
|
*/
|
|
/*
|
|
public String makeCommandLineString(String classToExecute) {
|
|
return makeCommandLineString(classToExecute, makeTransportAddress());
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Make command line to launch debugee VM as an array of arguments,
|
|
* using specified <code>transportAddress</code> for JDWP connection.
|
|
*/
|
|
public String[] makeCommandLineArgs(String classToExecute, String transportAddress) {
|
|
Vector<String> args = new Vector<String>();
|
|
|
|
args.add(argumentHandler.getLaunchExecPath());
|
|
|
|
String javaOpts = argumentHandler.getLaunchOptions();
|
|
if (javaOpts != null && javaOpts.length() > 0) {
|
|
StringTokenizer st = new StringTokenizer(javaOpts);
|
|
|
|
while (st.hasMoreTokens()) {
|
|
args.add(st.nextToken());
|
|
}
|
|
}
|
|
|
|
/*
|
|
String classPath = System.getProperty("java.class.path");
|
|
args.add("-classpath")
|
|
args.add(classPath);
|
|
*/
|
|
|
|
args.add("-Xdebug");
|
|
|
|
String server;
|
|
if (argumentHandler.isAttachingConnector()) {
|
|
server = "y";
|
|
} else {
|
|
server = "n";
|
|
}
|
|
|
|
String jdwpArgs = "-Xrunjdwp:"
|
|
+ "server=" + server
|
|
+ ",transport=" + argumentHandler.getTransportName()
|
|
+ ",address=" + transportAddress
|
|
+ ",includevirtualthreads=y";
|
|
|
|
if (! argumentHandler.isDefaultJVMDIStrictMode()) {
|
|
if (argumentHandler.isJVMDIStrictMode())
|
|
jdwpArgs += ",strict=y";
|
|
else
|
|
jdwpArgs += ",strict=n";
|
|
}
|
|
|
|
args.add(jdwpArgs);
|
|
|
|
if(System.getProperty("main.wrapper") != null) {
|
|
args.add(MainWrapper.class.getName());
|
|
args.add(System.getProperty("main.wrapper"));
|
|
}
|
|
|
|
if (classToExecute != null) {
|
|
StringTokenizer st = new StringTokenizer(classToExecute);
|
|
|
|
while (st.hasMoreTokens()) {
|
|
args.add(st.nextToken());
|
|
}
|
|
}
|
|
|
|
String[] rawArgs = argumentHandler.getRawArguments();
|
|
for (int i = 0; i < rawArgs.length; i++) {
|
|
String rawArg = rawArgs[i];
|
|
// " has to be escaped on windows
|
|
if (IS_WINDOWS) {
|
|
rawArg = rawArg.replace("\"", "\\\"");
|
|
}
|
|
args.add(rawArg);
|
|
}
|
|
|
|
String[] argsArray = new String[args.size()];
|
|
for (int i = 0; i < args.size(); i++) {
|
|
argsArray[i] = (String) args.elementAt(i);
|
|
}
|
|
|
|
return argsArray;
|
|
}
|
|
|
|
/**
|
|
* Make command line to launch debugee VM as an array of arguments.
|
|
*/
|
|
public String[] makeCommandLineArgs(String classToExecute) {
|
|
return makeCommandLineArgs(classToExecute, makeTransportAddress());
|
|
}
|
|
|
|
/**
|
|
* Make connection to remote BindServer and start BindServerListener thread.
|
|
*
|
|
* @throws IOException if I/O error occured while connecting
|
|
*/
|
|
public void connectToBindServer(String taskID) {
|
|
if (bindServerListener != null) {
|
|
throw new Failure("Connection to BindServer already exists");
|
|
}
|
|
try {
|
|
bindServerListener = new BindServerListener(this);
|
|
bindServerListener.setDaemon(true);
|
|
bindServerListener.connect(taskID);
|
|
bindServerListener.start();
|
|
} catch (IOException e) {
|
|
e.printStackTrace(getOutStream());
|
|
throw new Failure("Caught exception while connecting to BindServer:\n\t" + e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Split string into list of substrings using specified separator.
|
|
*/
|
|
private static String[] splitString(String givenString, String separator) {
|
|
Vector<String> tmpList = new Vector<String>();
|
|
StringTokenizer tokenizer = new StringTokenizer(givenString, separator);
|
|
while(tokenizer.hasMoreTokens()) {
|
|
tmpList.add(tokenizer.nextToken());
|
|
}
|
|
String[] list = new String[tmpList.size()];
|
|
for (int i = 0; i < tmpList.size(); i++) {
|
|
list[i] = tmpList.elementAt(i);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Send command to remote <code>BindServer</code> and receive reply.
|
|
*
|
|
* @throws IOException if I/O error occured while launching process
|
|
*/
|
|
public synchronized Object sendRemoteCommand(Object command) {
|
|
try {
|
|
bindServerListener.sendCommand(command);
|
|
Object reply = bindServerListener.getReply();
|
|
return reply;
|
|
} catch (IOException e) {
|
|
e.printStackTrace(log.getOutStream());
|
|
throw new Failure("Unexpected exception while sending command to BindServer:\n\t"
|
|
+ e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Launch remote process using request to <code>BindServer</code>.
|
|
*
|
|
* @throws IOException if I/O error occured
|
|
*/
|
|
public void launchRemoteProcess(String[] args) throws IOException {
|
|
String pathSeparator = System.getProperty("path.separator");
|
|
BindServer.LaunchDebugee command =
|
|
new BindServer.LaunchDebugee(args,
|
|
System.getProperty("file.separator"),
|
|
System.getProperty("user.dir"),
|
|
splitString(System.getProperty("java.library.path"), pathSeparator),
|
|
splitString(System.getProperty("java.class.path"), pathSeparator),
|
|
splitString(System.getProperty("java.library.path"), pathSeparator));
|
|
|
|
Object reply = sendRemoteCommand(command);
|
|
if (reply instanceof BindServer.OK) {
|
|
// do nothing
|
|
} else if (reply instanceof BindServer.RequestFailed) {
|
|
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
|
|
throw new Failure("BindServer error: " + castedReply.reason);
|
|
} else {
|
|
throw new Failure("Wrong reply from BindServer: " + reply);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return exit status of the remotely launched process
|
|
* using request to <code>BindServer</code>.
|
|
*/
|
|
public int getRemoteProcessStatus () {
|
|
Object reply = sendRemoteCommand(new BindServer.DebugeeExitCode());
|
|
if (reply instanceof BindServer.OK) {
|
|
BindServer.OK castedReply = (BindServer.OK)reply;
|
|
return (int)castedReply.info;
|
|
} else if (reply instanceof BindServer.CaughtException) {
|
|
BindServer.CaughtException castedReply = (BindServer.CaughtException)reply;
|
|
throw new IllegalThreadStateException(castedReply.reason);
|
|
} else if (reply instanceof BindServer.RequestFailed) {
|
|
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
|
|
throw new Failure("BindServer error: " + castedReply.reason);
|
|
} else {
|
|
throw new Failure("Wrong reply from BindServer: " + reply);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether the remotely launched process has been terminated
|
|
* using request to <code>BindServer</code>.
|
|
*/
|
|
public boolean isRemoteProcessTerminated () {
|
|
try {
|
|
int value = getRemoteProcessStatus();
|
|
return true;
|
|
} catch (IllegalThreadStateException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------- //
|
|
|
|
/**
|
|
* Kill the remotely launched process
|
|
* using request to <code>BindServer</code>.
|
|
*/
|
|
public void killRemoteProcess () {
|
|
Object reply = sendRemoteCommand(new BindServer.KillDebugee());
|
|
if (reply instanceof BindServer.OK) {
|
|
return;
|
|
} else if (reply instanceof BindServer.RequestFailed) {
|
|
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
|
|
throw new Failure("BindServer error: " + castedReply.reason);
|
|
} else {
|
|
throw new Failure("Wrong reply from BindServer: " + reply);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait until the remotely launched process exits or crashes
|
|
* using request to <code>BindServer</code>.
|
|
*/
|
|
public int waitForRemoteProcess () {
|
|
|
|
Object reply = sendRemoteCommand(new BindServer.WaitForDebugee(0));
|
|
if (reply instanceof BindServer.OK) {
|
|
BindServer.OK castedReply = (BindServer.OK)reply;
|
|
return (int)castedReply.info;
|
|
} else if (reply instanceof BindServer.RequestFailed) {
|
|
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
|
|
throw new Failure("BindServer error: " + castedReply.reason);
|
|
} else {
|
|
throw new Failure("Wrong reply from BindServer: " + reply);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close binder by closing all started threads.
|
|
*/
|
|
public void close() {
|
|
if (bindServerListener != null) {
|
|
bindServerListener.close();
|
|
}
|
|
closePipeServerSocket();
|
|
}
|
|
|
|
/**
|
|
* Finalize binder by invoking <code>close()</code>.
|
|
*
|
|
*/
|
|
public void cleanup() {
|
|
close();
|
|
}
|
|
|
|
/**
|
|
* Separate thread for listening connection from <code>BindServer</code>.
|
|
*/
|
|
private class BindServerListener extends Thread {
|
|
private SocketConnection connection = null;
|
|
private Log.Logger logger = null;
|
|
|
|
/** List of received responses from <code>BindServer</code>. */
|
|
private LinkedList<BindServer.Response> replies = new LinkedList<BindServer.Response>();
|
|
|
|
/**
|
|
* Make thread.
|
|
*/
|
|
public BindServerListener(Log.Logger logger) {
|
|
this.logger = logger;
|
|
}
|
|
|
|
/**
|
|
* Establish connection to <code>BindServer</code>.
|
|
*/
|
|
public void connect(String taskID) throws IOException {
|
|
String host = argumentHandler.getDebugeeHost();
|
|
int port = argumentHandler.getBindPortNumber();
|
|
display("Connecting to BindServer: " + host + ":" + port);
|
|
connection = new SocketConnection(logger, "BindServer");
|
|
// connection.setPingTimeout(DebugeeBinder.PING_TIMEOUT);
|
|
connection.attach(host, port);
|
|
handshake(taskID);
|
|
}
|
|
|
|
/**
|
|
* Receive OK(version) from BindServer and check received version number.
|
|
*/
|
|
private void handshake(String taskID) {
|
|
// receive OK(version)
|
|
trace(TRACE_LEVEL_ACTIONS, "Waiting for initial OK(version) from BindServer");
|
|
Object reply = connection.readObject();
|
|
trace(TRACE_LEVEL_ACTIONS, "Got initial OK(version) from BindServer: " + reply);
|
|
if (reply instanceof BindServer.RequestFailed) {
|
|
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
|
|
trace(TRACE_LEVEL_ACTIONS, "Reply is RequestFailed: throw Failure");
|
|
throw new Failure("BindServer error: " + castedReply.reason);
|
|
} else if (reply instanceof BindServer.OK) {
|
|
BindServer.OK castedReply = (BindServer.OK)reply;
|
|
trace(TRACE_LEVEL_ACTIONS, "Reply is OK: check BindServer version");
|
|
if (castedReply.info != BindServer.VERSION) {
|
|
throw new Failure("Wrong version of BindServer: " + castedReply.info
|
|
+ " (expected: " + BindServer.VERSION + ")");
|
|
}
|
|
display("Connected to BindServer: version " + castedReply.info);
|
|
} else {
|
|
trace(TRACE_LEVEL_ACTIONS, "Reply is unknown: throw Failure");
|
|
throw new Failure("Wrong reply from BindServer: " + reply);
|
|
}
|
|
|
|
// send TaskID(id)
|
|
try {
|
|
trace(TRACE_LEVEL_ACTIONS, "Sending TaskID(id) to BindServer");
|
|
sendCommand(new BindServer.TaskID(taskID));
|
|
trace(TRACE_LEVEL_ACTIONS, "Sent TaskID(id) to BindServer");
|
|
} catch (IOException e) {
|
|
throw new Failure("Caught IOException while sending TaskID(id) to BindServer:\n\t"
|
|
+ e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if thread is connected to <code>BindServer</code>.
|
|
*/
|
|
public boolean isConnected() {
|
|
return (connection != null && connection.isConnected());
|
|
}
|
|
|
|
/**
|
|
* Send a command to </code>BindServer</code>.
|
|
*/
|
|
public synchronized void sendCommand(Object command) throws IOException {
|
|
connection.writeObject(command);
|
|
}
|
|
|
|
/**
|
|
* Receive response from <code>BindServer</code>.
|
|
*/
|
|
public Object getReply() {
|
|
synchronized (replies) {
|
|
while (replies.isEmpty()) {
|
|
if (!isConnected()) {
|
|
throw new Failure("No reply from BindServer: connection lost");
|
|
}
|
|
try {
|
|
replies.wait(TRY_DELAY);
|
|
} catch (InterruptedException e) {
|
|
e.printStackTrace(getOutStream());
|
|
throw new Failure("Thread interrupted while waiting for reply from BindServer:\n\t"
|
|
+ e);
|
|
}
|
|
}
|
|
Object reply = replies.removeFirst();
|
|
if (reply == null) {
|
|
throw new Failure("No reply from BindServer: connection lost");
|
|
}
|
|
return reply;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add response object to the list of received responses.
|
|
*/
|
|
private void addReply(BindServer.Response reply) {
|
|
synchronized (replies) {
|
|
replies.add(reply);
|
|
replies.notifyAll();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read packets from <code>BindServer<code> connection and
|
|
* notify waiting thread if response or IOPipe message received.
|
|
* Received lines of redirected streams are put into log.
|
|
*/
|
|
public void run() {
|
|
trace(TRACE_LEVEL_THREADS, "BindServerListener thread started");
|
|
try {
|
|
for (;;) {
|
|
Object reply = connection.readObject();
|
|
if (reply == null) {
|
|
break;
|
|
} else if (reply instanceof BindServer.Disconnect) {
|
|
reply = null;
|
|
trace(TRACE_LEVEL_ACTIONS, "Packet is Disconnect: close connection");
|
|
break;
|
|
} else if (reply instanceof BindServer.RedirectedStream) {
|
|
BindServer.RedirectedStream castedReply = (BindServer.RedirectedStream)reply;
|
|
trace(TRACE_LEVEL_ACTIONS, "Packet is RedirectedStream: put message into log");
|
|
log.println(castedReply.line);
|
|
} else if (reply instanceof BindServer.Response) {
|
|
BindServer.Response castedReply = (BindServer.Response)reply;
|
|
trace(TRACE_LEVEL_ACTIONS, "Packet is reply: notify all threads waiting for reply");
|
|
addReply(castedReply);
|
|
} else {
|
|
trace(TRACE_LEVEL_ACTIONS, "Packet is unknown: throw Failure");
|
|
throw new Failure("Wrong reply from BindServer: " + reply);
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace(getOutStream());
|
|
complain("Caught exception while reading packets from BindServer:\n\t" + e);
|
|
} finally {
|
|
closeConnection();
|
|
addReply(null);
|
|
trace(TRACE_LEVEL_THREADS, "BindServerListener thread finished");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send Disconnect command to </code>BindServer</code>.
|
|
*/
|
|
public void disconnect() {
|
|
if (connection == null) return;
|
|
try {
|
|
sendCommand(new BindServer.Disconnect());
|
|
} catch (IOException e) {
|
|
display("Caught IOException while requesting disconnection with BindServer");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close socket connection.
|
|
*/
|
|
public void closeConnection() {
|
|
if (connection != null) {
|
|
connection.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for thread finished in the specified timeout or interrupt it.
|
|
*/
|
|
public void waitForThread(long millis) {
|
|
DebugeeBinder.waitForThread(this, millis, logger);
|
|
}
|
|
|
|
/**
|
|
* Close this thread by waiting for it finishes or interrupt it
|
|
* and close socket connection.
|
|
*/
|
|
public void close() {
|
|
disconnect();
|
|
waitForThread(DebugeeBinder.THREAD_TIMEOUT);
|
|
closeConnection();
|
|
}
|
|
|
|
} // BindServerListener
|
|
|
|
} // DebugeeBinder
|