ohrstrom@1504: /* ohrstrom@1504: * Copyright (c) 2011-2012, Oracle and/or its affiliates. All rights reserved. ohrstrom@1504: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. ohrstrom@1504: * ohrstrom@1504: * This code is free software; you can redistribute it and/or modify it ohrstrom@1504: * under the terms of the GNU General Public License version 2 only, as ohrstrom@1504: * published by the Free Software Foundation. Oracle designates this ohrstrom@1504: * particular file as subject to the "Classpath" exception as provided ohrstrom@1504: * by Oracle in the LICENSE file that accompanied this code. ohrstrom@1504: * ohrstrom@1504: * This code is distributed in the hope that it will be useful, but WITHOUT ohrstrom@1504: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ohrstrom@1504: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License ohrstrom@1504: * version 2 for more details (a copy is included in the LICENSE file that ohrstrom@1504: * accompanied this code). ohrstrom@1504: * ohrstrom@1504: * You should have received a copy of the GNU General Public License version ohrstrom@1504: * 2 along with this work; if not, write to the Free Software Foundation, ohrstrom@1504: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. ohrstrom@1504: * ohrstrom@1504: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohrstrom@1504: * or visit www.oracle.com if you need additional information or have any ohrstrom@1504: * questions. ohrstrom@1504: */ ohrstrom@1504: package com.sun.tools.sjavac.server; ohrstrom@1504: ohrstrom@1504: import java.io.BufferedReader; ohrstrom@1504: import java.io.File; ohrstrom@1504: import java.io.IOException; ohrstrom@1504: import java.io.InputStreamReader; ohrstrom@1504: import java.io.PrintWriter; ohrstrom@1504: import java.io.FileNotFoundException; ohrstrom@1504: import java.net.URI; ohrstrom@1504: import java.util.HashSet; ohrstrom@1504: import java.util.Set; ohrstrom@1504: import java.util.HashMap; ohrstrom@1504: import java.util.Map; ohrstrom@1504: ohrstrom@1504: import java.net.InetAddress; ohrstrom@1504: import java.net.InetSocketAddress; ohrstrom@1504: import java.net.ServerSocket; ohrstrom@1504: import java.net.Socket; ohrstrom@1504: import java.net.SocketAddress; ohrstrom@1504: import java.util.ArrayList; ohrstrom@1504: import java.util.Random; ohrstrom@1504: ohrstrom@1504: import com.sun.tools.sjavac.Util; ohrstrom@1504: import com.sun.tools.sjavac.ProblemException; ohrstrom@1504: import java.io.*; ohrstrom@1504: import java.util.*; ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server. ohrstrom@1504: * ohrstrom@1504: *

This is NOT part of any supported API. If you write code that depends on this, you do so at your own risk. This code and its internal interfaces are ohrstrom@1504: * subject to change or deletion without notice.

