SjavacClient.java revision 3305:c42875d558d4
142421Syokota/* 242421Syokota * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 342421Syokota * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 442421Syokota * 542421Syokota * This code is free software; you can redistribute it and/or modify it 642421Syokota * under the terms of the GNU General Public License version 2 only, as 742421Syokota * published by the Free Software Foundation. Oracle designates this 842421Syokota * particular file as subject to the "Classpath" exception as provided 942421Syokota * by Oracle in the LICENSE file that accompanied this code. 1042421Syokota * 1142421Syokota * This code is distributed in the hope that it will be useful, but WITHOUT 1242421Syokota * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1342421Syokota * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1442421Syokota * version 2 for more details (a copy is included in the LICENSE file that 1542421Syokota * accompanied this code). 1642421Syokota * 1742421Syokota * You should have received a copy of the GNU General Public License version 1842421Syokota * 2 along with this work; if not, write to the Free Software Foundation, 1942421Syokota * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2042421Syokota * 2142421Syokota * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2242421Syokota * or visit www.oracle.com if you need additional information or have any 2342421Syokota * questions. 2442421Syokota */ 2542421Syokota 2650477Speterpackage com.sun.tools.sjavac.client; 2742421Syokota 2842421Syokotaimport java.io.BufferedReader; 2942421Syokotaimport java.io.File; 3042421Syokotaimport java.io.IOException; 3142421Syokotaimport java.io.InputStreamReader; 3242421Syokotaimport java.io.OutputStreamWriter; 3342421Syokotaimport java.io.PrintStream; 3442421Syokotaimport java.io.PrintWriter; 3542421Syokotaimport java.io.Reader; 3642421Syokotaimport java.io.Writer; 3742421Syokotaimport java.net.InetAddress; 3842421Syokotaimport java.net.InetSocketAddress; 3942421Syokotaimport java.net.Socket; 4042421Syokotaimport java.util.ArrayList; 4142421Syokotaimport java.util.Arrays; 4242421Syokotaimport java.util.List; 4342421Syokotaimport java.util.Scanner; 4442421Syokotaimport java.util.stream.Stream; 4542421Syokota 4642421Syokotaimport com.sun.tools.sjavac.Log; 4742421Syokotaimport com.sun.tools.sjavac.Util; 4842421Syokotaimport com.sun.tools.sjavac.options.OptionHelper; 4942421Syokotaimport com.sun.tools.sjavac.options.Options; 5042421Syokotaimport com.sun.tools.sjavac.server.CompilationSubResult; 5142421Syokotaimport com.sun.tools.sjavac.server.PortFile; 5242421Syokotaimport com.sun.tools.sjavac.server.Sjavac; 5342421Syokotaimport com.sun.tools.sjavac.server.SjavacServer; 5442421Syokota 5542421Syokotaimport static java.util.stream.Collectors.joining; 5642421Syokota 5742421Syokota/** 5842421Syokota * Sjavac implementation that delegates requests to a SjavacServer. 5942421Syokota * 6042421Syokota * <p><b>This is NOT part of any supported API. 6142421Syokota * If you write code that depends on this, you do so at your own risk. 6242421Syokota * This code and its internal interfaces are subject to change or 6348104Syokota * deletion without notice.</b> 6448104Syokota */ 6548104Syokotapublic class SjavacClient implements Sjavac { 6648104Syokota 6755205Speter // The id can perhaps be used in the future by the javac server to reuse the 6848104Syokota // JavaCompiler instance for several compiles using the same id. 6948104Syokota private final String id; 7048104Syokota private final PortFile portFile; 7148104Syokota 7248104Syokota // Default keepalive for server is 120 seconds. 7348104Syokota // I.e. it will accept 120 seconds of inactivity before quitting. 7442421Syokota private final int keepalive; 7548104Syokota private final int poolsize; 7642421Syokota 7748104Syokota // The sjavac option specifies how the server part of sjavac is spawned. 7848104Syokota // If you have the experimental sjavac in your path, you are done. If not, you have 7948104Syokota // to point to a com.sun.tools.sjavac.Main that supports --startserver 8048104Syokota // for example by setting: sjavac=java%20-jar%20...javac.jar%com.sun.tools.sjavac.Main 8148104Syokota private final String sjavacForkCmd; 8283366Sjulian 8348104Syokota // Wait 2 seconds for response, before giving up on javac server. 8483366Sjulian static int CONNECTION_TIMEOUT = 2000; 8548104Syokota static int MAX_CONNECT_ATTEMPTS = 3; 8648104Syokota static int WAIT_BETWEEN_CONNECT_ATTEMPTS = 2000; 8748104Syokota 8883366Sjulian // Store the server conf settings here. 8948104Syokota private final String settings; 9048104Syokota 9148104Syokota public SjavacClient(Options options) { 9248104Syokota String tmpServerConf = options.getServerConf(); 9348104Syokota String serverConf = (tmpServerConf!=null)? tmpServerConf : ""; 9448104Syokota String tmpId = Util.extractStringOption("id", serverConf); 9555205Speter id = (tmpId!=null) ? tmpId : "id"+(((new java.util.Random()).nextLong())&Long.MAX_VALUE); 9648104Syokota String defaultPortfile = options.getDestDir() 9742421Syokota .resolve("javac_server") 98 .toAbsolutePath() 99 .toString(); 100 String portfileName = Util.extractStringOption("portfile", serverConf, defaultPortfile); 101 portFile = SjavacServer.getPortFile(portfileName); 102 sjavacForkCmd = Util.extractStringOption("sjavac", serverConf, "sjavac"); 103 int poolsize = Util.extractIntOption("poolsize", serverConf); 104 keepalive = Util.extractIntOption("keepalive", serverConf, 120); 105 106 this.poolsize = poolsize > 0 ? poolsize : Runtime.getRuntime().availableProcessors(); 107 settings = (serverConf.equals("")) ? "id="+id+",portfile="+portfileName : serverConf; 108 } 109 110 /** 111 * Hand out the server settings. 112 * @return The server settings, possibly a default value. 113 */ 114 public String serverSettings() { 115 return settings; 116 } 117 118 @Override 119 public int compile(String[] args) { 120 int result = -1; 121 try (Socket socket = tryConnect()) { 122 PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); 123 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 124 125 // Send args array to server 126 out.println(args.length); 127 for (String arg : args) 128 out.println(arg); 129 out.flush(); 130 131 // Read server response line by line 132 String line; 133 while (null != (line = in.readLine())) { 134 if (!line.contains(":")) { 135 throw new AssertionError("Could not parse protocol line: >>\"" + line + "\"<<"); 136 } 137 String[] typeAndContent = line.split(":", 2); 138 String type = typeAndContent[0]; 139 String content = typeAndContent[1]; 140 141 try { 142 if (Log.isDebugging()) { 143 // Distinguish server generated output if debugging. 144 content = "[sjavac-server] " + content; 145 } 146 Log.log(Log.Level.valueOf(type), content); 147 continue; 148 } catch (IllegalArgumentException e) { 149 // Parsing of 'type' as log level failed. 150 } 151 152 if (type.equals(SjavacServer.LINE_TYPE_RC)) { 153 result = Integer.parseInt(content); 154 } 155 } 156 } catch (PortFileInaccessibleException e) { 157 Log.error("Port file inaccessible."); 158 result = CompilationSubResult.ERROR_FATAL; 159 } catch (IOException ioe) { 160 Log.error("IOException caught during compilation: " + ioe.getMessage()); 161 Log.debug(ioe); 162 result = CompilationSubResult.ERROR_FATAL; 163 } catch (InterruptedException ie) { 164 Thread.currentThread().interrupt(); // Restore interrupt 165 Log.error("Compilation interrupted."); 166 Log.debug(ie); 167 result = CompilationSubResult.ERROR_FATAL; 168 } 169 return result; 170 } 171 172 /* 173 * Makes MAX_CONNECT_ATTEMPTS attepmts to connect to server. 174 */ 175 private Socket tryConnect() throws IOException, InterruptedException { 176 makeSureServerIsRunning(portFile); 177 int attempt = 0; 178 while (true) { 179 Log.debug("Trying to connect. Attempt " + (++attempt) + " of " + MAX_CONNECT_ATTEMPTS); 180 try { 181 return makeConnectionAttempt(); 182 } catch (IOException ex) { 183 Log.error("Connection attempt failed: " + ex.getMessage()); 184 if (attempt >= MAX_CONNECT_ATTEMPTS) { 185 Log.error("Giving up"); 186 throw new IOException("Could not connect to server", ex); 187 } 188 } 189 Thread.sleep(WAIT_BETWEEN_CONNECT_ATTEMPTS); 190 } 191 } 192 193 private Socket makeConnectionAttempt() throws IOException { 194 Socket socket = new Socket(); 195 InetAddress localhost = InetAddress.getByName(null); 196 InetSocketAddress address = new InetSocketAddress(localhost, portFile.getPort()); 197 socket.connect(address, CONNECTION_TIMEOUT); 198 Log.debug("Connected"); 199 return socket; 200 } 201 202 /* 203 * Will return immediately if a server already seems to be running, 204 * otherwise fork a new server and block until it seems to be running. 205 */ 206 private void makeSureServerIsRunning(PortFile portFile) 207 throws IOException, InterruptedException { 208 209 if (portFile.exists()) { 210 portFile.lock(); 211 portFile.getValues(); 212 portFile.unlock(); 213 214 if (portFile.containsPortInfo()) { 215 // Server seems to already be running 216 return; 217 } 218 } 219 220 // Fork a new server and wait for it to start 221 SjavacClient.fork(sjavacForkCmd, 222 portFile, 223 poolsize, 224 keepalive); 225 } 226 227 @Override 228 public void shutdown() { 229 // Nothing to clean up 230 } 231 232 /* 233 * Fork a server process process and wait for server to come around 234 */ 235 public static void fork(String sjavacCmd, PortFile portFile, int poolsize, int keepalive) 236 throws IOException, InterruptedException { 237 List<String> cmd = new ArrayList<>(); 238 cmd.addAll(Arrays.asList(OptionHelper.unescapeCmdArg(sjavacCmd).split(" "))); 239 cmd.add("--startserver:" 240 + "portfile=" + portFile.getFilename() 241 + ",poolsize=" + poolsize 242 + ",keepalive="+ keepalive); 243 244 Process serverProcess; 245 Log.debug("Starting server. Command: " + String.join(" ", cmd)); 246 try { 247 // If the cmd for some reason can't be executed (file is not found, 248 // or is not executable for instance) this will throw an 249 // IOException and p == null. 250 serverProcess = new ProcessBuilder(cmd) 251 .redirectErrorStream(true) 252 .start(); 253 } catch (IOException ex) { 254 // Message is typically something like: 255 // Cannot run program "xyz": error=2, No such file or directory 256 Log.error("Failed to create server process: " + ex.getMessage()); 257 Log.debug(ex); 258 throw new IOException(ex); 259 } 260 261 // serverProcess != null at this point. 262 try { 263 // Throws an IOException if no valid values materialize 264 portFile.waitForValidValues(); 265 } catch (IOException ex) { 266 // Process was started, but server failed to initialize. This could 267 // for instance be due to the JVM not finding the server class, 268 // or the server running in to some exception early on. 269 Log.error("Sjavac server failed to initialize: " + ex.getMessage()); 270 Log.error("Process output:"); 271 Reader serverStdoutStderr = new InputStreamReader(serverProcess.getInputStream()); 272 try (BufferedReader br = new BufferedReader(serverStdoutStderr)) { 273 br.lines().forEach(Log::error); 274 } 275 Log.error("<End of process output>"); 276 try { 277 Log.error("Process exit code: " + serverProcess.exitValue()); 278 } catch (IllegalThreadStateException e) { 279 // Server is presumably still running. 280 } 281 throw new IOException("Server failed to initialize: " + ex.getMessage(), ex); 282 } 283 } 284} 285