SjavacServer.java revision 2606:452dd2988607
1/* 2 * Copyright (c) 2011, 2014, 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.File; 28import java.io.FileNotFoundException; 29import java.io.IOException; 30import java.io.PrintStream; 31import java.io.PrintWriter; 32import java.net.InetAddress; 33import java.net.InetSocketAddress; 34import java.net.ServerSocket; 35import java.net.Socket; 36import java.net.SocketException; 37import java.util.ArrayList; 38import java.util.HashMap; 39import java.util.Map; 40import java.util.Random; 41import java.util.concurrent.atomic.AtomicBoolean; 42 43import com.sun.tools.sjavac.ProblemException; 44import com.sun.tools.sjavac.Util; 45import com.sun.tools.sjavac.comp.SjavacImpl; 46import com.sun.tools.sjavac.comp.PooledSjavac; 47 48/** 49 * The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server. 50 * 51 * <p><b>This is NOT part of any supported API. 52 * If you write code that depends on this, you do so at your own risk. 53 * This code and its internal interfaces are subject to change or 54 * deletion without notice.</b> 55 */ 56public class SjavacServer implements Terminable { 57 58 // Used in protocol to indicate which method to invoke 59 public final static String CMD_COMPILE = "compile"; 60 public final static String CMD_SYS_INFO = "sys-info"; 61 62 final private String portfilename; 63 final private String logfile; 64 final private String stdouterrfile; 65 final private int poolsize; 66 final private int keepalive; 67 final private PrintStream err; 68 69 // The secret cookie shared between server and client through the port file. 70 // Used to prevent clients from believing that they are communicating with 71 // an old server when a new server has started and reused the same port as 72 // an old server. 73 private final long myCookie; 74 75 // Accumulated build time, not counting idle time, used for logging purposes 76 private long totalBuildTime; 77 78 // The javac server specific log file. 79 PrintWriter theLog; 80 81 // The sjavac implementation to delegate requests to 82 Sjavac sjavac; 83 84 private ServerSocket serverSocket; 85 86 private PortFile portFile; 87 private PortFileMonitor portFileMonitor; 88 89 // Set to false break accept loop 90 final AtomicBoolean keepAcceptingRequests = new AtomicBoolean(); 91 92 // For the client, all port files fetched, one per started javac server. 93 // Though usually only one javac server is started by a client. 94 private static Map<String, PortFile> allPortFiles; 95 private static Map<String, Long> maxServerMemory; 96 97 public SjavacServer(String settings, PrintStream err) throws FileNotFoundException { 98 // Extract options. TODO: Change to proper constructor args 99 portfilename = Util.extractStringOption("portfile", settings); 100 logfile = Util.extractStringOption("logfile", settings); 101 stdouterrfile = Util.extractStringOption("stdouterrfile", settings); 102 keepalive = Util.extractIntOption("keepalive", settings, 120); 103 poolsize = Util.extractIntOption("poolsize", settings, 104 Runtime.getRuntime().availableProcessors()); 105 this.err = err; 106 107 myCookie = new Random().nextLong(); 108 theLog = new PrintWriter(logfile); 109 } 110 111 112 /** 113 * Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time. 114 */ 115 public static synchronized PortFile getPortFile(String filename) throws FileNotFoundException { 116 if (allPortFiles == null) { 117 allPortFiles = new HashMap<>(); 118 } 119 PortFile pf = allPortFiles.get(filename); 120 121 // Port file known. Does it still exist? 122 if (pf != null) { 123 try { 124 if (!pf.exists()) 125 pf = null; 126 } catch (IOException ioex) { 127 ioex.printStackTrace(); 128 } 129 } 130 131 if (pf == null) { 132 pf = new PortFile(filename); 133 allPortFiles.put(filename, pf); 134 } 135 return pf; 136 } 137 138 /** 139 * Get the cookie used for this server. 140 */ 141 long getCookie() { 142 return myCookie; 143 } 144 145 /** 146 * Get the port used for this server. 147 */ 148 int getPort() { 149 return serverSocket.getLocalPort(); 150 } 151 152 /** 153 * Sum up the total build time for this javac server. 154 */ 155 public void addBuildTime(long inc) { 156 totalBuildTime += inc; 157 } 158 159 /** 160 * Log this message. 161 */ 162 public void log(String msg) { 163 if (theLog != null) { 164 theLog.println(msg); 165 } else { 166 System.err.println(msg); 167 } 168 } 169 170 /** 171 * Make sure the log is flushed. 172 */ 173 public void flushLog() { 174 if (theLog != null) { 175 theLog.flush(); 176 } 177 } 178 179 /** 180 * Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3" 181 * is sent as the settings parameter. Returns 0 on success, -1 on failure. 182 */ 183 public int startServer() throws IOException { 184 long serverStart = System.currentTimeMillis(); 185 186 // The port file is locked and the server port and cookie is written into it. 187 portFile = getPortFile(portfilename); 188 189 synchronized (portFile) { 190 portFile.lock(); 191 portFile.getValues(); 192 if (portFile.containsPortInfo()) { 193 err.println("Javac server not started because portfile exists!"); 194 portFile.unlock(); 195 return -1; 196 } 197 198 // .-----------. .--------. .------. 199 // socket -->| IdleReset |-->| Pooled |-->| Impl |--> javac 200 // '-----------' '--------' '------' 201 sjavac = new SjavacImpl(); 202 sjavac = new PooledSjavac(sjavac, poolsize); 203 sjavac = new IdleResetSjavac(sjavac, 204 this, 205 keepalive * 1000); 206 207 serverSocket = new ServerSocket(); 208 InetAddress localhost = InetAddress.getByName(null); 209 serverSocket.bind(new InetSocketAddress(localhost, 0)); 210 211 // At this point the server accepts connections, so it is now safe 212 // to publish the port / cookie information 213 portFile.setValues(getPort(), getCookie()); 214 portFile.unlock(); 215 } 216 217 portFileMonitor = new PortFileMonitor(portFile, this); 218 portFileMonitor.start(); 219 220 log("Sjavac server started. Accepting connections..."); 221 log(" port: " + getPort()); 222 log(" time: " + new java.util.Date()); 223 log(" poolsize: " + poolsize); 224 flushLog(); 225 226 keepAcceptingRequests.set(true); 227 do { 228 try { 229 Socket socket = serverSocket.accept(); 230 new Thread(new RequestHandler(socket, sjavac)).start(); 231 } catch (SocketException se) { 232 // Caused by serverSocket.close() and indicates shutdown 233 } 234 } while (keepAcceptingRequests.get()); 235 236 log("Shutting down."); 237 238 // No more connections accepted. If any client managed to connect after 239 // the accept() was interrupted but before the server socket is closed 240 // here, any attempt to read or write to the socket will result in an 241 // IOException on the client side. 242 243 long realTime = System.currentTimeMillis() - serverStart; 244 log("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms"); 245 flushLog(); 246 247 // Shut down 248 sjavac.shutdown(); 249 250 return 0; 251 } 252 253 /** 254 * Fork a background process. Returns the command line used that can be printed if something failed. 255 */ 256 public static String fork(String sjavac, String portfile, String logfile, int poolsize, int keepalive, 257 final PrintStream err, String stdouterrfile, boolean background) 258 throws IOException, ProblemException { 259 if (stdouterrfile != null && stdouterrfile.trim().equals("")) { 260 stdouterrfile = null; 261 } 262 final String startserver = "--startserver:portfile=" + portfile + ",logfile=" + logfile + ",stdouterrfile=" + stdouterrfile + ",poolsize=" + poolsize + ",keepalive="+ keepalive; 263 264 if (background) { 265 sjavac += "%20" + startserver; 266 sjavac = sjavac.replaceAll("%20", " "); 267 sjavac = sjavac.replaceAll("%2C", ","); 268 // If the java/sh/cmd launcher fails the failure will be captured by stdouterr because of the redirection here. 269 String[] cmd = {"/bin/sh", "-c", sjavac + " >> " + stdouterrfile + " 2>&1"}; 270 if (!(new File("/bin/sh")).canExecute()) { 271 ArrayList<String> wincmd = new ArrayList<>(); 272 wincmd.add("cmd"); 273 wincmd.add("/c"); 274 wincmd.add("start"); 275 wincmd.add("cmd"); 276 wincmd.add("/c"); 277 wincmd.add(sjavac + " >> " + stdouterrfile + " 2>&1"); 278 cmd = wincmd.toArray(new String[wincmd.size()]); 279 } 280 Process pp = null; 281 try { 282 pp = Runtime.getRuntime().exec(cmd); 283 } catch (Exception e) { 284 e.printStackTrace(err); 285 e.printStackTrace(new PrintWriter(stdouterrfile)); 286 } 287 StringBuilder rs = new StringBuilder(); 288 for (String s : cmd) { 289 rs.append(s + " "); 290 } 291 return rs.toString(); 292 } 293 294 // Do not spawn a background server, instead run it within the same JVM. 295 Thread t = new Thread() { 296 @Override 297 public void run() { 298 try { 299 SjavacServer server = new SjavacServer(startserver, err); 300 server.startServer(); 301 } catch (Throwable t) { 302 t.printStackTrace(err); 303 } 304 } 305 }; 306 t.setDaemon(true); 307 t.start(); 308 return ""; 309 } 310 311 @Override 312 public void shutdown(String quitMsg) { 313 if (!keepAcceptingRequests.compareAndSet(true, false)) { 314 // Already stopped, no need to shut down again 315 return; 316 } 317 318 log("Quitting: " + quitMsg); 319 flushLog(); 320 321 portFileMonitor.shutdown(); // No longer any need to monitor port file 322 323 // Unpublish port before shutting down socket to minimize the number of 324 // failed connection attempts 325 try { 326 portFile.delete(); 327 } catch (IOException e) { 328 e.printStackTrace(theLog); 329 } 330 try { 331 serverSocket.close(); 332 } catch (IOException e) { 333 e.printStackTrace(theLog); 334 } 335 } 336} 337