blob: 595641378725f8b57d416ec92f7dbbdc75401c44 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jem.internal.proxy.remote;
/*
*/
import java.io.IOException;
import java.net.Socket;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.Stack;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.*;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamsProxy;
import org.eclipse.jem.internal.proxy.core.*;
/**
* This is the factory registry to use for Remote VM.
* It adds to the standard registry, connection specific information.
*
* This will always hold onto one connection open until termination is requested.
* That way while the IDE is up, the remove vm won't time out.
*/
public class REMProxyFactoryRegistry extends BaseProxyFactoryRegistry {
public static final String REMOTE_REGISTRY_TYPE_ID = "org.eclipse.jem.REMOTE"; //$NON-NLS-1$
protected int fServerPort = 0; // The server port to use when making connections.
protected REMCallbackRegistry fCallbackServer; // The callback server thread for this remote vm.
protected Stack fConnectionPool = new Stack(); // Stack of free connections.
protected static int NUMBER_FREE_CONNECTIONS = 5; // Number of free connections to keep open.
protected IProcess fProcess; // The process that is the server. If null and fServerPort is not zero,
// then this registry is in test mode
// and the server is in same the process.
protected String fName;
protected int fCallbackServerPort;
protected Integer fRegistryKey;
protected REMRegistryController fRegistryController;
protected final static Object TERMINATE_JOB_FAMILY = new Object();
// Package protected because only the ProxyVMStarter should set this flag. It would set it if
// working with a debugger because we don't how long it will be to respond to requests when
// someone is working with a debugger.
boolean fNoTimeouts = false;
// This is set via the static setGlobalNoTimeouts() method, or via debug options flag. It is here so that
// when debugging callbacks, but not debugging remote vm, that no timeouts for any registry will occur.
// Or it can be set through the debug .options flag.
static boolean fGlobalNoTimeouts = "true".equalsIgnoreCase(Platform.getDebugOption(ProxyPlugin.getPlugin().getBundle().getSymbolicName()+ProxyRemoteUtil.NO_TIMEOUTS)); //$NON-NLS-1$;
/**
* Typicall set through the "expression" evaluation of the debugger.
* @param noTimeouts
*
* @since 1.0.0
*/
public static void setGlobalNoTimeouts(boolean noTimeouts) {
fGlobalNoTimeouts = noTimeouts;
}
// An internal thread that locks and waits for the remote vm to register itself.
private WaitForRegistrationThread waitRegistrationThread;
private class WaitForRegistrationThread extends Thread {
public WaitForRegistrationThread() {
super("Wait for remote vm registration thread"); //$NON-NLS-1$
}
/**
* @see java.lang.Thread#run()
*/
public void run() {
// Wait for registration. Put it into a thread so this
// can occur while other stuff goes on. It locks the fConnectionPool
// until done so that the first request for a connection by anyone
// else will wait until this thread is finished.
synchronized(fConnectionPool) {
synchronized(REMProxyFactoryRegistry.this) {
// Notify the main thread that we have the
// connection pool locked.
REMProxyFactoryRegistry.this.notifyAll();
}
synchronized (this) {
// sync on self so that it can be notified when finally receive the registration
long stopTime = System.currentTimeMillis()+60000;
while (waitRegistrationThread != null && (fNoTimeouts || System.currentTimeMillis() < stopTime)) {
try {
Thread.currentThread().wait(60000);
} catch (InterruptedException e) {
}
}
}
}
waitRegistrationThread = null; // No longer exists.
}
}
public REMProxyFactoryRegistry(REMRegistryController registryController, String name) {
super(REMOTE_REGISTRY_TYPE_ID);
fRegistryController = registryController;
fRegistryKey = fRegistryController.registerRegistry(this); // Register the registry with the plugin.
fName = name;
// Get the waitRegistrationThread started before we actually launch remote vm so
// that it is waiting when the callback comes in.
synchronized (this) {
waitRegistrationThread = new WaitForRegistrationThread();
waitRegistrationThread.start();
// Now we will wait until the registration callback has been done. The thread will
// signal us when that is done. This is so that we don't continue on and let
// a work connection be requested before we even got a chance to start waiting
// for the registration.
while(true) {
try {
wait();
break;
} catch (InterruptedException e) {
}
};
}
}
public Integer getRegistryKey() {
return fRegistryKey;
}
public void initializeRegistry(IProcess process) {
fProcess = process;
processListener = new IDebugEventSetListener() {
/**
* @see org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(DebugEvent[])
*/
public void handleDebugEvents(DebugEvent[] events) {
for (int i = 0; i < events.length; i++) {
DebugEvent e = events[i];
if (e.getSource() == fProcess && e.getKind() == DebugEvent.TERMINATE) {
// We terminating too soon. Pop up a msg.
IStreamsProxy stProxy = fProcess.getStreamsProxy();
java.io.StringWriter s = new java.io.StringWriter();
java.io.PrintWriter w = new java.io.PrintWriter(s);
String msg = MessageFormat.format(ProxyRemoteMessages.Proxy_Terminated_too_soon_ERROR_, new Object[] {fName});
w.println(msg);
w.println(ProxyRemoteMessages.VM_TERMINATED_INFO_);
w.println(ProxyRemoteMessages.VM_COMMAND_LINE);
w.println(fProcess.getAttribute(IProcess.ATTR_CMDLINE));
w.println(ProxyRemoteMessages.VM_TERMINATED_LINE1);
w.println(stProxy.getErrorStreamMonitor().getContents());
w.println(ProxyRemoteMessages.VM_TERMINATED_LINE2);
w.println(stProxy.getOutputStreamMonitor().getContents());
w.println(ProxyRemoteMessages.VM_TERMINATED_LINE3);
w.close();
DebugModeHelper dh = new DebugModeHelper();
dh.displayErrorMessage(ProxyRemoteMessages.Proxy_Error_Title, msg);
ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, s.toString(), null));
processListener = null;
DebugPlugin.getDefault().removeDebugEventListener(this);
terminateRegistry();
break;
}
}
}
};
DebugPlugin.getDefault().addDebugEventListener(processListener);
}
private IDebugEventSetListener processListener = null;
/**
* Get the CallbackRegistry
*/
public ICallbackRegistry getCallbackRegistry() {
if (fCallbackServer == null)
fCallbackServer = new REMCallbackRegistry(fName, this);
return fCallbackServer;
}
/**
* This is called by the registry controller to tell
* the registry to terminate with prejudice all
* pending TerminateJobs.
*
*
* @since 1.1.0
*/
public static void cancelAllTerminateJobs() {
IJobManager jobManager = Job.getJobManager();
jobManager.cancel(TERMINATE_JOB_FAMILY);
try {
jobManager.join(TERMINATE_JOB_FAMILY, null);
} catch (OperationCanceledException e) {
} catch (InterruptedException e) {
}
}
private static class TerminateProcess extends Job {
private IProcess process;
public TerminateProcess(IProcess process) {
super(ProxyRemoteMessages.REMProxyFactoryRegistry_Job_TerminateProcess_Title);
this.process = process;
}
public boolean belongsTo(Object family) {
return family == TERMINATE_JOB_FAMILY || super.belongsTo(family);
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
public IStatus run(IProgressMonitor mon) {
try {
// There is no join on a process available, so we will have to
// busy wait. Give it 10 seconds in 1/10 second intervals.
for (int i=0; !process.isTerminated() && i<100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
if (!process.isTerminated()) {
process.terminate();
}
} catch (DebugException e) {
}
return Status.OK_STATUS;
}
}
protected void registryTerminated(boolean wait) {
if (processListener != null) {
// Remove listener cause we are now going to terminate process and don't want premature terminate notice.
// Sometimes in shutdown we are called and the debug plugin may of already been shutdown. In that case the db
// will be null and there is nothing remove listener from.
DebugPlugin db = DebugPlugin.getDefault();
if (db != null)
db.removeDebugEventListener(processListener);
processListener = null;
}
Job tjob = null;
if (waitRegistrationThread != null) {
synchronized (waitRegistrationThread) {
// Still waiting. close it out.
WaitForRegistrationThread wThread = waitRegistrationThread;
waitRegistrationThread = null;
wThread.notifyAll();
}
}
if (fServerPort != 0) {
IREMConnection closeCon = null; // The connection we will use to close the remote vm.
synchronized(fConnectionPool) {
// Now we walk through all of the free connections and close them properly.
Iterator itr = fConnectionPool.iterator();
if (itr.hasNext())
closeCon = (IREMConnection) itr.next();
while (itr.hasNext()) {
IREMConnection con = (IREMConnection) itr.next();
con.close();
}
}
// Now we terminate the server.
if (closeCon == null)
try {
closeCon = getFreeConnection(); // There weren't any free connections, so get a new one so that we can close it.
} catch (IllegalStateException e) {
// Do nothing, don't want to stop termination just because we can't get a connection.
}
if (closeCon != null) {
closeCon.terminateServer(); // We got a connection to terminate (process may of terminated early, so we would not have a conn then).
}
fConnectionPool.clear();
fServerPort = 0;
if (fProcess != null && !fRegistryController.inShutDown()) {
tjob = new TerminateProcess(fProcess);
tjob.setSystem(true);
tjob.schedule();
fProcess = null;
}
}
if (fCallbackServer != null) {
fCallbackServer.requestShutdown();
fCallbackServer = null;
}
fConnectionPool.clear();
fRegistryController.deregisterRegistry(fRegistryKey); // De-register this registry.
if (wait && tjob != null) {
try {
tjob.join();
} catch (InterruptedException e) {
// It timed out, so we'll just go on.
}
}
}
/**
* Return the server port number.
*/
public int getServerPort() {
return fServerPort;
}
/*
* set the server port.
*/
void setServerPort(int serverport) {
fServerPort = serverport;
if (waitRegistrationThread != null) {
synchronized (waitRegistrationThread) {
// Close it out, we are now registered
WaitForRegistrationThread wThread = waitRegistrationThread;
waitRegistrationThread = null;
wThread.notifyAll();
}
}
}
/**
* Get a free connection
* @return
* @throws IllegalStateException - Thrown if a connection cannot be created.
*
* @since 1.0.0
*/
public IREMConnection getFreeConnection() throws IllegalStateException {
Thread thread = Thread.currentThread();
if (thread instanceof REMCallbackThread) {
// The current thread is a call back thread, so just reuse the connection.
// But this thread could actually be trying to access another registry.
// So if this thread is for this registry, use it, if not for this registry, create a new connection.
// But if for this registry AND is already in a transaction, we need a fresh connection.
REMCallbackThread callbackThread = (REMCallbackThread) thread;
if (callbackThread.registry == this && !callbackThread.inTransaction()) {
// This way any calls out to the remote vm will be on same thread as callback caller
// on remote vm because that thread is waiting on this connection for commands.
IREMConnection c = (callbackThread).getConnection();
if (c.isConnected())
return c;
else
throw new IllegalStateException(ProxyRemoteMessages.REMProxyFactoryRegistry_CallbackConnectionNotWorking_EXC_);
}
}
synchronized(fConnectionPool) {
if (!fConnectionPool.isEmpty())
return (IREMConnection) fConnectionPool.pop();
// else we need to allocate one.
return createConnection();
}
}
/**
* Make a new connection.
* @return
* @throws IllegalStateException - Thrown if connection cannot be created.
*
* @since 1.0.0
*/
protected IREMConnection createConnection() throws IllegalStateException {
// If we have a server port, then the server is probably open. If we don't then there is no server.
if (fServerPort != 0) {
// We are putting it off into a thread because there are no timeout capabilities on getting a socket.
// So we need to allow for that.
final Socket[] scArray = new Socket[1];
final boolean[] waiting = new boolean[] {true};
Thread doIt = new Thread(new Runnable() {
public void run() {
try {
Socket sc = new Socket("localhost", fServerPort); //$NON-NLS-1$
synchronized (this) {
if (waiting[0])
scArray[0] = sc;
else
sc.close(); // We are no longer waiting on this thread so close the socket since no one will use it.
}
} catch (IOException e) {
ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e)); //$NON-NLS-1$
}
}
});
doIt.start();
while (true) {
try {
doIt.join(!fNoTimeouts ? 60000 : 0);
synchronized (doIt) {
waiting[0] = false; // To let it know we are no longer waiting
}
break;
} catch (InterruptedException e) {
}
}
if (scArray[0] == null) {
// Log where we are at so we can know where it was we down.
ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", new IllegalStateException(ProxyRemoteMessages.REMProxyFactoryRegistry_ConnectionCreationFailed_INFO_))); //$NON-NLS-1$
throw new IllegalStateException(ProxyRemoteMessages.REMProxyFactoryRegistry_CouldNotCreateSocketConnectionToRemoteVM_EXC_); // Couldn't get one, probably server is down. //$NON-NLS-1$
}
REMConnection connection = new REMConnection(scArray[0], fNoTimeouts);
if (connection.isConnected())
return connection;
// Failed, close the socket.
try {
scArray[0].close();
} catch (IOException e) {
}
} else
ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "No Server to retrieve a connection.", null)); ///$NON-NLS-1$
throw new IllegalStateException(ProxyRemoteMessages.REMProxyFactoryRegistry_CouldNotCreateSocketConnectionToRemoteVM_EXC_);
}
/**
* Free the connection
*/
public void returnConnection(IREMConnection connection) {
if (connection.isConnected()) {
Thread thread = Thread.currentThread();
if (!(thread instanceof REMCallbackThread) || ((REMCallbackThread) thread).getConnection() != connection) {
// We are not a callback thread, or we are but the connection is not for the thread, then the connection
// can be returned.
synchronized (fConnectionPool) {
if (fConnectionPool.size() < NUMBER_FREE_CONNECTIONS)
fConnectionPool.push(connection);
else
connection.close(); // We don't need to maintain more than five free connections.
}
}
}
}
/**
* Release this connection. This means close it out.
*/
public void closeConnection(IREMConnection connection) {
connection.close();
}
public int connectionCount() {
synchronized (fConnectionPool) {
return fConnectionPool.size();
}
}
}