/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.python.pydev.debug.remote; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.model.IProcess; import org.python.pydev.core.log.Log; import org.python.pydev.debug.model.AbstractDebugTarget; import org.python.pydev.debug.model.PySourceLocator; import org.python.pydev.debug.model.remote.AbstractRemoteDebugger; import com.python.pydev.debug.DebugPluginPrefsInitializer; import com.python.pydev.debug.model.ProcessServer; import com.python.pydev.debug.model.PyDebugTargetServer; /** * After this class is created once, it will stay alive 'forever', as it will block in the server socket accept. * Note that if it for some reason exits (in the case of an exception), the thread will be recreated. */ public class RemoteDebuggerServer extends AbstractRemoteDebugger implements Runnable { /** * 0 == infinite timeout. */ private final static int TIMEOUT = 0; /** * The socket that should be used to listen for clients that want a remote debug session. */ private volatile static ServerSocket serverSocket; /** * The launch that generated this debug server */ private volatile ILaunch launch; /** * Are we terminated? * (starts as if it was terminated) */ private volatile boolean terminated = true; /** * An emulation of a process, to make Eclipse happy (and so that we have somewhere to write to). */ private volatile ProcessServer serverProcess; /** * The iprocess that is created for the debug server */ private volatile IProcess iProcess; /** * Identifies if we're in the middle of a dispose operation (prevent recursive calls). */ private volatile boolean inDispose = false; /** * Identifies if we're in the middle of a stop listening operation (prevent recursive calls). */ private volatile boolean inStopListening = false; /** * This is the server */ private volatile static RemoteDebuggerServer remoteServer; /** * The thread for the debug. */ private volatile static Thread remoteServerThread; /** * Helper to make locking. */ private static final Object lock = new Object(); /** * Private (it's a singleton) */ private RemoteDebuggerServer() { } public static RemoteDebuggerServer getInstance() { synchronized (lock) { if (remoteServer == null) { remoteServer = new RemoteDebuggerServer(); } return remoteServer; } } public void startListening() { synchronized (lock) { stopListening(); //Stops listening if it's currently listening... if (serverSocket == null) { try { serverSocket = new ServerSocket(DebugPluginPrefsInitializer.getRemoteDebuggerPort()); serverSocket.setReuseAddress(true); serverSocket.setSoTimeout(TIMEOUT); } catch (Throwable e) { Log.log(e); } } if (remoteServerThread == null) { remoteServerThread = new Thread(remoteServer); remoteServerThread.start(); } } } public void run() { try { while (true) { //will be blocked here until a client connects (or when the socket is closed) startDebugging(serverSocket.accept()); } } catch (SocketException e) { //ignore (will create a new one later) } catch (Exception e) { Log.log(e); } finally { remoteServerThread = null; } } private void startDebugging(Socket socket) throws InterruptedException { try { Thread.sleep(1000); if (launch != null) { launch.setSourceLocator(new PySourceLocator()); } PyDebugTargetServer target = new PyDebugTargetServer(launch, null, this); target.startTransmission(socket); target.initialize(); this.addTarget(target); } catch (IOException e) { Log.log(e); } } public void stopListening() { synchronized (lock) { if (terminated || this.inStopListening) { return; } this.inStopListening = true; try { terminated = true; try { if (launch != null && launch.canTerminate()) { launch.terminate(); } remoteServer.dispose(); if (serverSocket != null) { try { serverSocket.close(); } catch (Throwable e) { Log.log(e); } serverSocket = null; } } catch (Exception e) { Log.log(e); } launch = null; } finally { this.inStopListening = false; } } } public void dispose() { synchronized (lock) { if (this.inDispose) { return; } this.inDispose = true; try { this.stopListening(); if (launch != null) { for (AbstractDebugTarget target : targets) { launch.removeDebugTarget(target); target.terminate(); } } targets.clear(); } finally { this.inDispose = false; } } } public void disconnect() throws DebugException { //dispose() calls terminate() that calls disconnect() //but this calls stopListening() anyways (it's responsible for checking if //it's already in the middle of something) stopListening(); } public void setLaunch(ILaunch launch, ProcessServer p, IProcess pro) { if (this.launch != null) { this.stopListening(); } terminated = false; //we have a launch... so, it's not finished this.serverProcess = p; this.launch = launch; this.iProcess = pro; } public boolean isTerminated() { return terminated; } public IProcess getIProcess() { return this.iProcess; } public ProcessServer getServerProcess() { return this.serverProcess; } }