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