/******************************************************************************* * Copyright (c) 2017 Microsoft 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: * Microsoft Corporation - initial API and implementation *******************************************************************************/ package com.microsoft.java.debug.plugin.internal; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import com.microsoft.java.debug.core.Configuration; import com.microsoft.java.debug.core.adapter.ProtocolServer; public class JavaDebugServer implements IDebugServer { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); private static JavaDebugServer singletonInstance; private ServerSocket serverSocket = null; private boolean isStarted = false; private ExecutorService executor = null; private JavaDebugServer() { try { this.serverSocket = new ServerSocket(0, 1); } catch (IOException e) { logger.log(Level.SEVERE, String.format("Failed to create Java Debug Server: %s", e.toString()), e); } } /** * Gets the single instance of JavaDebugServer. * @return the JavaDebugServer instance */ public static synchronized IDebugServer getInstance() { if (singletonInstance == null) { singletonInstance = new JavaDebugServer(); } return singletonInstance; } /** * Gets the server port. */ @Override public synchronized int getPort() { if (this.serverSocket != null) { return this.serverSocket.getLocalPort(); } return -1; } /** * Starts the server if it's not started yet. */ @Override public synchronized void start() { if (this.serverSocket != null && !this.isStarted) { this.isStarted = true; this.executor = new ThreadPoolExecutor(0, 100, 30L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); // Execute eventLoop in a new thread. new Thread(new Runnable() { @Override public void run() { while (true) { try { // Allow server socket to service multiple clients at the same time. // When a request comes in, create a connection thread to process it. // Then the server goes back to listen for new connection request. Socket connection = serverSocket.accept(); executor.submit(createConnectionTask(connection)); } catch (IOException e) { logger.log(Level.SEVERE, String.format("Setup socket connection exception: %s", e.toString()), e); closeServerSocket(); // If exception occurs when waiting for new client connection, shut down the connection pool // to make sure no new tasks are accepted. But the previously submitted tasks will continue to run. shutdownConnectionPool(false); return; } } } }, "Java Debug Server").start(); } } @Override public synchronized void stop() { closeServerSocket(); shutdownConnectionPool(true); } private synchronized void closeServerSocket() { if (serverSocket != null) { try { logger.info("Close debugserver socket port " + serverSocket.getLocalPort()); serverSocket.close(); } catch (IOException e) { logger.log(Level.SEVERE, String.format("Close ServerSocket exception: %s", e.toString()), e); } } serverSocket = null; } private synchronized void shutdownConnectionPool(boolean now) { if (this.executor != null) { if (now) { this.executor.shutdownNow(); } else { this.executor.shutdown(); } } } private Runnable createConnectionTask(Socket connection) { return new Runnable() { @Override public void run() { try { ProtocolServer protocolServer = new ProtocolServer(connection.getInputStream(), connection.getOutputStream(), JdtProviderContextFactory.createProviderContext()); // protocol server will dispatch request and send response in a while-loop. protocolServer.run(); } catch (IOException e) { logger.log(Level.SEVERE, String.format("Socket connection exception: %s", e.toString()), e); } finally { logger.info("Debug connection closed"); } } }; } }