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