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