SjavacClient.java revision 2674:e284f560acf6
1/*
2 * Copyright (c) 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
26package com.sun.tools.sjavac.client;
27
28import java.io.BufferedReader;
29import java.io.File;
30import java.io.FileNotFoundException;
31import java.io.FileReader;
32import java.io.IOException;
33import java.io.ObjectInputStream;
34import java.io.ObjectOutputStream;
35import java.io.PrintWriter;
36import java.io.StringWriter;
37import java.net.InetAddress;
38import java.net.InetSocketAddress;
39import java.net.Socket;
40import java.net.URI;
41import java.util.List;
42import java.util.Set;
43
44import com.sun.tools.sjavac.Log;
45import com.sun.tools.sjavac.ProblemException;
46import com.sun.tools.sjavac.Util;
47import com.sun.tools.sjavac.options.Options;
48import com.sun.tools.sjavac.server.CompilationResult;
49import com.sun.tools.sjavac.server.PortFile;
50import com.sun.tools.sjavac.server.Sjavac;
51import com.sun.tools.sjavac.server.SjavacServer;
52import com.sun.tools.sjavac.server.SysInfo;
53
54/**
55 * Sjavac implementation that delegates requests to a SjavacServer.
56 *
57 *  <p><b>This is NOT part of any supported API.
58 *  If you write code that depends on this, you do so at your own risk.
59 *  This code and its internal interfaces are subject to change or
60 *  deletion without notice.</b>
61 */
62public class SjavacClient implements Sjavac {
63
64    // The id can perhaps be used in the future by the javac server to reuse the
65    // JavaCompiler instance for several compiles using the same id.
66    private final String id;
67    private final String portfileName;
68    private final String logfile;
69    private final String stdouterrfile;
70    private final boolean background;
71
72    // Default keepalive for server is 120 seconds.
73    // I.e. it will accept 120 seconds of inactivity before quitting.
74    private final int keepalive;
75    private final int poolsize;
76
77    // The sjavac option specifies how the server part of sjavac is spawned.
78    // If you have the experimental sjavac in your path, you are done. If not, you have
79    // to point to a com.sun.tools.sjavac.Main that supports --startserver
80    // for example by setting: sjavac=java%20-jar%20...javac.jar%com.sun.tools.sjavac.Main
81    private final String sjavacForkCmd;
82
83    // Wait 2 seconds for response, before giving up on javac server.
84    static int CONNECTION_TIMEOUT = 2000;
85    static int MAX_CONNECT_ATTEMPTS = 3;
86    static int WAIT_BETWEEN_CONNECT_ATTEMPTS = 2000;
87
88    // Store the server conf settings here.
89    private final String settings;
90
91    public SjavacClient(Options options) {
92        String tmpServerConf = options.getServerConf();
93        String serverConf = (tmpServerConf!=null)? tmpServerConf : "";
94        String tmpId = Util.extractStringOption("id", serverConf);
95        id = (tmpId!=null) ? tmpId : "id"+(((new java.util.Random()).nextLong())&Long.MAX_VALUE);
96        String p = Util.extractStringOption("portfile", serverConf);
97        portfileName = (p!=null) ? p : options.getStateDir().toFile().getAbsolutePath()+File.separatorChar+"javac_server";
98        logfile = Util.extractStringOption("logfile", serverConf, portfileName + ".javaclog");
99        stdouterrfile = Util.extractStringOption("stdouterrfile", serverConf, portfileName + ".stdouterr");
100        background = Util.extractBooleanOption("background", serverConf, true);
101        sjavacForkCmd = Util.extractStringOption("sjavac", serverConf, "sjavac");
102        int poolsize = Util.extractIntOption("poolsize", serverConf);
103        keepalive = Util.extractIntOption("keepalive", serverConf, 120);
104
105        this.poolsize = poolsize > 0 ? poolsize : Runtime.getRuntime().availableProcessors();
106        settings = (serverConf.equals("")) ? "id="+id+",portfile="+portfileName : serverConf;
107    }
108
109    /**
110     * Hand out the server settings.
111     * @return The server settings, possibly a default value.
112     */
113    public String serverSettings() {
114        return settings;
115    }
116
117    /**
118     * Make a request to the server only to get the maximum possible heap size to use for compilations.
119     *
120     * @param port_file The port file used to synchronize creation of this server.
121     * @param id The identify of the compilation.
122     * @param out Standard out information.
123     * @param err Standard err information.
124     * @return The maximum heap size in bytes.
125     */
126    @Override
127    public SysInfo getSysInfo() {
128        try (Socket socket = tryConnect()) {
129            // The ObjectInputStream constructor will block until the
130            // corresponding ObjectOutputStream has written and flushed the
131            // header, so it is important that the ObjectOutputStreams on server
132            // and client are opened before the ObjectInputStreams.
133            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
134            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
135            oos.writeObject(id);
136            oos.writeObject(SjavacServer.CMD_SYS_INFO);
137            oos.flush();
138            return (SysInfo) ois.readObject();
139        } catch (IOException | ClassNotFoundException ex) {
140            Log.error("[CLIENT] Exception caught: " + ex);
141            StringWriter sw = new StringWriter();
142            ex.printStackTrace(new PrintWriter(sw));
143        }
144        return null;
145    }
146
147    @Override
148    public CompilationResult compile(String protocolId,
149                                     String invocationId,
150                                     String[] args,
151                                     List<File> explicitSources,
152                                     Set<URI> sourcesToCompile,
153                                     Set<URI> visibleSources) {
154        CompilationResult result;
155        try (Socket socket = tryConnect()) {
156            // The ObjectInputStream constructor will block until the
157            // corresponding ObjectOutputStream has written and flushed the
158            // header, so it is important that the ObjectOutputStreams on server
159            // and client are opened before the ObjectInputStreams.
160            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
161            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
162            oos.writeObject(id);
163            oos.writeObject(SjavacServer.CMD_COMPILE);
164            oos.writeObject(protocolId);
165            oos.writeObject(invocationId);
166            oos.writeObject(args);
167            oos.writeObject(explicitSources);
168            oos.writeObject(sourcesToCompile);
169            oos.writeObject(visibleSources);
170            oos.flush();
171            result = (CompilationResult) ois.readObject();
172        } catch (IOException | ClassNotFoundException ex) {
173            Log.error("Exception caught: " + ex);
174            result = new CompilationResult(CompilationResult.ERROR_FATAL);
175            result.stderr = Util.getStackTrace(ex);
176        }
177        return result;
178    }
179
180    private Socket tryConnect() throws IOException {
181
182        PortFile portFile;
183        try {
184            // This should be taken care of at a higher level (JDK-8048451)
185            portFile = SjavacServer.getPortFile(portfileName);
186        } catch (FileNotFoundException e) {
187            // Reached for instance if directory of port file does not exist
188            Log.error("Port file inaccessable: " + e);
189            throw new RuntimeException(e);
190        }
191        for (int i = 0; i < MAX_CONNECT_ATTEMPTS; i++) {
192            Log.info(String.format("Trying to connect (attempt %d of %d)",
193                                   i+1, MAX_CONNECT_ATTEMPTS));
194            try {
195                if (!makeSureServerIsRunning(portFile))
196                    continue;
197                Socket socket = new Socket();
198                InetAddress localhost = InetAddress.getByName(null);
199                socket.connect(new InetSocketAddress(localhost, portFile.getPort()),
200                               CONNECTION_TIMEOUT);
201                return socket;
202            } catch (ProblemException | IOException ex) {
203                Log.error("Caught exception during tryConnect: " + ex);
204            }
205
206            try {
207                Thread.sleep(WAIT_BETWEEN_CONNECT_ATTEMPTS);
208            } catch (InterruptedException e) {
209                Thread.currentThread().interrupt();
210            }
211        }
212        throw new IOException("Could not connect to server");
213    }
214
215    private boolean makeSureServerIsRunning(PortFile portFile)
216            throws IOException, ProblemException, FileNotFoundException {
217
218        synchronized (portFile) {
219            portFile.lock();
220            portFile.getValues();
221            portFile.unlock();
222        }
223
224        if (!portFile.containsPortInfo()) {
225            String forkCmd = SjavacServer.fork(sjavacForkCmd,
226                                               portFile.getFilename(),
227                                               logfile,
228                                               poolsize,
229                                               keepalive,
230                                               System.err,
231                                               stdouterrfile,
232                                               background);
233            if (!portFile.waitForValidValues()) {
234                // This can be simplified once JDK-8048457 has been addressed
235                // since we won't have an SjavacClient if background = false
236                if (background) {
237                    // There seems be some problem with spawning the external
238                    // process (for instance no fork command provided and no
239                    // sjavac on path)
240                    StringWriter sw = new StringWriter();
241                    SjavacClient.printFailedAttempt(forkCmd,
242                                                    stdouterrfile,
243                                                    new PrintWriter(sw));
244                    Log.error(sw.toString());
245                }
246            }
247        }
248        return portFile.containsPortInfo();
249    }
250
251
252    public static void printFailedAttempt(String cmd, String f, PrintWriter err) {
253        err.println("---- Failed to start javac server with this command -----");
254        err.println(cmd);
255        try {
256            BufferedReader in = new BufferedReader(new FileReader(f));
257            err.println("---- stdout/stderr output from attempt to start javac server -----");
258            for (;;) {
259                String l = in.readLine();
260                if (l == null) {
261                    break;
262                }
263                err.println(l);
264            }
265            err.println("------------------------------------------------------------------");
266        } catch (Exception e) {
267            err.println("The stdout/stderr output in file " + f + " does not exist and the server did not start.");
268        }
269    }
270
271    @Override
272    public void shutdown() {
273        // Nothing to clean up
274    }
275}
276