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