SjavacServer.java revision 3202:a5066095d36e
1/*
2 * Copyright (c) 2011, 2016, 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
26package com.sun.tools.sjavac.server;
27
28import java.io.FileNotFoundException;
29import java.io.IOException;
30import java.io.PrintStream;
31import java.io.PrintWriter;
32import java.net.InetAddress;
33import java.net.InetSocketAddress;
34import java.net.ServerSocket;
35import java.net.Socket;
36import java.net.SocketException;
37import java.util.HashMap;
38import java.util.Map;
39import java.util.Random;
40import java.util.concurrent.atomic.AtomicBoolean;
41
42import com.sun.tools.sjavac.Util;
43import com.sun.tools.sjavac.client.PortFileInaccessibleException;
44import com.sun.tools.sjavac.comp.PooledSjavac;
45import com.sun.tools.sjavac.comp.SjavacImpl;
46
47/**
48 * The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server.
49 *
50 *  <p><b>This is NOT part of any supported API.
51 *  If you write code that depends on this, you do so at your own risk.
52 *  This code and its internal interfaces are subject to change or
53 *  deletion without notice.</b>
54 */
55public class SjavacServer implements Terminable {
56
57    // Used in protocol to tell the content of each line
58    public final static String LINE_TYPE_RC = "RC";
59    public final static String LINE_TYPE_STDOUT = "STDOUT";
60    public final static String LINE_TYPE_STDERR = "STDERR";
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        this(Util.extractStringOption("portfile", settings),
99             Util.extractStringOption("logfile", settings),
100             Util.extractStringOption("stdouterrfile", settings),
101             Util.extractIntOption("poolsize", settings, Runtime.getRuntime().availableProcessors()),
102             Util.extractIntOption("keepalive", settings, 120),
103             err);
104    }
105
106    public SjavacServer(String portfilename,
107                        String logfile,
108                        String stdouterrfile,
109                        int poolsize,
110                        int keepalive,
111                        PrintStream err)
112                                throws FileNotFoundException {
113        this.portfilename = portfilename;
114        this.logfile = logfile;
115        this.stdouterrfile = stdouterrfile;
116        this.poolsize = poolsize;
117        this.keepalive = keepalive;
118        this.err = err;
119
120        myCookie = new Random().nextLong();
121        theLog = new PrintWriter(logfile);
122    }
123
124
125    /**
126     * Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time.
127     */
128    public static synchronized PortFile getPortFile(String filename) throws PortFileInaccessibleException {
129        if (allPortFiles == null) {
130            allPortFiles = new HashMap<>();
131        }
132        PortFile pf = allPortFiles.get(filename);
133
134        // Port file known. Does it still exist?
135        if (pf != null) {
136            try {
137                if (!pf.exists())
138                    pf = null;
139            } catch (IOException ioex) {
140                ioex.printStackTrace();
141            }
142        }
143
144        if (pf == null) {
145            pf = new PortFile(filename);
146            allPortFiles.put(filename, pf);
147        }
148        return pf;
149    }
150
151    /**
152     * Get the cookie used for this server.
153     */
154    long getCookie() {
155        return myCookie;
156    }
157
158    /**
159     * Get the port used for this server.
160     */
161    int getPort() {
162        return serverSocket.getLocalPort();
163    }
164
165    /**
166     * Sum up the total build time for this javac server.
167     */
168    public void addBuildTime(long inc) {
169        totalBuildTime += inc;
170    }
171
172    /**
173     * Log this message.
174     */
175    public void log(String msg) {
176        if (theLog != null) {
177            theLog.println(msg);
178        } else {
179            System.err.println(msg);
180        }
181    }
182
183    /**
184     * Make sure the log is flushed.
185     */
186    public void flushLog() {
187        if (theLog != null) {
188            theLog.flush();
189        }
190    }
191
192    /**
193     * Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3"
194     * is sent as the settings parameter. Returns 0 on success, -1 on failure.
195     */
196    public int startServer() throws IOException, InterruptedException {
197        long serverStart = System.currentTimeMillis();
198
199        // The port file is locked and the server port and cookie is written into it.
200        portFile = getPortFile(portfilename);
201
202        synchronized (portFile) {
203            portFile.lock();
204            portFile.getValues();
205            if (portFile.containsPortInfo()) {
206                err.println("Javac server not started because portfile exists!");
207                portFile.unlock();
208                return -1;
209            }
210
211            //           .-----------.   .--------.   .------.
212            // socket -->| IdleReset |-->| Pooled |-->| Impl |--> javac
213            //           '-----------'   '--------'   '------'
214            sjavac = new SjavacImpl();
215            sjavac = new PooledSjavac(sjavac, poolsize);
216            sjavac = new IdleResetSjavac(sjavac,
217                                         this,
218                                         keepalive * 1000);
219
220            serverSocket = new ServerSocket();
221            InetAddress localhost = InetAddress.getByName(null);
222            serverSocket.bind(new InetSocketAddress(localhost, 0));
223
224            // At this point the server accepts connections, so it is  now safe
225            // to publish the port / cookie information
226            portFile.setValues(getPort(), getCookie());
227            portFile.unlock();
228        }
229
230        portFileMonitor = new PortFileMonitor(portFile, this);
231        portFileMonitor.start();
232
233        log("Sjavac server started. Accepting connections...");
234        log("    port: " + getPort());
235        log("    time: " + new java.util.Date());
236        log("    poolsize: " + poolsize);
237        flushLog();
238
239        keepAcceptingRequests.set(true);
240        do {
241            try {
242                Socket socket = serverSocket.accept();
243                new Thread(new RequestHandler(socket, sjavac)).start();
244            } catch (SocketException se) {
245                // Caused by serverSocket.close() and indicates shutdown
246            }
247        } while (keepAcceptingRequests.get());
248
249        log("Shutting down.");
250
251        // No more connections accepted. If any client managed to connect after
252        // the accept() was interrupted but before the server socket is closed
253        // here, any attempt to read or write to the socket will result in an
254        // IOException on the client side.
255
256        long realTime = System.currentTimeMillis() - serverStart;
257        log("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
258        flushLog();
259
260        // Shut down
261        sjavac.shutdown();
262
263        return 0;
264    }
265
266    @Override
267    public void shutdown(String quitMsg) {
268        if (!keepAcceptingRequests.compareAndSet(true, false)) {
269            // Already stopped, no need to shut down again
270            return;
271        }
272
273        log("Quitting: " + quitMsg);
274        flushLog();
275
276        portFileMonitor.shutdown(); // No longer any need to monitor port file
277
278        // Unpublish port before shutting down socket to minimize the number of
279        // failed connection attempts
280        try {
281            portFile.delete();
282        } catch (IOException | InterruptedException e) {
283            e.printStackTrace(theLog);
284        }
285        try {
286            serverSocket.close();
287        } catch (IOException e) {
288            e.printStackTrace(theLog);
289        }
290    }
291}
292