/******************************************************************************* * 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 v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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(); } } }