langtools/src/share/classes/com/sun/tools/sjavac/server/SjavacServer.java
changeset 26098 32588700060b
equal deleted inserted replaced
26097:4a16592140fa 26098:32588700060b
       
     1 /*
       
     2  * Copyright (c) 2011, 2014, 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.File;
       
    28 import java.io.FileNotFoundException;
       
    29 import java.io.IOException;
       
    30 import java.io.PrintStream;
       
    31 import java.io.PrintWriter;
       
    32 import java.net.InetAddress;
       
    33 import java.net.InetSocketAddress;
       
    34 import java.net.ServerSocket;
       
    35 import java.net.Socket;
       
    36 import java.net.SocketException;
       
    37 import java.util.ArrayList;
       
    38 import java.util.HashMap;
       
    39 import java.util.Map;
       
    40 import java.util.Random;
       
    41 import java.util.concurrent.atomic.AtomicBoolean;
       
    42 
       
    43 import com.sun.tools.sjavac.ProblemException;
       
    44 import com.sun.tools.sjavac.Util;
       
    45 import com.sun.tools.sjavac.comp.SjavacImpl;
       
    46 import com.sun.tools.sjavac.comp.PooledSjavac;
       
    47 
       
    48 /**
       
    49  * The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server.
       
    50  *
       
    51  *  <p><b>This is NOT part of any supported API.
       
    52  *  If you write code that depends on this, you do so at your own risk.
       
    53  *  This code and its internal interfaces are subject to change or
       
    54  *  deletion without notice.</b>
       
    55  */
       
    56 public class SjavacServer implements Terminable {
       
    57 
       
    58     // Used in protocol to indicate which method to invoke
       
    59     public final static String CMD_COMPILE = "compile";
       
    60     public final static String CMD_SYS_INFO = "sys-info";
       
    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         // Extract options. TODO: Change to proper constructor args
       
    99         portfilename = Util.extractStringOption("portfile", settings);
       
   100         logfile = Util.extractStringOption("logfile", settings);
       
   101         stdouterrfile = Util.extractStringOption("stdouterrfile", settings);
       
   102         keepalive = Util.extractIntOption("keepalive", settings, 120);
       
   103         poolsize = Util.extractIntOption("poolsize", settings,
       
   104                                          Runtime.getRuntime().availableProcessors());
       
   105         this.err = err;
       
   106 
       
   107         myCookie = new Random().nextLong();
       
   108         theLog = new PrintWriter(logfile);
       
   109     }
       
   110 
       
   111 
       
   112     /**
       
   113      * Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time.
       
   114      */
       
   115     public static synchronized PortFile getPortFile(String filename) throws FileNotFoundException {
       
   116         if (allPortFiles == null) {
       
   117             allPortFiles = new HashMap<>();
       
   118         }
       
   119         PortFile pf = allPortFiles.get(filename);
       
   120 
       
   121         // Port file known. Does it still exist?
       
   122         if (pf != null) {
       
   123             try {
       
   124                 if (!pf.exists())
       
   125                     pf = null;
       
   126             } catch (IOException ioex) {
       
   127                 ioex.printStackTrace();
       
   128             }
       
   129         }
       
   130 
       
   131         if (pf == null) {
       
   132             pf = new PortFile(filename);
       
   133             allPortFiles.put(filename, pf);
       
   134         }
       
   135         return pf;
       
   136     }
       
   137 
       
   138     /**
       
   139      * Get the cookie used for this server.
       
   140      */
       
   141     long getCookie() {
       
   142         return myCookie;
       
   143     }
       
   144 
       
   145     /**
       
   146      * Get the port used for this server.
       
   147      */
       
   148     int getPort() {
       
   149         return serverSocket.getLocalPort();
       
   150     }
       
   151 
       
   152     /**
       
   153      * Sum up the total build time for this javac server.
       
   154      */
       
   155     public void addBuildTime(long inc) {
       
   156         totalBuildTime += inc;
       
   157     }
       
   158 
       
   159     /**
       
   160      * Log this message.
       
   161      */
       
   162     public void log(String msg) {
       
   163         if (theLog != null) {
       
   164             theLog.println(msg);
       
   165         } else {
       
   166             System.err.println(msg);
       
   167         }
       
   168     }
       
   169 
       
   170     /**
       
   171      * Make sure the log is flushed.
       
   172      */
       
   173     public void flushLog() {
       
   174         if (theLog != null) {
       
   175             theLog.flush();
       
   176         }
       
   177     }
       
   178 
       
   179     /**
       
   180      * Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3"
       
   181      * is sent as the settings parameter. Returns 0 on success, -1 on failure.
       
   182      */
       
   183     public int startServer() throws IOException {
       
   184         long serverStart = System.currentTimeMillis();
       
   185 
       
   186         // The port file is locked and the server port and cookie is written into it.
       
   187         portFile = getPortFile(portfilename);
       
   188 
       
   189         synchronized (portFile) {
       
   190             portFile.lock();
       
   191             portFile.getValues();
       
   192             if (portFile.containsPortInfo()) {
       
   193                 err.println("Javac server not started because portfile exists!");
       
   194                 portFile.unlock();
       
   195                 return -1;
       
   196             }
       
   197 
       
   198             //           .-----------.   .--------.   .------.
       
   199             // socket -->| IdleReset |-->| Pooled |-->| Impl |--> javac
       
   200             //           '-----------'   '--------'   '------'
       
   201             sjavac = new SjavacImpl();
       
   202             sjavac = new PooledSjavac(sjavac, poolsize);
       
   203             sjavac = new IdleResetSjavac(sjavac,
       
   204                                          this,
       
   205                                          keepalive * 1000);
       
   206 
       
   207             serverSocket = new ServerSocket();
       
   208             InetAddress localhost = InetAddress.getByName(null);
       
   209             serverSocket.bind(new InetSocketAddress(localhost, 0));
       
   210 
       
   211             // At this point the server accepts connections, so it is  now safe
       
   212             // to publish the port / cookie information
       
   213             portFile.setValues(getPort(), getCookie());
       
   214             portFile.unlock();
       
   215         }
       
   216 
       
   217         portFileMonitor = new PortFileMonitor(portFile, this);
       
   218         portFileMonitor.start();
       
   219 
       
   220         log("Sjavac server started. Accepting connections...");
       
   221         log("    port: " + getPort());
       
   222         log("    time: " + new java.util.Date());
       
   223         log("    poolsize: " + poolsize);
       
   224         flushLog();
       
   225 
       
   226         keepAcceptingRequests.set(true);
       
   227         do {
       
   228             try {
       
   229                 Socket socket = serverSocket.accept();
       
   230                 new Thread(new RequestHandler(socket, sjavac)).start();
       
   231             } catch (SocketException se) {
       
   232                 // Caused by serverSocket.close() and indicates shutdown
       
   233             }
       
   234         } while (keepAcceptingRequests.get());
       
   235 
       
   236         log("Shutting down.");
       
   237 
       
   238         // No more connections accepted. If any client managed to connect after
       
   239         // the accept() was interrupted but before the server socket is closed
       
   240         // here, any attempt to read or write to the socket will result in an
       
   241         // IOException on the client side.
       
   242 
       
   243         long realTime = System.currentTimeMillis() - serverStart;
       
   244         log("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
       
   245         flushLog();
       
   246 
       
   247         // Shut down
       
   248         sjavac.shutdown();
       
   249 
       
   250         return 0;
       
   251     }
       
   252 
       
   253     /**
       
   254      * Fork a background process. Returns the command line used that can be printed if something failed.
       
   255      */
       
   256     public static String fork(String sjavac, String portfile, String logfile, int poolsize, int keepalive,
       
   257             final PrintStream err, String stdouterrfile, boolean background)
       
   258             throws IOException, ProblemException {
       
   259         if (stdouterrfile != null && stdouterrfile.trim().equals("")) {
       
   260             stdouterrfile = null;
       
   261         }
       
   262         final String startserver = "--startserver:portfile=" + portfile + ",logfile=" + logfile + ",stdouterrfile=" + stdouterrfile + ",poolsize=" + poolsize + ",keepalive="+ keepalive;
       
   263 
       
   264         if (background) {
       
   265             sjavac += "%20" + startserver;
       
   266             sjavac = sjavac.replaceAll("%20", " ");
       
   267             sjavac = sjavac.replaceAll("%2C", ",");
       
   268             // If the java/sh/cmd launcher fails the failure will be captured by stdouterr because of the redirection here.
       
   269             String[] cmd = {"/bin/sh", "-c", sjavac + " >> " + stdouterrfile + " 2>&1"};
       
   270             if (!(new File("/bin/sh")).canExecute()) {
       
   271                 ArrayList<String> wincmd = new ArrayList<>();
       
   272                 wincmd.add("cmd");
       
   273                 wincmd.add("/c");
       
   274                 wincmd.add("start");
       
   275                 wincmd.add("cmd");
       
   276                 wincmd.add("/c");
       
   277                 wincmd.add(sjavac + " >> " + stdouterrfile + " 2>&1");
       
   278                 cmd = wincmd.toArray(new String[wincmd.size()]);
       
   279             }
       
   280             Process pp = null;
       
   281             try {
       
   282                 pp = Runtime.getRuntime().exec(cmd);
       
   283             } catch (Exception e) {
       
   284                 e.printStackTrace(err);
       
   285                 e.printStackTrace(new PrintWriter(stdouterrfile));
       
   286             }
       
   287             StringBuilder rs = new StringBuilder();
       
   288             for (String s : cmd) {
       
   289                 rs.append(s + " ");
       
   290             }
       
   291             return rs.toString();
       
   292         }
       
   293 
       
   294         // Do not spawn a background server, instead run it within the same JVM.
       
   295         Thread t = new Thread() {
       
   296             @Override
       
   297             public void run() {
       
   298                 try {
       
   299                     SjavacServer server = new SjavacServer(startserver, err);
       
   300                     server.startServer();
       
   301                 } catch (Throwable t) {
       
   302                     t.printStackTrace(err);
       
   303                 }
       
   304             }
       
   305         };
       
   306         t.setDaemon(true);
       
   307         t.start();
       
   308         return "";
       
   309     }
       
   310 
       
   311     @Override
       
   312     public void shutdown(String quitMsg) {
       
   313         if (!keepAcceptingRequests.compareAndSet(false, true)) {
       
   314             // Already stopped, no need to shut down again
       
   315             return;
       
   316         }
       
   317 
       
   318         log("Quitting: " + quitMsg);
       
   319         flushLog();
       
   320 
       
   321         portFileMonitor.shutdown(); // No longer any need to monitor port file
       
   322 
       
   323         // Unpublish port before shutting down socket to minimize the number of
       
   324         // failed connection attempts
       
   325         try {
       
   326             portFile.delete();
       
   327         } catch (IOException e) {
       
   328             e.printStackTrace(theLog);
       
   329         }
       
   330         try {
       
   331             serverSocket.close();
       
   332         } catch (IOException e) {
       
   333             e.printStackTrace(theLog);
       
   334         }
       
   335     }
       
   336 
       
   337     public static void cleanup(String... args) {
       
   338         String settings = Util.findServerSettings(args);
       
   339         if (settings == null) return;
       
   340         String portfile = Util.extractStringOption("portfile", settings);
       
   341         String background = Util.extractStringOption("background", settings);
       
   342         if (background != null && background.equals("false")) {
       
   343             // If the server runs within this jvm, then delete the portfile,
       
   344             // since this jvm is about to exit soon.
       
   345             File f = new File(portfile);
       
   346             f.delete();
       
   347         }
       
   348     }
       
   349 }