langtools/src/share/classes/com/sun/tools/sjavac/server/JavacServer.java
changeset 15368 2577ddb7e710
child 20241 1e178dbe29c1
equal deleted inserted replaced
15367:31b57f2b8d0b 15368:2577ddb7e710
       
     1 /*
       
     2  * Copyright (c) 2011-2012, 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 package com.sun.tools.sjavac.server;
       
    26 
       
    27 import java.io.BufferedReader;
       
    28 import java.io.File;
       
    29 import java.io.IOException;
       
    30 import java.io.InputStreamReader;
       
    31 import java.io.PrintWriter;
       
    32 import java.io.FileNotFoundException;
       
    33 import java.net.URI;
       
    34 import java.util.HashSet;
       
    35 import java.util.Set;
       
    36 import java.util.HashMap;
       
    37 import java.util.Map;
       
    38 
       
    39 import java.net.InetAddress;
       
    40 import java.net.InetSocketAddress;
       
    41 import java.net.ServerSocket;
       
    42 import java.net.Socket;
       
    43 import java.net.SocketAddress;
       
    44 import java.util.ArrayList;
       
    45 import java.util.Random;
       
    46 
       
    47 import com.sun.tools.sjavac.Util;
       
    48 import com.sun.tools.sjavac.ProblemException;
       
    49 import java.io.*;
       
    50 import java.util.*;
       
    51 
       
    52 /**
       
    53  * The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server.
       
    54  *
       
    55  * <p><b>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
       
    56  * subject to change or deletion without notice.</b></p>
       
    57  */
       
    58 public class JavacServer {
       
    59     // Responding to this tcp/ip port on localhost.
       
    60 
       
    61     private final ServerSocket serverSocket;
       
    62     // The secret cookie shared between server and client through the port file.
       
    63     private final long myCookie;
       
    64     // When the server was started.
       
    65     private long serverStart;
       
    66     // Accumulated build time for all requests, not counting idle time.
       
    67     private long totalBuildTime;
       
    68     // The javac server specific log file.
       
    69     PrintWriter theLog;
       
    70     // The compiler pool that maintains the compiler threads.
       
    71     CompilerPool compilerPool;
       
    72     // For the client, all port files fetched, one per started javac server.
       
    73     // Though usually only one javac server is started by a client.
       
    74     private static Map<String, PortFile> allPortFiles;
       
    75     private static Map<String, Long> maxServerMemory;
       
    76     final static int ERROR_FATAL = -1;
       
    77     final static int ERROR_BUT_TRY_AGAIN = -4712;
       
    78     final static String PROTOCOL_COOKIE_VERSION = "----THE-COOKIE-V2----";
       
    79     final static String PROTOCOL_CWD = "----THE-CWD----";
       
    80     final static String PROTOCOL_ID = "----THE-ID----";
       
    81     final static String PROTOCOL_ARGS = "----THE-ARGS----";
       
    82     final static String PROTOCOL_SOURCES_TO_COMPILE = "----THE-SOURCES-TO-COMPILE----";
       
    83     final static String PROTOCOL_VISIBLE_SOURCES = "----THE-VISIBLE-SOURCES----";
       
    84     final static String PROTOCOL_END = "----THE-END----";
       
    85     final static String PROTOCOL_STDOUT = "----THE-STDOUT----";
       
    86     final static String PROTOCOL_STDERR = "----THE-STDERR----";
       
    87     final static String PROTOCOL_PACKAGE_ARTIFACTS = "----THE-PACKAGE_ARTIFACTS----";
       
    88     final static String PROTOCOL_PACKAGE_DEPENDENCIES = "----THE-PACKAGE_DEPENDENCIES----";
       
    89     final static String PROTOCOL_PACKAGE_PUBLIC_APIS = "----THE-PACKAGE-PUBLIC-APIS----";
       
    90     final static String PROTOCOL_SYSINFO = "----THE-SYSINFO----";
       
    91     final static String PROTOCOL_RETURN_CODE = "----THE-RETURN-CODE----";
       
    92     // Check if the portfile is gone, every 5 seconds.
       
    93     static int CHECK_PORTFILE_INTERVAL = 5;
       
    94     // Wait 2 seconds for response, before giving up on javac server.
       
    95     static int CONNECTION_TIMEOUT = 2;
       
    96     static int WAIT_BETWEEN_CONNECT_ATTEMPTS = 1;
       
    97     static int MAX_NUM_CONNECT_ATTEMPTS = 3;
       
    98 
       
    99     /**
       
   100      * Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time.
       
   101      */
       
   102     private static synchronized PortFile getPortFile(String filename) throws FileNotFoundException {
       
   103         if (allPortFiles == null) {
       
   104             allPortFiles = new HashMap<String, PortFile>();
       
   105         }
       
   106         PortFile pf = allPortFiles.get(filename);
       
   107         if (pf == null) {
       
   108             pf = new PortFile(filename);
       
   109             allPortFiles.put(filename, pf);
       
   110         }
       
   111         return pf;
       
   112     }
       
   113 
       
   114     /**
       
   115      * Get the cookie used for this server.
       
   116      */
       
   117     long getCookie() {
       
   118         return myCookie;
       
   119     }
       
   120 
       
   121     /**
       
   122      * Get the port used for this server.
       
   123      */
       
   124     int getPort() {
       
   125         return serverSocket.getLocalPort();
       
   126     }
       
   127 
       
   128     /**
       
   129      * Sum up the total build time for this javac server.
       
   130      */
       
   131     public void addBuildTime(long inc) {
       
   132         totalBuildTime += inc;
       
   133     }
       
   134 
       
   135     /**
       
   136      * Log this message.
       
   137      */
       
   138     public void log(String msg) {
       
   139         if (theLog != null) {
       
   140             theLog.println(msg);
       
   141         } else {
       
   142             System.err.println(msg);
       
   143         }
       
   144     }
       
   145 
       
   146     /**
       
   147      * Make sure the log is flushed.
       
   148      */
       
   149     public void flushLog() {
       
   150         if (theLog != null) {
       
   151             theLog.flush();
       
   152         }
       
   153     }
       
   154 
       
   155     /**
       
   156      * Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3"
       
   157      * is sent as the settings parameter. Returns 0 on success, -1 on failure.
       
   158      */
       
   159     public static int startServer(String settings, PrintStream err) {
       
   160         try {
       
   161             String portfile = Util.extractStringOption("portfile", settings);
       
   162             // The log file collects more javac server specific log information.
       
   163             String logfile = Util.extractStringOption("logfile", settings);
       
   164             // The stdouterr file collects all the System.out and System.err writes to disk.
       
   165             String stdouterrfile = Util.extractStringOption("stdouterrfile", settings);
       
   166             // We could perhaps use System.setOut and setErr here.
       
   167             // But for the moment we rely on the client to spawn a shell where stdout
       
   168             // and stderr are redirected already.
       
   169             // The pool size is a limit the number of concurrent compiler threads used.
       
   170             // The server might use less than these to avoid memory problems.
       
   171             int poolsize = Util.extractIntOption("poolsize", settings);
       
   172             if (poolsize <= 0) {
       
   173                 // If not set, default to the number of cores.
       
   174                 poolsize = Runtime.getRuntime().availableProcessors();
       
   175             }
       
   176 
       
   177             // How many seconds of inactivity will the server accept before quitting?
       
   178             int keepalive = Util.extractIntOption("keepalive", settings);
       
   179             if (keepalive <= 0) {
       
   180                 keepalive = 120;
       
   181             }
       
   182             // The port file is locked and the server port and cookie is written into it.
       
   183             PortFile portFile = getPortFile(portfile);
       
   184             JavacServer s;
       
   185 
       
   186             synchronized (portFile) {
       
   187                 portFile.lock();
       
   188                 portFile.getValues();
       
   189                 if (portFile.containsPortInfo()) {
       
   190                     err.println("Javac server not started because portfile exists!");
       
   191                     portFile.unlock();
       
   192                     return -1;
       
   193                 }
       
   194                 s = new JavacServer(poolsize, logfile);
       
   195                 portFile.setValues(s.getPort(), s.getCookie());
       
   196                 portFile.unlock();
       
   197             }
       
   198 
       
   199             // Run the server. Will delete the port file when shutting down.
       
   200             // It will shut down automatically when no new requests have come in
       
   201             // during the last 125 seconds.
       
   202             s.run(portFile, err, keepalive);
       
   203             // The run loop for the server has exited.
       
   204             return 0;
       
   205         } catch (Exception e) {
       
   206             e.printStackTrace(err);
       
   207             return -1;
       
   208         }
       
   209     }
       
   210 
       
   211     /**
       
   212      * Dispatch a compilation request to a javac server.
       
   213      *
       
   214      * @param args are the command line args to javac and is allowed to contain source files, @file and other command line options to javac.
       
   215      *
       
   216      * The generated classes, h files and other artifacts from the javac invocation are stored by the javac server to disk.
       
   217      *
       
   218      * @param sources_to_compile The sources to compile.
       
   219      *
       
   220      * @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!
       
   221      * (Sources to compile are always visible.) The visible sources are those supplied by the (filtered) -sourcepath
       
   222      *
       
   223      * @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
       
   224      * 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.
       
   225      *
       
   226      * The server return meta data about the build in the following parameters.
       
   227      * @param package_artifacts, map from package name to set of created artifacts for that package.
       
   228      * @param package_dependencies, map from package name to set of packages that it depends upon.
       
   229      * @param package_pubapis, map from package name to unique string identifying its pub api.
       
   230      */
       
   231     public static int useServer(String settings, String[] args,
       
   232             Set<URI> sourcesToCompile,
       
   233             Set<URI> visibleSources,
       
   234             Map<URI, Set<String>> visibleClasses,
       
   235             Map<String, Set<URI>> packageArtifacts,
       
   236             Map<String, Set<String>> packageDependencies,
       
   237             Map<String, String> packagePubapis,
       
   238             SysInfo sysinfo,
       
   239             PrintStream out,
       
   240             PrintStream err) {
       
   241         try {
       
   242             // The id can perhaps be used in the future by the javac server to reuse the
       
   243             // JavaCompiler instance for several compiles using the same id.
       
   244             String id = Util.extractStringOption("id", settings);
       
   245             String portfile = Util.extractStringOption("portfile", settings);
       
   246             String logfile = Util.extractStringOption("logfile", settings);
       
   247             String stdouterrfile = Util.extractStringOption("stdouterrfile", settings);
       
   248             String background = Util.extractStringOption("background", settings);
       
   249             if (background == null || !background.equals("false")) {
       
   250                 background = "true";
       
   251             }
       
   252             // The sjavac option specifies how the server part of sjavac is spawned.
       
   253             // If you have the experimental sjavac in your path, you are done. If not, you have
       
   254             // to point to a com.sun.tools.sjavac.Main that supports --startserver
       
   255             // for example by setting: sjavac=java%20-jar%20...javac.jar%com.sun.tools.sjavac.Main
       
   256             String sjavac = Util.extractStringOption("sjavac", settings);
       
   257             int poolsize = Util.extractIntOption("poolsize", settings);
       
   258             int keepalive = Util.extractIntOption("keepalive", settings);
       
   259 
       
   260             if (keepalive <= 0) {
       
   261                 // Default keepalive for server is 120 seconds.
       
   262                 // I.e. it will accept 120 seconds of inactivity before quitting.
       
   263                 keepalive = 120;
       
   264             }
       
   265             if (portfile == null) {
       
   266                 err.println("No portfile was specified!");
       
   267                 return -1;
       
   268             }
       
   269             if (logfile == null) {
       
   270                 logfile = portfile + ".javaclog";
       
   271             }
       
   272             if (stdouterrfile == null) {
       
   273                 stdouterrfile = portfile + ".stdouterr";
       
   274             }
       
   275             // Default to sjavac and hope it is in the path.
       
   276             if (sjavac == null) {
       
   277                 sjavac = "sjavac";
       
   278             }
       
   279 
       
   280             int attempts = 0;
       
   281             int rc = -1;
       
   282             do {
       
   283                 PortFile port_file = getPortFile(portfile);
       
   284                 synchronized (port_file) {
       
   285                     port_file.lock();
       
   286                     port_file.getValues();
       
   287                     port_file.unlock();
       
   288                 }
       
   289                 if (!port_file.containsPortInfo()) {
       
   290                     String cmd = fork(sjavac, port_file.getFilename(), logfile, poolsize, keepalive, err, stdouterrfile, background);
       
   291 
       
   292                     if (background.equals("true") && !port_file.waitForValidValues()) {
       
   293                         // Ouch the server did not start! Lets print its stdouterrfile and the command used.
       
   294                         printFailedAttempt(cmd, stdouterrfile, err);
       
   295                         // And give up.
       
   296                         return -1;
       
   297                     }
       
   298                 }
       
   299                 rc = connectAndCompile(port_file, id, args, sourcesToCompile, visibleSources,
       
   300                         packageArtifacts, packageDependencies, packagePubapis, sysinfo,
       
   301                         out, err);
       
   302                 // Try again until we manage to connect. Any error after that
       
   303                 // will cause the compilation to fail.
       
   304                 if (rc == ERROR_BUT_TRY_AGAIN) {
       
   305                     // We could not connect to the server. Try again.
       
   306                     attempts++;
       
   307                     try {
       
   308                         Thread.sleep(WAIT_BETWEEN_CONNECT_ATTEMPTS);
       
   309                     } catch (InterruptedException e) {
       
   310                     }
       
   311                 }
       
   312             } while (rc == ERROR_BUT_TRY_AGAIN && attempts < MAX_NUM_CONNECT_ATTEMPTS);
       
   313             return rc;
       
   314         } catch (Exception e) {
       
   315             e.printStackTrace(err);
       
   316             return -1;
       
   317         }
       
   318     }
       
   319 
       
   320     private static void printFailedAttempt(String cmd, String f, PrintStream err) {
       
   321         err.println("---- Failed to start javac server with this command -----");
       
   322         err.println(cmd);
       
   323         try {
       
   324             BufferedReader in = new BufferedReader(new FileReader(f));
       
   325             err.println("---- stdout/stderr output from attempt to start javac server -----");
       
   326             for (;;) {
       
   327                 String l = in.readLine();
       
   328                 if (l == null) {
       
   329                     break;
       
   330                 }
       
   331                 err.println(l);
       
   332             }
       
   333             err.println("------------------------------------------------------------------");
       
   334         } catch (Exception e) {
       
   335             err.println("The stdout/stderr output in file " + f + " does not exist and the server did not start.");
       
   336         }
       
   337     }
       
   338 
       
   339     /**
       
   340      * Spawn the server instance.
       
   341      */
       
   342 
       
   343     private JavacServer(int poolSize, String logfile) throws IOException {
       
   344         serverStart = System.currentTimeMillis();
       
   345         // Create a server socket on a random port that is bound to the localhost/127.0.0.1 interface.
       
   346         // I.e only local processes can connect to this port.
       
   347         serverSocket = new ServerSocket(0, 128, InetAddress.getByName(null));
       
   348         compilerPool = new CompilerPool(poolSize, this);
       
   349         Random rnd = new Random();
       
   350         myCookie = rnd.nextLong();
       
   351         theLog = new PrintWriter(logfile);
       
   352         log("Javac server started. port=" + getPort() + " date=" + (new java.util.Date()) + " with poolsize=" + poolSize);
       
   353         flushLog();
       
   354     }
       
   355 
       
   356     /**
       
   357      * Fork a background process. Returns the command line used that can be printed if something failed.
       
   358      */
       
   359     private static String fork(String sjavac, String portfile, String logfile, int poolsize, int keepalive,
       
   360             final PrintStream err, String stdouterrfile, String background)
       
   361             throws IOException, ProblemException {
       
   362         if (stdouterrfile != null && stdouterrfile.trim().equals("")) {
       
   363             stdouterrfile = null;
       
   364         }
       
   365         final String startserver = "--startserver:portfile=" + portfile + ",logfile=" + logfile + ",stdouterrfile=" + stdouterrfile + ",poolsize=" + poolsize + ",keepalive="+ keepalive;
       
   366 
       
   367         if (background.equals("true")) {
       
   368             sjavac += "%20" + startserver;
       
   369             sjavac = sjavac.replaceAll("%20", " ");
       
   370             sjavac = sjavac.replaceAll("%2C", ",");
       
   371             // If the java/sh/cmd launcher fails the failure will be captured by stdouterr because of the redirection here.
       
   372             String[] cmd = {"/bin/sh", "-c", sjavac + " >> " + stdouterrfile + " 2>&1"};
       
   373             if (!(new File("/bin/sh")).canExecute()) {
       
   374                 ArrayList<String> wincmd = new ArrayList<String>();
       
   375                 wincmd.add("cmd");
       
   376                 wincmd.add("/c");
       
   377                 wincmd.add("start");
       
   378                 wincmd.add("cmd");
       
   379                 wincmd.add("/c");
       
   380                 wincmd.add(sjavac + " >> " + stdouterrfile + " 2>&1");
       
   381                 cmd = wincmd.toArray(new String[wincmd.size()]);
       
   382             }
       
   383             Process pp = null;
       
   384             try {
       
   385                 pp = Runtime.getRuntime().exec(cmd);
       
   386             } catch (Exception e) {
       
   387                 e.printStackTrace(err);
       
   388                 e.printStackTrace(new PrintWriter(stdouterrfile));
       
   389             }
       
   390             StringBuilder rs = new StringBuilder();
       
   391             for (String s : cmd) {
       
   392                 rs.append(s + " ");
       
   393             }
       
   394             return rs.toString();
       
   395         }
       
   396 
       
   397         // Do not spawn a background server, instead run it within the same JVM.
       
   398         Thread t = new Thread() {
       
   399             @Override
       
   400             public void run() {
       
   401                 try {
       
   402                     JavacServer.startServer(startserver, err);
       
   403                 } catch (Throwable t) {
       
   404                     t.printStackTrace(err);
       
   405                 }
       
   406             }
       
   407         };
       
   408         t.start();
       
   409         return "";
       
   410     }
       
   411 
       
   412     /**
       
   413      * Expect this key on the next line read from the reader.
       
   414      */
       
   415     private static boolean expect(BufferedReader in, String key) throws IOException {
       
   416         String s = in.readLine();
       
   417         if (s != null && s.equals(key)) {
       
   418             return true;
       
   419         }
       
   420         return false;
       
   421     }
       
   422 
       
   423     /**
       
   424      * Make a request to the server only to get the maximum possible heap size to use for compilations.
       
   425      *
       
   426      * @param port_file The port file used to synchronize creation of this server.
       
   427      * @param id The identify of the compilation.
       
   428      * @param out Standard out information.
       
   429      * @param err Standard err information.
       
   430      * @return The maximum heap size in bytes.
       
   431      */
       
   432     public static SysInfo connectGetSysInfo(String serverSettings, PrintStream out, PrintStream err) {
       
   433         SysInfo sysinfo = new SysInfo(-1, -1);
       
   434         String id = Util.extractStringOption("id", serverSettings);
       
   435         String portfile = Util.extractStringOption("portfile", serverSettings);
       
   436         try {
       
   437             PortFile pf = getPortFile(portfile);
       
   438             useServer(serverSettings, new String[0],
       
   439                     new HashSet<URI>(),
       
   440                     new HashSet<URI>(),
       
   441                     new HashMap<URI, Set<String>>(),
       
   442                     new HashMap<String, Set<URI>>(),
       
   443                     new HashMap<String, Set<String>>(),
       
   444                     new HashMap<String, String>(),
       
   445                     sysinfo, out, err);
       
   446         } catch (Exception e) {
       
   447             e.printStackTrace(err);
       
   448         }
       
   449         return sysinfo;
       
   450     }
       
   451 
       
   452     /**
       
   453      * Connect and compile using the javac server settings and the args. When using more advanced features, the sources_to_compile and visible_sources are
       
   454      * supplied to the server and meta data is returned in package_artifacts, package_dependencies and package_pubapis.
       
   455      */
       
   456     private static int connectAndCompile(PortFile portFile, String id, String[] args,
       
   457             Set<URI> sourcesToCompile,
       
   458             Set<URI> visibleSources,
       
   459             Map<String, Set<URI>> packageArtifacts,
       
   460             Map<String, Set<String>> packageDependencies,
       
   461             Map<String, String> packagePublicApis,
       
   462             SysInfo sysinfo,
       
   463             PrintStream out,
       
   464             PrintStream err) {
       
   465         int rc = -3;
       
   466         try {
       
   467             int port = portFile.getPort();
       
   468             if (port == 0) {
       
   469                 return ERROR_BUT_TRY_AGAIN;
       
   470             }
       
   471             long cookie = portFile.getCookie();
       
   472 
       
   473             // Acquire the localhost/127.0.0.1 address.
       
   474             InetAddress addr = InetAddress.getByName(null);
       
   475             SocketAddress sockaddr = new InetSocketAddress(addr, port);
       
   476             Socket sock = new Socket();
       
   477             int timeoutMs = CONNECTION_TIMEOUT * 1000;
       
   478             try {
       
   479                 sock.connect(sockaddr, timeoutMs);
       
   480             } catch (java.net.ConnectException e) {
       
   481                 err.println("Could not connect to javac server found in portfile: " + portFile.getFilename() + " " + e);
       
   482                 return ERROR_BUT_TRY_AGAIN;
       
   483             }
       
   484             if (!sock.isConnected()) {
       
   485                 err.println("Could not connect to javac server found in portfile: " + portFile.getFilename());
       
   486                 return ERROR_BUT_TRY_AGAIN;
       
   487             }
       
   488             BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
       
   489             PrintWriter sockout = new PrintWriter(sock.getOutputStream());
       
   490 
       
   491             sockout.println(PROTOCOL_COOKIE_VERSION);
       
   492             sockout.println("" + cookie);
       
   493             sockout.println(PROTOCOL_CWD);
       
   494             sockout.println(System.getProperty("user.dir"));
       
   495             sockout.println(PROTOCOL_ID);
       
   496             sockout.println(id);
       
   497             sockout.println(PROTOCOL_ARGS);
       
   498             for (String s : args) {
       
   499                 StringBuffer buf = new StringBuffer();
       
   500                 String[] paths = s.split(File.pathSeparator);
       
   501                 int c = 0;
       
   502                 for (String path : paths) {
       
   503                     File f = new File(path);
       
   504                     if (f.isFile() || f.isDirectory()) {
       
   505                         buf.append(f.getAbsolutePath());
       
   506                         c++;
       
   507                         if (c < paths.length) {
       
   508                             buf.append(File.pathSeparator);
       
   509                         }
       
   510                     } else {
       
   511                         buf = new StringBuffer(s);
       
   512                         break;
       
   513                     }
       
   514                 }
       
   515                 sockout.println(buf.toString());
       
   516             }
       
   517             sockout.println(PROTOCOL_SOURCES_TO_COMPILE);
       
   518             for (URI uri : sourcesToCompile) {
       
   519                 sockout.println(uri.toString());
       
   520             }
       
   521             sockout.println(PROTOCOL_VISIBLE_SOURCES);
       
   522             for (URI uri : visibleSources) {
       
   523                 sockout.println(uri.toString());
       
   524             }
       
   525             sockout.println(PROTOCOL_END);
       
   526             sockout.flush();
       
   527 
       
   528             StringBuffer stdout = new StringBuffer();
       
   529             StringBuffer stderr = new StringBuffer();
       
   530 
       
   531             if (!expect(in, PROTOCOL_STDOUT)) {
       
   532                 return ERROR_FATAL;
       
   533             }
       
   534             // Load stdout
       
   535             for (;;) {
       
   536                 String l = in.readLine();
       
   537                 if (l == null) {
       
   538                     return ERROR_FATAL;
       
   539                 }
       
   540                 if (l.equals(PROTOCOL_STDERR)) {
       
   541                     break;
       
   542                 }
       
   543                 stdout.append(l);
       
   544                 stdout.append('\n');
       
   545             }
       
   546             // Load stderr
       
   547             for (;;) {
       
   548                 String l = in.readLine();
       
   549                 if (l == null) {
       
   550                     return ERROR_FATAL;
       
   551                 }
       
   552                 if (l.equals(PROTOCOL_PACKAGE_ARTIFACTS)) {
       
   553                     break;
       
   554                 }
       
   555                 stderr.append(l);
       
   556                 stderr.append('\n');
       
   557             }
       
   558             // Load the package artifacts
       
   559             Set<URI> lastUriSet = null;
       
   560             for (;;) {
       
   561                 String l = in.readLine();
       
   562                 if (l == null) {
       
   563                     return ERROR_FATAL;
       
   564                 }
       
   565                 if (l.equals(PROTOCOL_PACKAGE_DEPENDENCIES)) {
       
   566                     break;
       
   567                 }
       
   568                 if (l.length() > 1 && l.charAt(0) == '+') {
       
   569                     String pkg = l.substring(1);
       
   570                     lastUriSet = new HashSet<URI>();
       
   571                     packageArtifacts.put(pkg, lastUriSet);
       
   572                 } else if (l.length() > 1 && lastUriSet != null) {
       
   573                     lastUriSet.add(new URI(l.substring(1)));
       
   574                 }
       
   575             }
       
   576             // Load package dependencies
       
   577             Set<String> lastPackageSet = null;
       
   578             for (;;) {
       
   579                 String l = in.readLine();
       
   580                 if (l == null) {
       
   581                     return ERROR_FATAL;
       
   582                 }
       
   583                 if (l.equals(PROTOCOL_PACKAGE_PUBLIC_APIS)) {
       
   584                     break;
       
   585                 }
       
   586                 if (l.length() > 1 && l.charAt(0) == '+') {
       
   587                     String pkg = l.substring(1);
       
   588                     lastPackageSet = new HashSet<String>();
       
   589                     packageDependencies.put(pkg, lastPackageSet);
       
   590                 } else if (l.length() > 1 && lastPackageSet != null) {
       
   591                     lastPackageSet.add(l.substring(1));
       
   592                 }
       
   593             }
       
   594             // Load package pubapis
       
   595             Map<String, StringBuffer> tmp = new HashMap<String, StringBuffer>();
       
   596             StringBuffer lastPublicApi = null;
       
   597             for (;;) {
       
   598                 String l = in.readLine();
       
   599                 if (l == null) {
       
   600                     return ERROR_FATAL;
       
   601                 }
       
   602                 if (l.equals(PROTOCOL_SYSINFO)) {
       
   603                     break;
       
   604                 }
       
   605                 if (l.length() > 1 && l.charAt(0) == '+') {
       
   606                     String pkg = l.substring(1);
       
   607                     lastPublicApi = new StringBuffer();
       
   608                     tmp.put(pkg, lastPublicApi);
       
   609                 } else if (l.length() > 1 && lastPublicApi != null) {
       
   610                     lastPublicApi.append(l.substring(1));
       
   611                     lastPublicApi.append("\n");
       
   612                 }
       
   613             }
       
   614             for (String p : tmp.keySet()) {
       
   615                 assert (packagePublicApis.get(p) == null);
       
   616                 String api = tmp.get(p).toString();
       
   617                 packagePublicApis.put(p, api);
       
   618             }
       
   619             // Now reading the max memory possible.
       
   620             for (;;) {
       
   621                 String l = in.readLine();
       
   622                 if (l == null) {
       
   623                     return ERROR_FATAL;
       
   624                 }
       
   625                 if (l.equals(PROTOCOL_RETURN_CODE)) {
       
   626                     break;
       
   627                 }
       
   628                 if (l.startsWith("num_cores=") && sysinfo != null) {
       
   629                     sysinfo.numCores = Integer.parseInt(l.substring(10));
       
   630                 }
       
   631                 if (l.startsWith("max_memory=") && sysinfo != null) {
       
   632                     sysinfo.maxMemory = Long.parseLong(l.substring(11));
       
   633                 }
       
   634             }
       
   635             String l = in.readLine();
       
   636             if (l == null) {
       
   637                 err.println("No return value from the server!");
       
   638                 return ERROR_FATAL;
       
   639             }
       
   640             rc = Integer.parseInt(l);
       
   641             out.print(stdout);
       
   642             err.print(stderr);
       
   643         } catch (Exception e) {
       
   644             e.printStackTrace(err);
       
   645         }
       
   646         return rc;
       
   647     }
       
   648 
       
   649     /**
       
   650      * 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
       
   651      * javac server.
       
   652      */
       
   653     private void run(PortFile portFile, PrintStream err, int keepalive) {
       
   654         boolean fileDeleted = false;
       
   655         long timeSinceLastCompile;
       
   656         try {
       
   657             // Every 5 second (check_portfile_interval) we test if the portfile has disappeared => quit
       
   658             // Or if the last request was finished more than 125 seconds ago => quit
       
   659             // 125 = seconds_of_inactivity_before_shutdown+check_portfile_interval
       
   660             serverSocket.setSoTimeout(CHECK_PORTFILE_INTERVAL*1000);
       
   661             for (;;) {
       
   662                 try {
       
   663                     Socket s = serverSocket.accept();
       
   664                     CompilerThread ct = compilerPool.grabCompilerThread();
       
   665                     ct.setSocket(s);
       
   666                     compilerPool.execute(ct);
       
   667                     flushLog();
       
   668                 } catch (java.net.SocketTimeoutException e) {
       
   669                     if (compilerPool.numActiveRequests() > 0) {
       
   670                         // Never quit while there are active requests!
       
   671                         continue;
       
   672                     }
       
   673                     // If this is the timeout after the portfile
       
   674                     // has been deleted by us. Then we truly stop.
       
   675                     if (fileDeleted) {
       
   676                         log("Quitting because of "+(keepalive+CHECK_PORTFILE_INTERVAL)+" seconds of inactivity!");
       
   677                         break;
       
   678                     }
       
   679                     // Check if the portfile is still there.
       
   680                     if (!portFile.exists()) {
       
   681                         // Time to quit because the portfile was deleted by another
       
   682                         // process, probably by the makefile that is done building.
       
   683                         log("Quitting because portfile was deleted!");
       
   684                         flushLog();
       
   685                         break;
       
   686                     }
       
   687                     // Check if portfile.stop is still there.
       
   688                     if (portFile.markedForStop()) {
       
   689                         // Time to quit because another process touched the file
       
   690                         // server.port.stop to signal that the server should stop.
       
   691                         // This is necessary on some operating systems that lock
       
   692                         // the port file hard!
       
   693                         log("Quitting because a portfile.stop file was found!");
       
   694                         portFile.delete();
       
   695                         flushLog();
       
   696                         break;
       
   697                     }
       
   698                     // Does the portfile still point to me?
       
   699                     if (!portFile.stillMyValues()) {
       
   700                         // Time to quit because another build has started.
       
   701                         log("Quitting because portfile is now owned by another javac server!");
       
   702                         flushLog();
       
   703                         break;
       
   704                     }
       
   705 
       
   706                     // Check how long since the last request finished.
       
   707                     long diff = System.currentTimeMillis() - compilerPool.lastRequestFinished();
       
   708                     if (diff < keepalive * 1000) {
       
   709                         // Do not quit if we have waited less than 120 seconds.
       
   710                         continue;
       
   711                     }
       
   712                     // Ok, time to quit because of inactivity. Perhaps the build
       
   713                     // was killed and the portfile not cleaned up properly.
       
   714                     portFile.delete();
       
   715                     fileDeleted = true;
       
   716                     log("" + keepalive + " seconds of inactivity quitting in "
       
   717                         + CHECK_PORTFILE_INTERVAL + " seconds!");
       
   718                     flushLog();
       
   719                     // Now we have a second 5 second grace
       
   720                     // period where javac remote requests
       
   721                     // that have loaded the data from the
       
   722                     // recently deleted portfile can connect
       
   723                     // and complete their requests.
       
   724                 }
       
   725             }
       
   726         } catch (Exception e) {
       
   727             e.printStackTrace(err);
       
   728             e.printStackTrace(theLog);
       
   729             flushLog();
       
   730         } finally {
       
   731             compilerPool.shutdown();
       
   732         }
       
   733         long realTime = System.currentTimeMillis() - serverStart;
       
   734         log("Shutting down.");
       
   735         log("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
       
   736         flushLog();
       
   737     }
       
   738 
       
   739     public static void cleanup(String... args) {
       
   740         String settings = Util.findServerSettings(args);
       
   741         if (settings == null) return;
       
   742         String portfile = Util.extractStringOption("portfile", settings);
       
   743         String background = Util.extractStringOption("background", settings);
       
   744         if (background != null && background.equals("false")) {
       
   745             // If the server runs within this jvm, then delete the portfile,
       
   746             // since this jvm is about to exit soon.
       
   747             File f = new File(portfile);
       
   748             f.delete();
       
   749         }
       
   750     }
       
   751 }