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