SjavacServer.java revision 3267:5282596d34b3
1168054Sflz/* 2168054Sflz * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. 3168266Sgabor * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4168266Sgabor * 5168266Sgabor * This code is free software; you can redistribute it and/or modify it 6168266Sgabor * under the terms of the GNU General Public License version 2 only, as 7168266Sgabor * published by the Free Software Foundation. Oracle designates this 8168266Sgabor * particular file as subject to the "Classpath" exception as provided 9168266Sgabor * by Oracle in the LICENSE file that accompanied this code. 10168266Sgabor * 11168054Sflz * This code is distributed in the hope that it will be useful, but WITHOUT 12168054Sflz * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13168064Sflz * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14168064Sflz * version 2 for more details (a copy is included in the LICENSE file that 15168064Sflz * accompanied this code). 16168064Sflz * 17168064Sflz * You should have received a copy of the GNU General Public License version 18168064Sflz * 2 along with this work; if not, write to the Free Software Foundation, 19168064Sflz * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20168064Sflz * 21168064Sflz * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22168064Sflz * or visit www.oracle.com if you need additional information or have any 23168064Sflz * questions. 24168064Sflz */ 25168064Sflz 26168064Sflzpackage com.sun.tools.sjavac.server; 27168054Sflz 28168054Sflzimport java.io.FileNotFoundException; 29168064Sflzimport java.io.FileWriter; 30168054Sflzimport java.io.IOException; 31168064Sflzimport java.io.PrintStream; 32168131Sbmahimport java.io.PrintWriter; 33168113Smarcusimport java.net.InetAddress; 34168123Snetchildimport java.net.InetSocketAddress; 35168064Sflzimport java.net.ServerSocket; 36168054Sflzimport java.net.Socket; 37168054Sflzimport java.net.SocketException; 38168054Sflzimport java.util.HashMap; 39168054Sflzimport java.util.Map; 40168261Sacheimport java.util.Random; 41168077Sflzimport java.util.concurrent.atomic.AtomicBoolean; 42168077Sflz 43168126Saleimport com.sun.tools.sjavac.Log; 44168069Sgargaimport com.sun.tools.sjavac.Util; 45168472Snovelimport com.sun.tools.sjavac.client.PortFileInaccessibleException; 46168274Ssemimport com.sun.tools.sjavac.comp.PooledSjavac; 47168274Ssemimport com.sun.tools.sjavac.comp.SjavacImpl; 48168113Smarcus 49168098Skrion/** 50168123Snetchild * The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server. 51168082Sgarga * 52168116Sclsung * <p><b>This is NOT part of any supported API. 53168177Sgabor * If you write code that depends on this, you do so at your own risk. 54168354Sdanfe * This code and its internal interfaces are subject to change or 55168072Sehaupt * deletion without notice.</b> 56168108Srafan */ 57168186Smatpublic class SjavacServer implements Terminable { 58168210Sitetcu 59168068Serwin // Prefix of line containing return code. 60168072Sehaupt public final static String LINE_TYPE_RC = "RC"; 61168113Smarcus 62168059Sgabor final private String portfilename; 63168098Skrion final private int poolsize; 64168054Sflz final private int keepalive; 65168059Sgabor 66168054Sflz // The secret cookie shared between server and client through the port file. 67168256Sijliao // Used to prevent clients from believing that they are communicating with 68168209Sitetcu // an old server when a new server has started and reused the same port as 69168076Sjmelo // an old server. 70168123Snetchild private final long myCookie; 71168054Sflz 72168055Spav // Accumulated build time, not counting idle time, used for logging purposes 73168055Spav private long totalBuildTime; 74168536Skevlo 75168177Sgabor // The sjavac implementation to delegate requests to 76168098Skrion Sjavac sjavac; 77168055Spav 78168161Sphilip private ServerSocket serverSocket; 79168054Sflz 80168208Sitetcu private PortFile portFile; 81168295Sleeym private PortFileMonitor portFileMonitor; 82168295Sleeym 83168068Serwin // Set to false break accept loop 84168337Slwhsu final AtomicBoolean keepAcceptingRequests = new AtomicBoolean(); 85168177Sgabor 86168113Smarcus // For the client, all port files fetched, one per started javac server. 87168186Smat // Though usually only one javac server is started by a client. 88168055Spav private static Map<String, PortFile> allPortFiles; 89168098Skrion 90168359Smm public SjavacServer(String settings) throws FileNotFoundException { 91168100Smnag this(Util.extractStringOption("portfile", settings), 92168123Snetchild Util.extractIntOption("poolsize", settings, Runtime.getRuntime().availableProcessors()), 93168177Sgabor Util.extractIntOption("keepalive", settings, 120)); 94168134Snork } 95168084Sehaupt 96168054Sflz public SjavacServer(String portfilename, 97168098Skrion int poolsize, 98168108Srafan int keepalive) 99168098Skrion throws FileNotFoundException { 100168098Skrion this.portfilename = portfilename; 101168098Skrion this.poolsize = poolsize; 102168055Spav this.keepalive = keepalive; 103168068Serwin this.myCookie = new Random().nextLong(); 104168290Ssem } 105168125Stdb 106168225Strhodes 107168186Smat /** 108168061Sahze * Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time. 109168069Sgarga */ 110168054Sflz public static synchronized PortFile getPortFile(String filename) { 111168054Sflz if (allPortFiles == null) { 112168064Sflz allPortFiles = new HashMap<>(); 113168064Sflz } 114168054Sflz PortFile pf = allPortFiles.get(filename); 115168055Spav 116168055Spav // Port file known. Does it still exist? 117168055Spav if (pf != null) { 118168055Spav try { 119168055Spav if (!pf.exists()) 120168055Spav pf = null; 121168057Sahze } catch (IOException ioex) { 122168055Spav ioex.printStackTrace(); 123168125Stdb } 124168208Sitetcu } 125168125Stdb 126168337Slwhsu if (pf == null) { 127168337Slwhsu pf = new PortFile(filename); 128168108Srafan allPortFiles.put(filename, pf); 129168108Srafan } 130168186Smat return pf; 131168186Smat } 132168068Serwin 133168068Serwin /** 134168072Sehaupt * Get the cookie used for this server. 135168072Sehaupt */ 136168274Ssem long getCookie() { 137168225Strhodes return myCookie; 138168225Strhodes } 139168068Serwin 140168059Sgabor /** 141168068Serwin * Get the port used for this server. 142168068Serwin */ 143168068Serwin int getPort() { 144168059Sgabor return serverSocket.getLocalPort(); 145168354Sdanfe } 146168098Skrion 147168098Skrion /** 148168054Sflz * Sum up the total build time for this javac server. 149168054Sflz */ 150168054Sflz public void addBuildTime(long inc) { 151168054Sflz totalBuildTime += inc; 152168069Sgarga } 153168069Sgarga 154168359Smm /** 155168069Sgarga * Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3" 156168069Sgarga * is sent as the settings parameter. Returns 0 on success, -1 on failure. 157168295Sleeym */ 158168295Sleeym public int startServer() throws IOException, InterruptedException { 159168210Sitetcu long serverStart = System.currentTimeMillis(); 160168210Sitetcu 161168123Snetchild // The port file is locked and the server port and cookie is written into it. 162168123Snetchild portFile = getPortFile(portfilename); 163168134Snork 164168134Snork synchronized (portFile) { 165168134Snork portFile.lock(); 166168134Snork portFile.getValues(); 167168134Snork if (portFile.containsPortInfo()) { 168168098Skrion Log.info("Javac server not started because portfile exists!"); 169168098Skrion portFile.unlock(); 170168098Skrion return -1; 171168098Skrion } 172168098Skrion 173168098Skrion // .-----------. .--------. .------. 174168098Skrion // socket -->| IdleReset |-->| Pooled |-->| Impl |--> javac 175168098Skrion // '-----------' '--------' '------' 176168209Sitetcu sjavac = new SjavacImpl(); 177168209Sitetcu sjavac = new PooledSjavac(sjavac, poolsize); 178168295Sleeym sjavac = new IdleResetSjavac(sjavac, 179168295Sleeym this, 180168113Smarcus keepalive * 1000); 181168113Smarcus 182168113Smarcus serverSocket = new ServerSocket(); 183168113Smarcus InetAddress localhost = InetAddress.getByName(null); 184168186Smat serverSocket.bind(new InetSocketAddress(localhost, 0)); 185168186Smat 186168076Sjmelo // At this point the server accepts connections, so it is now safe 187168076Sjmelo // to publish the port / cookie information 188168123Snetchild portFile.setValues(getPort(), getCookie()); 189168123Snetchild portFile.unlock(); 190168126Sale } 191168126Sale 192168472Snovel portFileMonitor = new PortFileMonitor(portFile, this); 193168084Sehaupt portFileMonitor.start(); 194168084Sehaupt 195168054Sflz Log.info("Sjavac server started. Accepting connections..."); 196168055Spav Log.info(" port: " + getPort()); 197168055Spav Log.info(" time: " + new java.util.Date()); 198168055Spav Log.info(" poolsize: " + poolsize); 199168054Sflz 200168161Sphilip 201168161Sphilip keepAcceptingRequests.set(true); 202168274Ssem do { 203168274Ssem try { 204168108Srafan Socket socket = serverSocket.accept(); 205168274Ssem new RequestHandler(socket, sjavac).start(); 206168108Srafan } catch (SocketException se) { 207168123Snetchild // Caused by serverSocket.close() and indicates shutdown 208168123Snetchild } 209168209Sitetcu } while (keepAcceptingRequests.get()); 210168209Sitetcu 211168054Sflz Log.info("Shutting down."); 212 213 // No more connections accepted. If any client managed to connect after 214 // the accept() was interrupted but before the server socket is closed 215 // here, any attempt to read or write to the socket will result in an 216 // IOException on the client side. 217 218 long realTime = System.currentTimeMillis() - serverStart; 219 Log.info("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms"); 220 221 // Shut down 222 sjavac.shutdown(); 223 224 return 0; 225 } 226 227 @Override 228 public void shutdown(String quitMsg) { 229 if (!keepAcceptingRequests.compareAndSet(true, false)) { 230 // Already stopped, no need to shut down again 231 return; 232 } 233 234 Log.info("Quitting: " + quitMsg); 235 236 portFileMonitor.shutdown(); // No longer any need to monitor port file 237 238 // Unpublish port before shutting down socket to minimize the number of 239 // failed connection attempts 240 try { 241 portFile.delete(); 242 } catch (IOException | InterruptedException e) { 243 Log.error(e); 244 } 245 try { 246 serverSocket.close(); 247 } catch (IOException e) { 248 Log.error(e); 249 } 250 } 251} 252