1 /* 2 * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.sjavac.server; 27 28 import java.io.FileNotFoundException; 29 import java.io.FileWriter; 30 import java.io.IOException; 31 import java.io.PrintStream; 32 import java.io.PrintWriter; 33 import java.net.InetAddress; 34 import java.net.InetSocketAddress; 35 import java.net.ServerSocket; 36 import java.net.Socket; 37 import java.net.SocketException; 38 import java.util.HashMap; 39 import java.util.Map; 40 import java.util.Random; 41 import java.util.concurrent.atomic.AtomicBoolean; 42 43 import com.sun.tools.javac.main.Main; 44 import com.sun.tools.javac.main.Main.Result; 45 import com.sun.tools.sjavac.Log; 46 import com.sun.tools.sjavac.Util; 47 import com.sun.tools.sjavac.client.PortFileInaccessibleException; 48 import com.sun.tools.sjavac.comp.PooledSjavac; 49 import com.sun.tools.sjavac.comp.SjavacImpl; 50 51 /** 52 * The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server. 53 * 54 * <p><b>This is NOT part of any supported API. 55 * If you write code that depends on this, you do so at your own risk. 56 * This code and its internal interfaces are subject to change or 57 * deletion without notice.</b> 58 */ 59 public class SjavacServer implements Terminable { 60 61 // Prefix of line containing return code. 62 public final static String LINE_TYPE_RC = "RC"; 63 64 final private String portfilename; 65 final private int poolsize; 66 final private int keepalive; 67 68 // The secret cookie shared between server and client through the port file. 69 // Used to prevent clients from believing that they are communicating with 70 // an old server when a new server has started and reused the same port as 71 // an old server. 72 private final long myCookie; 73 74 // Accumulated build time, not counting idle time, used for logging purposes 75 private long totalBuildTime; 76 77 // The sjavac implementation to delegate requests to 78 Sjavac sjavac; 79 80 private ServerSocket serverSocket; 81 82 private PortFile portFile; 83 private PortFileMonitor portFileMonitor; 84 85 // Set to false break accept loop 86 final AtomicBoolean keepAcceptingRequests = new AtomicBoolean(); 87 88 // For the client, all port files fetched, one per started javac server. 89 // Though usually only one javac server is started by a client. 90 private static Map<String, PortFile> allPortFiles; 91 SjavacServer(String settings)92 public SjavacServer(String settings) throws FileNotFoundException { 93 this(Util.extractStringOption("portfile", settings), 94 Util.extractIntOption("poolsize", settings, Runtime.getRuntime().availableProcessors()), 95 Util.extractIntOption("keepalive", settings, 120)); 96 } 97 SjavacServer(String portfilename, int poolsize, int keepalive)98 public SjavacServer(String portfilename, 99 int poolsize, 100 int keepalive) 101 throws FileNotFoundException { 102 this.portfilename = portfilename; 103 this.poolsize = poolsize; 104 this.keepalive = keepalive; 105 this.myCookie = new Random().nextLong(); 106 } 107 108 109 /** 110 * Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time. 111 */ getPortFile(String filename)112 public static synchronized PortFile getPortFile(String filename) { 113 if (allPortFiles == null) { 114 allPortFiles = new HashMap<>(); 115 } 116 PortFile pf = allPortFiles.get(filename); 117 118 // Port file known. Does it still exist? 119 if (pf != null) { 120 try { 121 if (!pf.exists()) 122 pf = null; 123 } catch (IOException ioex) { 124 ioex.printStackTrace(); 125 } 126 } 127 128 if (pf == null) { 129 pf = new PortFile(filename); 130 allPortFiles.put(filename, pf); 131 } 132 return pf; 133 } 134 135 /** 136 * Get the cookie used for this server. 137 */ getCookie()138 long getCookie() { 139 return myCookie; 140 } 141 142 /** 143 * Get the port used for this server. 144 */ getPort()145 int getPort() { 146 return serverSocket.getLocalPort(); 147 } 148 149 /** 150 * Sum up the total build time for this javac server. 151 */ addBuildTime(long inc)152 public void addBuildTime(long inc) { 153 totalBuildTime += inc; 154 } 155 156 /** 157 * Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3" 158 * is sent as the settings parameter. Returns 0 on success, -1 on failure. 159 */ startServer()160 public int startServer() throws IOException, InterruptedException { 161 long serverStart = System.currentTimeMillis(); 162 163 // The port file is locked and the server port and cookie is written into it. 164 portFile = getPortFile(portfilename); 165 166 synchronized (portFile) { 167 portFile.lock(); 168 portFile.getValues(); 169 if (portFile.containsPortInfo()) { 170 Log.debug("Javac server not started because portfile exists!"); 171 portFile.unlock(); 172 return Result.ERROR.exitCode; 173 } 174 175 // .-----------. .--------. .------. 176 // socket -->| IdleReset |-->| Pooled |-->| Impl |--> javac 177 // '-----------' '--------' '------' 178 sjavac = new SjavacImpl(); 179 sjavac = new PooledSjavac(sjavac, poolsize); 180 sjavac = new IdleResetSjavac(sjavac, 181 this, 182 keepalive * 1000); 183 184 serverSocket = new ServerSocket(); 185 InetAddress localhost = InetAddress.getByName(null); 186 serverSocket.bind(new InetSocketAddress(localhost, 0)); 187 188 // At this point the server accepts connections, so it is now safe 189 // to publish the port / cookie information 190 portFile.setValues(getPort(), getCookie()); 191 portFile.unlock(); 192 } 193 194 portFileMonitor = new PortFileMonitor(portFile, this); 195 portFileMonitor.start(); 196 197 Log.debug("Sjavac server started. Accepting connections..."); 198 Log.debug(" port: " + getPort()); 199 Log.debug(" time: " + new java.util.Date()); 200 Log.debug(" poolsize: " + poolsize); 201 202 203 keepAcceptingRequests.set(true); 204 do { 205 try { 206 Socket socket = serverSocket.accept(); 207 new RequestHandler(socket, sjavac).start(); 208 } catch (SocketException se) { 209 // Caused by serverSocket.close() and indicates shutdown 210 } 211 } while (keepAcceptingRequests.get()); 212 213 Log.debug("Shutting down."); 214 215 // No more connections accepted. If any client managed to connect after 216 // the accept() was interrupted but before the server socket is closed 217 // here, any attempt to read or write to the socket will result in an 218 // IOException on the client side. 219 220 long realTime = System.currentTimeMillis() - serverStart; 221 Log.debug("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms"); 222 223 // Shut down 224 sjavac.shutdown(); 225 226 return Result.OK.exitCode; 227 } 228 229 @Override shutdown(String quitMsg)230 public void shutdown(String quitMsg) { 231 if (!keepAcceptingRequests.compareAndSet(true, false)) { 232 // Already stopped, no need to shut down again 233 return; 234 } 235 236 Log.debug("Quitting: " + quitMsg); 237 238 portFileMonitor.shutdown(); // No longer any need to monitor port file 239 240 // Unpublish port before shutting down socket to minimize the number of 241 // failed connection attempts 242 try { 243 portFile.delete(); 244 } catch (IOException | InterruptedException e) { 245 Log.error(e); 246 } 247 try { 248 serverSocket.close(); 249 } catch (IOException e) { 250 Log.error(e); 251 } 252 } 253 } 254