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