ohrstrom@1504: */ ohrstrom@1504: public class JavacServer { ohrstrom@1504: // Responding to this tcp/ip port on localhost. ohrstrom@1504: ohrstrom@1504: private final ServerSocket serverSocket; ohrstrom@1504: // The secret cookie shared between server and client through the port file. ohrstrom@1504: private final long myCookie; ohrstrom@1504: // When the server was started. ohrstrom@1504: private long serverStart; ohrstrom@1504: // Accumulated build time for all requests, not counting idle time. ohrstrom@1504: private long totalBuildTime; ohrstrom@1504: // The javac server specific log file. ohrstrom@1504: PrintWriter theLog; ohrstrom@1504: // The compiler pool that maintains the compiler threads. ohrstrom@1504: CompilerPool compilerPool; ohrstrom@1504: // For the client, all port files fetched, one per started javac server. ohrstrom@1504: // Though usually only one javac server is started by a client. ohrstrom@1504: private static Map allPortFiles; ohrstrom@1504: private static Map maxServerMemory; ohrstrom@1504: final static int ERROR_FATAL = -1; ohrstrom@1504: final static int ERROR_BUT_TRY_AGAIN = -4712; ohrstrom@1504: final static String PROTOCOL_COOKIE_VERSION = "----THE-COOKIE-V2----"; ohrstrom@1504: final static String PROTOCOL_CWD = "----THE-CWD----"; ohrstrom@1504: final static String PROTOCOL_ID = "----THE-ID----"; ohrstrom@1504: final static String PROTOCOL_ARGS = "----THE-ARGS----"; ohrstrom@1504: final static String PROTOCOL_SOURCES_TO_COMPILE = "----THE-SOURCES-TO-COMPILE----"; ohrstrom@1504: final static String PROTOCOL_VISIBLE_SOURCES = "----THE-VISIBLE-SOURCES----"; ohrstrom@1504: final static String PROTOCOL_END = "----THE-END----"; ohrstrom@1504: final static String PROTOCOL_STDOUT = "----THE-STDOUT----"; ohrstrom@1504: final static String PROTOCOL_STDERR = "----THE-STDERR----"; ohrstrom@1504: final static String PROTOCOL_PACKAGE_ARTIFACTS = "----THE-PACKAGE_ARTIFACTS----"; ohrstrom@1504: final static String PROTOCOL_PACKAGE_DEPENDENCIES = "----THE-PACKAGE_DEPENDENCIES----"; ohrstrom@1504: final static String PROTOCOL_PACKAGE_PUBLIC_APIS = "----THE-PACKAGE-PUBLIC-APIS----"; ohrstrom@1504: final static String PROTOCOL_SYSINFO = "----THE-SYSINFO----"; ohrstrom@1504: final static String PROTOCOL_RETURN_CODE = "----THE-RETURN-CODE----"; ohrstrom@1504: // Check if the portfile is gone, every 5 seconds. ohrstrom@1504: static int CHECK_PORTFILE_INTERVAL = 5; ohrstrom@1504: // Wait 2 seconds for response, before giving up on javac server. ohrstrom@1504: static int CONNECTION_TIMEOUT = 2; ohrstrom@1504: static int WAIT_BETWEEN_CONNECT_ATTEMPTS = 1; ohrstrom@1504: static int MAX_NUM_CONNECT_ATTEMPTS = 3; ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time. ohrstrom@1504: */ ohrstrom@1504: private static synchronized PortFile getPortFile(String filename) throws FileNotFoundException { ohrstrom@1504: if (allPortFiles == null) { ohrstrom@1504: allPortFiles = new HashMap(); ohrstrom@1504: } ohrstrom@1504: PortFile pf = allPortFiles.get(filename); ohrstrom@1504: if (pf == null) { ohrstrom@1504: pf = new PortFile(filename); ohrstrom@1504: allPortFiles.put(filename, pf); ohrstrom@1504: } ohrstrom@1504: return pf; ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Get the cookie used for this server. ohrstrom@1504: */ ohrstrom@1504: long getCookie() { ohrstrom@1504: return myCookie; ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Get the port used for this server. ohrstrom@1504: */ ohrstrom@1504: int getPort() { ohrstrom@1504: return serverSocket.getLocalPort(); ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Sum up the total build time for this javac server. ohrstrom@1504: */ ohrstrom@1504: public void addBuildTime(long inc) { ohrstrom@1504: totalBuildTime += inc; ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Log this message. ohrstrom@1504: */ ohrstrom@1504: public void log(String msg) { ohrstrom@1504: if (theLog != null) { ohrstrom@1504: theLog.println(msg); ohrstrom@1504: } else { ohrstrom@1504: System.err.println(msg); ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Make sure the log is flushed. ohrstrom@1504: */ ohrstrom@1504: public void flushLog() { ohrstrom@1504: if (theLog != null) { ohrstrom@1504: theLog.flush(); ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3" ohrstrom@1504: * is sent as the settings parameter. Returns 0 on success, -1 on failure. ohrstrom@1504: */ ohrstrom@1504: public static int startServer(String settings, PrintStream err) { ohrstrom@1504: try { ohrstrom@1504: String portfile = Util.extractStringOption("portfile", settings); ohrstrom@1504: // The log file collects more javac server specific log information. ohrstrom@1504: String logfile = Util.extractStringOption("logfile", settings); ohrstrom@1504: // The stdouterr file collects all the System.out and System.err writes to disk. ohrstrom@1504: String stdouterrfile = Util.extractStringOption("stdouterrfile", settings); ohrstrom@1504: // We could perhaps use System.setOut and setErr here. ohrstrom@1504: // But for the moment we rely on the client to spawn a shell where stdout ohrstrom@1504: // and stderr are redirected already. ohrstrom@1504: // The pool size is a limit the number of concurrent compiler threads used. ohrstrom@1504: // The server might use less than these to avoid memory problems. ohrstrom@1504: int poolsize = Util.extractIntOption("poolsize", settings); ohrstrom@1504: if (poolsize <= 0) { ohrstrom@1504: // If not set, default to the number of cores. ohrstrom@1504: poolsize = Runtime.getRuntime().availableProcessors(); ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: // How many seconds of inactivity will the server accept before quitting? ohrstrom@1504: int keepalive = Util.extractIntOption("keepalive", settings); ohrstrom@1504: if (keepalive <= 0) { ohrstrom@1504: keepalive = 120; ohrstrom@1504: } ohrstrom@1504: // The port file is locked and the server port and cookie is written into it. ohrstrom@1504: PortFile portFile = getPortFile(portfile); ohrstrom@1504: JavacServer s; ohrstrom@1504: ohrstrom@1504: synchronized (portFile) { ohrstrom@1504: portFile.lock(); ohrstrom@1504: portFile.getValues(); ohrstrom@1504: if (portFile.containsPortInfo()) { ohrstrom@1504: err.println("Javac server not started because portfile exists!"); ohrstrom@1504: portFile.unlock(); ohrstrom@1504: return -1; ohrstrom@1504: } ohrstrom@1504: s = new JavacServer(poolsize, logfile); ohrstrom@1504: portFile.setValues(s.getPort(), s.getCookie()); ohrstrom@1504: portFile.unlock(); ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: // Run the server. Will delete the port file when shutting down. ohrstrom@1504: // It will shut down automatically when no new requests have come in ohrstrom@1504: // during the last 125 seconds. ohrstrom@1504: s.run(portFile, err, keepalive); ohrstrom@1504: // The run loop for the server has exited. ohrstrom@1504: return 0; ohrstrom@1504: } catch (Exception e) { ohrstrom@1504: e.printStackTrace(err); ohrstrom@1504: return -1; ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Dispatch a compilation request to a javac server. ohrstrom@1504: * ohrstrom@1504: * @param args are the command line args to javac and is allowed to contain source files, @file and other command line options to javac. ohrstrom@1504: * ohrstrom@1504: * The generated classes, h files and other artifacts from the javac invocation are stored by the javac server to disk. ohrstrom@1504: * ohrstrom@1504: * @param sources_to_compile The sources to compile. ohrstrom@1504: * ohrstrom@1504: * @param visibleSources If visible sources has a non zero size, then visible_sources are the only files in the file system that the javac server can see! ohrstrom@1504: * (Sources to compile are always visible.) The visible sources are those supplied by the (filtered) -sourcepath ohrstrom@1504: * ohrstrom@1504: * @param visibleClasses If visible classes for a specific root/jar has a non zero size, then visible_classes are the only class files that the javac server ohrstrom@1504: * can see, in that root/jar. It maps from a classpath root or a jar file to the set of visible classes for that root/jar. ohrstrom@1504: * ohrstrom@1504: * The server return meta data about the build in the following parameters. ohrstrom@1504: * @param package_artifacts, map from package name to set of created artifacts for that package. ohrstrom@1504: * @param package_dependencies, map from package name to set of packages that it depends upon. ohrstrom@1504: * @param package_pubapis, map from package name to unique string identifying its pub api. ohrstrom@1504: */ ohrstrom@1504: public static int useServer(String settings, String[] args, ohrstrom@1504: Set sourcesToCompile, ohrstrom@1504: Set visibleSources, ohrstrom@1504: Map> visibleClasses, ohrstrom@1504: Map> packageArtifacts, ohrstrom@1504: Map> packageDependencies, ohrstrom@1504: Map packagePubapis, ohrstrom@1504: SysInfo sysinfo, ohrstrom@1504: PrintStream out, ohrstrom@1504: PrintStream err) { ohrstrom@1504: try { ohrstrom@1504: // The id can perhaps be used in the future by the javac server to reuse the ohrstrom@1504: // JavaCompiler instance for several compiles using the same id. ohrstrom@1504: String id = Util.extractStringOption("id", settings); ohrstrom@1504: String portfile = Util.extractStringOption("portfile", settings); ohrstrom@1504: String logfile = Util.extractStringOption("logfile", settings); ohrstrom@1504: String stdouterrfile = Util.extractStringOption("stdouterrfile", settings); ohrstrom@1504: String background = Util.extractStringOption("background", settings); ohrstrom@1504: if (background == null || !background.equals("false")) { ohrstrom@1504: background = "true"; ohrstrom@1504: } ohrstrom@1504: // The sjavac option specifies how the server part of sjavac is spawned. ohrstrom@1504: // If you have the experimental sjavac in your path, you are done. If not, you have ohrstrom@1504: // to point to a com.sun.tools.sjavac.Main that supports --startserver ohrstrom@1504: // for example by setting: sjavac=java%20-jar%20...javac.jar%com.sun.tools.sjavac.Main ohrstrom@1504: String sjavac = Util.extractStringOption("sjavac", settings); ohrstrom@1504: int poolsize = Util.extractIntOption("poolsize", settings); ohrstrom@1504: int keepalive = Util.extractIntOption("keepalive", settings); ohrstrom@1504: ohrstrom@1504: if (keepalive <= 0) { ohrstrom@1504: // Default keepalive for server is 120 seconds. ohrstrom@1504: // I.e. it will accept 120 seconds of inactivity before quitting. ohrstrom@1504: keepalive = 120; ohrstrom@1504: } ohrstrom@1504: if (portfile == null) { ohrstrom@1504: err.println("No portfile was specified!"); ohrstrom@1504: return -1; ohrstrom@1504: } ohrstrom@1504: if (logfile == null) { ohrstrom@1504: logfile = portfile + ".javaclog"; ohrstrom@1504: } ohrstrom@1504: if (stdouterrfile == null) { ohrstrom@1504: stdouterrfile = portfile + ".stdouterr"; ohrstrom@1504: } ohrstrom@1504: // Default to sjavac and hope it is in the path. ohrstrom@1504: if (sjavac == null) { ohrstrom@1504: sjavac = "sjavac"; ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: int attempts = 0; ohrstrom@1504: int rc = -1; ohrstrom@1504: do { ohrstrom@1504: PortFile port_file = getPortFile(portfile); ohrstrom@1504: synchronized (port_file) { ohrstrom@1504: port_file.lock(); ohrstrom@1504: port_file.getValues(); ohrstrom@1504: port_file.unlock(); ohrstrom@1504: } ohrstrom@1504: if (!port_file.containsPortInfo()) { ohrstrom@1504: String cmd = fork(sjavac, port_file.getFilename(), logfile, poolsize, keepalive, err, stdouterrfile, background); ohrstrom@1504: ohrstrom@1504: if (background.equals("true") && !port_file.waitForValidValues()) { ohrstrom@1504: // Ouch the server did not start! Lets print its stdouterrfile and the command used. ohrstrom@1504: printFailedAttempt(cmd, stdouterrfile, err); ohrstrom@1504: // And give up. ohrstrom@1504: return -1; ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: rc = connectAndCompile(port_file, id, args, sourcesToCompile, visibleSources, ohrstrom@1504: packageArtifacts, packageDependencies, packagePubapis, sysinfo, ohrstrom@1504: out, err); ohrstrom@1504: // Try again until we manage to connect. Any error after that ohrstrom@1504: // will cause the compilation to fail. ohrstrom@1504: if (rc == ERROR_BUT_TRY_AGAIN) { ohrstrom@1504: // We could not connect to the server. Try again. ohrstrom@1504: attempts++; ohrstrom@1504: try { ohrstrom@1504: Thread.sleep(WAIT_BETWEEN_CONNECT_ATTEMPTS); ohrstrom@1504: } catch (InterruptedException e) { ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: } while (rc == ERROR_BUT_TRY_AGAIN && attempts < MAX_NUM_CONNECT_ATTEMPTS); ohrstrom@1504: return rc; ohrstrom@1504: } catch (Exception e) { ohrstrom@1504: e.printStackTrace(err); ohrstrom@1504: return -1; ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: private static void printFailedAttempt(String cmd, String f, PrintStream err) { ohrstrom@1504: err.println("---- Failed to start javac server with this command -----"); ohrstrom@1504: err.println(cmd); ohrstrom@1504: try { ohrstrom@1504: BufferedReader in = new BufferedReader(new FileReader(f)); ohrstrom@1504: err.println("---- stdout/stderr output from attempt to start javac server -----"); ohrstrom@1504: for (;;) { ohrstrom@1504: String l = in.readLine(); ohrstrom@1504: if (l == null) { ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: err.println(l); ohrstrom@1504: } ohrstrom@1504: err.println("------------------------------------------------------------------"); ohrstrom@1504: } catch (Exception e) { ohrstrom@1504: err.println("The stdout/stderr output in file " + f + " does not exist and the server did not start."); ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Spawn the server instance. ohrstrom@1504: */ ohrstrom@1504: ohrstrom@1504: private JavacServer(int poolSize, String logfile) throws IOException { ohrstrom@1504: serverStart = System.currentTimeMillis(); ohrstrom@1504: // Create a server socket on a random port that is bound to the localhost/127.0.0.1 interface. ohrstrom@1504: // I.e only local processes can connect to this port. ohrstrom@1504: serverSocket = new ServerSocket(0, 128, InetAddress.getByName(null)); ohrstrom@1504: compilerPool = new CompilerPool(poolSize, this); ohrstrom@1504: Random rnd = new Random(); ohrstrom@1504: myCookie = rnd.nextLong(); ohrstrom@1504: theLog = new PrintWriter(logfile); ohrstrom@1504: log("Javac server started. port=" + getPort() + " date=" + (new java.util.Date()) + " with poolsize=" + poolSize); ohrstrom@1504: flushLog(); ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Fork a background process. Returns the command line used that can be printed if something failed. ohrstrom@1504: */ ohrstrom@1504: private static String fork(String sjavac, String portfile, String logfile, int poolsize, int keepalive, ohrstrom@1504: final PrintStream err, String stdouterrfile, String background) ohrstrom@1504: throws IOException, ProblemException { ohrstrom@1504: if (stdouterrfile != null && stdouterrfile.trim().equals("")) { ohrstrom@1504: stdouterrfile = null; ohrstrom@1504: } ohrstrom@1504: final String startserver = "--startserver:portfile=" + portfile + ",logfile=" + logfile + ",stdouterrfile=" + stdouterrfile + ",poolsize=" + poolsize + ",keepalive="+ keepalive; ohrstrom@1504: ohrstrom@1504: if (background.equals("true")) { ohrstrom@1504: sjavac += "%20" + startserver; ohrstrom@1504: sjavac = sjavac.replaceAll("%20", " "); ohrstrom@1504: sjavac = sjavac.replaceAll("%2C", ","); ohrstrom@1504: // If the java/sh/cmd launcher fails the failure will be captured by stdouterr because of the redirection here. ohrstrom@1504: String[] cmd = {"/bin/sh", "-c", sjavac + " >> " + stdouterrfile + " 2>&1"}; ohrstrom@1504: if (!(new File("/bin/sh")).canExecute()) { ohrstrom@1504: ArrayList wincmd = new ArrayList(); ohrstrom@1504: wincmd.add("cmd"); ohrstrom@1504: wincmd.add("/c"); ohrstrom@1504: wincmd.add("start"); ohrstrom@1504: wincmd.add("cmd"); ohrstrom@1504: wincmd.add("/c"); ohrstrom@1504: wincmd.add(sjavac + " >> " + stdouterrfile + " 2>&1"); ohrstrom@1504: cmd = wincmd.toArray(new String[wincmd.size()]); ohrstrom@1504: } ohrstrom@1504: Process pp = null; ohrstrom@1504: try { ohrstrom@1504: pp = Runtime.getRuntime().exec(cmd); ohrstrom@1504: } catch (Exception e) { ohrstrom@1504: e.printStackTrace(err); ohrstrom@1504: e.printStackTrace(new PrintWriter(stdouterrfile)); ohrstrom@1504: } ohrstrom@1504: StringBuilder rs = new StringBuilder(); ohrstrom@1504: for (String s : cmd) { ohrstrom@1504: rs.append(s + " "); ohrstrom@1504: } ohrstrom@1504: return rs.toString(); ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: // Do not spawn a background server, instead run it within the same JVM. ohrstrom@1504: Thread t = new Thread() { ohrstrom@1504: @Override ohrstrom@1504: public void run() { ohrstrom@1504: try { ohrstrom@1504: JavacServer.startServer(startserver, err); ohrstrom@1504: } catch (Throwable t) { ohrstrom@1504: t.printStackTrace(err); ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: }; ohrstrom@1504: t.start(); ohrstrom@1504: return ""; ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Expect this key on the next line read from the reader. ohrstrom@1504: */ ohrstrom@1504: private static boolean expect(BufferedReader in, String key) throws IOException { ohrstrom@1504: String s = in.readLine(); ohrstrom@1504: if (s != null && s.equals(key)) { ohrstrom@1504: return true; ohrstrom@1504: } ohrstrom@1504: return false; ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Make a request to the server only to get the maximum possible heap size to use for compilations. ohrstrom@1504: * ohrstrom@1504: * @param port_file The port file used to synchronize creation of this server. ohrstrom@1504: * @param id The identify of the compilation. ohrstrom@1504: * @param out Standard out information. ohrstrom@1504: * @param err Standard err information. ohrstrom@1504: * @return The maximum heap size in bytes. ohrstrom@1504: */ ohrstrom@1504: public static SysInfo connectGetSysInfo(String serverSettings, PrintStream out, PrintStream err) { ohrstrom@1504: SysInfo sysinfo = new SysInfo(-1, -1); ohrstrom@1504: String id = Util.extractStringOption("id", serverSettings); ohrstrom@1504: String portfile = Util.extractStringOption("portfile", serverSettings); ohrstrom@1504: try { ohrstrom@1504: PortFile pf = getPortFile(portfile); ohrstrom@1504: useServer(serverSettings, new String[0], ohrstrom@1504: new HashSet(), ohrstrom@1504: new HashSet(), ohrstrom@1504: new HashMap>(), ohrstrom@1504: new HashMap>(), ohrstrom@1504: new HashMap>(), ohrstrom@1504: new HashMap(), ohrstrom@1504: sysinfo, out, err); ohrstrom@1504: } catch (Exception e) { ohrstrom@1504: e.printStackTrace(err); ohrstrom@1504: } ohrstrom@1504: return sysinfo; ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Connect and compile using the javac server settings and the args. When using more advanced features, the sources_to_compile and visible_sources are ohrstrom@1504: * supplied to the server and meta data is returned in package_artifacts, package_dependencies and package_pubapis. ohrstrom@1504: */ ohrstrom@1504: private static int connectAndCompile(PortFile portFile, String id, String[] args, ohrstrom@1504: Set sourcesToCompile, ohrstrom@1504: Set visibleSources, ohrstrom@1504: Map> packageArtifacts, ohrstrom@1504: Map> packageDependencies, ohrstrom@1504: Map packagePublicApis, ohrstrom@1504: SysInfo sysinfo, ohrstrom@1504: PrintStream out, ohrstrom@1504: PrintStream err) { ohrstrom@1504: int rc = -3; ohrstrom@1504: try { ohrstrom@1504: int port = portFile.getPort(); ohrstrom@1504: if (port == 0) { ohrstrom@1504: return ERROR_BUT_TRY_AGAIN; ohrstrom@1504: } ohrstrom@1504: long cookie = portFile.getCookie(); ohrstrom@1504: ohrstrom@1504: // Acquire the localhost/127.0.0.1 address. ohrstrom@1504: InetAddress addr = InetAddress.getByName(null); ohrstrom@1504: SocketAddress sockaddr = new InetSocketAddress(addr, port); ohrstrom@1504: Socket sock = new Socket(); ohrstrom@1504: int timeoutMs = CONNECTION_TIMEOUT * 1000; ohrstrom@1504: try { ohrstrom@1504: sock.connect(sockaddr, timeoutMs); ohrstrom@1504: } catch (java.net.ConnectException e) { ohrstrom@1504: err.println("Could not connect to javac server found in portfile: " + portFile.getFilename() + " " + e); ohrstrom@1504: return ERROR_BUT_TRY_AGAIN; ohrstrom@1504: } ohrstrom@1504: if (!sock.isConnected()) { ohrstrom@1504: err.println("Could not connect to javac server found in portfile: " + portFile.getFilename()); ohrstrom@1504: return ERROR_BUT_TRY_AGAIN; ohrstrom@1504: } ohrstrom@1504: BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream())); ohrstrom@1504: PrintWriter sockout = new PrintWriter(sock.getOutputStream()); ohrstrom@1504: ohrstrom@1504: sockout.println(PROTOCOL_COOKIE_VERSION); ohrstrom@1504: sockout.println("" + cookie); ohrstrom@1504: sockout.println(PROTOCOL_CWD); ohrstrom@1504: sockout.println(System.getProperty("user.dir")); ohrstrom@1504: sockout.println(PROTOCOL_ID); ohrstrom@1504: sockout.println(id); ohrstrom@1504: sockout.println(PROTOCOL_ARGS); ohrstrom@1504: for (String s : args) { ohrstrom@1504: StringBuffer buf = new StringBuffer(); ohrstrom@1504: String[] paths = s.split(File.pathSeparator); ohrstrom@1504: int c = 0; ohrstrom@1504: for (String path : paths) { ohrstrom@1504: File f = new File(path); ohrstrom@1504: if (f.isFile() || f.isDirectory()) { ohrstrom@1504: buf.append(f.getAbsolutePath()); ohrstrom@1504: c++; ohrstrom@1504: if (c < paths.length) { ohrstrom@1504: buf.append(File.pathSeparator); ohrstrom@1504: } ohrstrom@1504: } else { ohrstrom@1504: buf = new StringBuffer(s); ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: sockout.println(buf.toString()); ohrstrom@1504: } ohrstrom@1504: sockout.println(PROTOCOL_SOURCES_TO_COMPILE); ohrstrom@1504: for (URI uri : sourcesToCompile) { ohrstrom@1504: sockout.println(uri.toString()); ohrstrom@1504: } ohrstrom@1504: sockout.println(PROTOCOL_VISIBLE_SOURCES); ohrstrom@1504: for (URI uri : visibleSources) { ohrstrom@1504: sockout.println(uri.toString()); ohrstrom@1504: } ohrstrom@1504: sockout.println(PROTOCOL_END); ohrstrom@1504: sockout.flush(); ohrstrom@1504: ohrstrom@1504: StringBuffer stdout = new StringBuffer(); ohrstrom@1504: StringBuffer stderr = new StringBuffer(); ohrstrom@1504: ohrstrom@1504: if (!expect(in, PROTOCOL_STDOUT)) { ohrstrom@1504: return ERROR_FATAL; ohrstrom@1504: } ohrstrom@1504: // Load stdout ohrstrom@1504: for (;;) { ohrstrom@1504: String l = in.readLine(); ohrstrom@1504: if (l == null) { ohrstrom@1504: return ERROR_FATAL; ohrstrom@1504: } ohrstrom@1504: if (l.equals(PROTOCOL_STDERR)) { ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: stdout.append(l); ohrstrom@1504: stdout.append('\n'); ohrstrom@1504: } ohrstrom@1504: // Load stderr ohrstrom@1504: for (;;) { ohrstrom@1504: String l = in.readLine(); ohrstrom@1504: if (l == null) { ohrstrom@1504: return ERROR_FATAL; ohrstrom@1504: } ohrstrom@1504: if (l.equals(PROTOCOL_PACKAGE_ARTIFACTS)) { ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: stderr.append(l); ohrstrom@1504: stderr.append('\n'); ohrstrom@1504: } ohrstrom@1504: // Load the package artifacts ohrstrom@1504: Set lastUriSet = null; ohrstrom@1504: for (;;) { ohrstrom@1504: String l = in.readLine(); ohrstrom@1504: if (l == null) { ohrstrom@1504: return ERROR_FATAL; ohrstrom@1504: } ohrstrom@1504: if (l.equals(PROTOCOL_PACKAGE_DEPENDENCIES)) { ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: if (l.length() > 1 && l.charAt(0) == '+') { ohrstrom@1504: String pkg = l.substring(1); ohrstrom@1504: lastUriSet = new HashSet(); ohrstrom@1504: packageArtifacts.put(pkg, lastUriSet); ohrstrom@1504: } else if (l.length() > 1 && lastUriSet != null) { ohrstrom@1504: lastUriSet.add(new URI(l.substring(1))); ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: // Load package dependencies ohrstrom@1504: Set lastPackageSet = null; ohrstrom@1504: for (;;) { ohrstrom@1504: String l = in.readLine(); ohrstrom@1504: if (l == null) { ohrstrom@1504: return ERROR_FATAL; ohrstrom@1504: } ohrstrom@1504: if (l.equals(PROTOCOL_PACKAGE_PUBLIC_APIS)) { ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: if (l.length() > 1 && l.charAt(0) == '+') { ohrstrom@1504: String pkg = l.substring(1); ohrstrom@1504: lastPackageSet = new HashSet(); ohrstrom@1504: packageDependencies.put(pkg, lastPackageSet); ohrstrom@1504: } else if (l.length() > 1 && lastPackageSet != null) { ohrstrom@1504: lastPackageSet.add(l.substring(1)); ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: // Load package pubapis ohrstrom@1504: Map tmp = new HashMap(); ohrstrom@1504: StringBuffer lastPublicApi = null; ohrstrom@1504: for (;;) { ohrstrom@1504: String l = in.readLine(); ohrstrom@1504: if (l == null) { ohrstrom@1504: return ERROR_FATAL; ohrstrom@1504: } ohrstrom@1504: if (l.equals(PROTOCOL_SYSINFO)) { ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: if (l.length() > 1 && l.charAt(0) == '+') { ohrstrom@1504: String pkg = l.substring(1); ohrstrom@1504: lastPublicApi = new StringBuffer(); ohrstrom@1504: tmp.put(pkg, lastPublicApi); ohrstrom@1504: } else if (l.length() > 1 && lastPublicApi != null) { ohrstrom@1504: lastPublicApi.append(l.substring(1)); ohrstrom@1504: lastPublicApi.append("\n"); ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: for (String p : tmp.keySet()) { ohrstrom@1504: assert (packagePublicApis.get(p) == null); ohrstrom@1504: String api = tmp.get(p).toString(); ohrstrom@1504: packagePublicApis.put(p, api); ohrstrom@1504: } ohrstrom@1504: // Now reading the max memory possible. ohrstrom@1504: for (;;) { ohrstrom@1504: String l = in.readLine(); ohrstrom@1504: if (l == null) { ohrstrom@1504: return ERROR_FATAL; ohrstrom@1504: } ohrstrom@1504: if (l.equals(PROTOCOL_RETURN_CODE)) { ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: if (l.startsWith("num_cores=") && sysinfo != null) { ohrstrom@1504: sysinfo.numCores = Integer.parseInt(l.substring(10)); ohrstrom@1504: } ohrstrom@1504: if (l.startsWith("max_memory=") && sysinfo != null) { ohrstrom@1504: sysinfo.maxMemory = Long.parseLong(l.substring(11)); ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: String l = in.readLine(); ohrstrom@1504: if (l == null) { ohrstrom@1504: err.println("No return value from the server!"); ohrstrom@1504: return ERROR_FATAL; ohrstrom@1504: } ohrstrom@1504: rc = Integer.parseInt(l); ohrstrom@1504: out.print(stdout); ohrstrom@1504: err.print(stderr); ohrstrom@1504: } catch (Exception e) { ohrstrom@1504: e.printStackTrace(err); ohrstrom@1504: } ohrstrom@1504: return rc; ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: /** ohrstrom@1504: * Run the server thread until it exits. Either because of inactivity or because the port file has been deleted by someone else, or overtaken by some other ohrstrom@1504: * javac server. ohrstrom@1504: */ ohrstrom@1504: private void run(PortFile portFile, PrintStream err, int keepalive) { ohrstrom@1504: boolean fileDeleted = false; ohrstrom@1504: long timeSinceLastCompile; ohrstrom@1504: try { ohrstrom@1504: // Every 5 second (check_portfile_interval) we test if the portfile has disappeared => quit ohrstrom@1504: // Or if the last request was finished more than 125 seconds ago => quit ohrstrom@1504: // 125 = seconds_of_inactivity_before_shutdown+check_portfile_interval ohrstrom@1504: serverSocket.setSoTimeout(CHECK_PORTFILE_INTERVAL*1000); ohrstrom@1504: for (;;) { ohrstrom@1504: try { ohrstrom@1504: Socket s = serverSocket.accept(); ohrstrom@1504: CompilerThread ct = compilerPool.grabCompilerThread(); ohrstrom@1504: ct.setSocket(s); ohrstrom@1504: compilerPool.execute(ct); ohrstrom@1504: flushLog(); ohrstrom@1504: } catch (java.net.SocketTimeoutException e) { ohrstrom@1504: if (compilerPool.numActiveRequests() > 0) { ohrstrom@1504: // Never quit while there are active requests! ohrstrom@1504: continue; ohrstrom@1504: } ohrstrom@1504: // If this is the timeout after the portfile ohrstrom@1504: // has been deleted by us. Then we truly stop. ohrstrom@1504: if (fileDeleted) { ohrstrom@1504: log("Quitting because of "+(keepalive+CHECK_PORTFILE_INTERVAL)+" seconds of inactivity!"); ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: // Check if the portfile is still there. ohrstrom@1504: if (!portFile.exists()) { ohrstrom@1504: // Time to quit because the portfile was deleted by another ohrstrom@1504: // process, probably by the makefile that is done building. ohrstrom@1504: log("Quitting because portfile was deleted!"); ohrstrom@1504: flushLog(); ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: // Check if portfile.stop is still there. ohrstrom@1504: if (portFile.markedForStop()) { ohrstrom@1504: // Time to quit because another process touched the file ohrstrom@1504: // server.port.stop to signal that the server should stop. ohrstrom@1504: // This is necessary on some operating systems that lock ohrstrom@1504: // the port file hard! ohrstrom@1504: log("Quitting because a portfile.stop file was found!"); ohrstrom@1504: portFile.delete(); ohrstrom@1504: flushLog(); ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: // Does the portfile still point to me? ohrstrom@1504: if (!portFile.stillMyValues()) { ohrstrom@1504: // Time to quit because another build has started. ohrstrom@1504: log("Quitting because portfile is now owned by another javac server!"); ohrstrom@1504: flushLog(); ohrstrom@1504: break; ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: // Check how long since the last request finished. ohrstrom@1504: long diff = System.currentTimeMillis() - compilerPool.lastRequestFinished(); ohrstrom@1504: if (diff < keepalive * 1000) { ohrstrom@1504: // Do not quit if we have waited less than 120 seconds. ohrstrom@1504: continue; ohrstrom@1504: } ohrstrom@1504: // Ok, time to quit because of inactivity. Perhaps the build ohrstrom@1504: // was killed and the portfile not cleaned up properly. ohrstrom@1504: portFile.delete(); ohrstrom@1504: fileDeleted = true; ohrstrom@1504: log("" + keepalive + " seconds of inactivity quitting in " ohrstrom@1504: + CHECK_PORTFILE_INTERVAL + " seconds!"); ohrstrom@1504: flushLog(); ohrstrom@1504: // Now we have a second 5 second grace ohrstrom@1504: // period where javac remote requests ohrstrom@1504: // that have loaded the data from the ohrstrom@1504: // recently deleted portfile can connect ohrstrom@1504: // and complete their requests. ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: } catch (Exception e) { ohrstrom@1504: e.printStackTrace(err); ohrstrom@1504: e.printStackTrace(theLog); ohrstrom@1504: flushLog(); ohrstrom@1504: } finally { ohrstrom@1504: compilerPool.shutdown(); ohrstrom@1504: } ohrstrom@1504: long realTime = System.currentTimeMillis() - serverStart; ohrstrom@1504: log("Shutting down."); ohrstrom@1504: log("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms"); ohrstrom@1504: flushLog(); ohrstrom@1504: } ohrstrom@1504: ohrstrom@1504: public static void cleanup(String... args) { ohrstrom@1504: String settings = Util.findServerSettings(args); ohrstrom@1504: if (settings == null) return; ohrstrom@1504: String portfile = Util.extractStringOption("portfile", settings); ohrstrom@1504: String background = Util.extractStringOption("background", settings); ohrstrom@1504: if (background != null && background.equals("false")) { ohrstrom@1504: // If the server runs within this jvm, then delete the portfile, ohrstrom@1504: // since this jvm is about to exit soon. ohrstrom@1504: File f = new File(portfile); ohrstrom@1504: f.delete(); ohrstrom@1504: } ohrstrom@1504: } ohrstrom@1504: }