PortFile.java revision 3267:5282596d34b3
1159687Snetchild/*
2159687Snetchild * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
3159687Snetchild * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4159687Snetchild *
5159687Snetchild * This code is free software; you can redistribute it and/or modify it
6159687Snetchild * under the terms of the GNU General Public License version 2 only, as
7159687Snetchild * published by the Free Software Foundation.  Oracle designates this
8159687Snetchild * particular file as subject to the "Classpath" exception as provided
9159687Snetchild * by Oracle in the LICENSE file that accompanied this code.
10159687Snetchild *
11159687Snetchild * This code is distributed in the hope that it will be useful, but WITHOUT
12159687Snetchild * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13159687Snetchild * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14159687Snetchild * version 2 for more details (a copy is included in the LICENSE file that
15159687Snetchild * accompanied this code).
16159687Snetchild *
17159687Snetchild * You should have received a copy of the GNU General Public License version
18159687Snetchild * 2 along with this work; if not, write to the Free Software Foundation,
19159687Snetchild * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20159687Snetchild *
21159687Snetchild * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22159687Snetchild * or visit www.oracle.com if you need additional information or have any
23159687Snetchild * questions.
24159687Snetchild */
25159687Snetchild
26159687Snetchildpackage com.sun.tools.sjavac.server;
27159687Snetchild
28159687Snetchildimport java.io.File;
29159687Snetchildimport java.io.FileNotFoundException;
30159687Snetchildimport java.io.IOException;
31159687Snetchildimport java.io.RandomAccessFile;
32159687Snetchildimport java.nio.channels.ClosedChannelException;
33159687Snetchildimport java.nio.channels.FileChannel;
34159687Snetchildimport java.nio.channels.FileLock;
35159687Snetchildimport java.nio.channels.FileLockInterruptionException;
36159687Snetchildimport java.util.concurrent.Semaphore;
37159687Snetchild
38159687Snetchildimport com.sun.tools.javac.util.Assert;
39159687Snetchildimport com.sun.tools.sjavac.Log;
40159687Snetchildimport com.sun.tools.sjavac.client.PortFileInaccessibleException;
41159687Snetchild
42159687Snetchild/**
43159687Snetchild * The PortFile class mediates access to a short binary file containing the tcp/ip port (for the localhost)
44159687Snetchild * and a cookie necessary for the server answering on that port. The file can be locked using file system
45159687Snetchild * primitives to avoid race conditions when several javac clients are started at the same. Note that file
46159687Snetchild * system locking is not always supported on a all operating systems and/or file systems.
47159687Snetchild *
48159687Snetchild *  <p><b>This is NOT part of any supported API.
49159687Snetchild *  If you write code that depends on this, you do so at your own risk.
50159687Snetchild *  This code and its internal interfaces are subject to change or
51159687Snetchild *  deletion without notice.</b>
52159687Snetchild */
53159687Snetchildpublic class PortFile {
54159687Snetchild
55159687Snetchild    // Port file format:
56159687Snetchild    // byte ordering: high byte first = big endian
57159687Snetchild    // Magic nr, 4 byte int, first in file.
58159687Snetchild    private final static int magicNr = 0x1174;
59159687Snetchild    // Followed by a 4 byte int, with the port nr.
60159687Snetchild    // Followed by a 8 byte long, with cookie nr.
61159687Snetchild
62159687Snetchild    private String filename;
63159687Snetchild    private File file;
64159687Snetchild    private File stopFile;
65159687Snetchild    private RandomAccessFile rwfile;
66159687Snetchild    private FileChannel channel;
67159687Snetchild
68159687Snetchild    // FileLock used to solve inter JVM synchronization, lockSem used to avoid
69159687Snetchild    // JVM internal OverlappingFileLockExceptions.
70159687Snetchild    // Class invariant: lock.isValid() <-> lockSem.availablePermits() == 0
71159687Snetchild    private FileLock lock;
72159687Snetchild    private Semaphore lockSem = new Semaphore(1);
73159687Snetchild
74159687Snetchild    private boolean containsPortInfo;
75159687Snetchild    private int serverPort;
76159687Snetchild    private long serverCookie;
77159687Snetchild    private int myServerPort;
78159687Snetchild    private long myServerCookie;
79159687Snetchild
80159687Snetchild    /**
81159687Snetchild     * Create a new portfile.
82159687Snetchild     * @param fn is the path to the file.
83159687Snetchild     */
84159687Snetchild    public PortFile(String fn) {
85159687Snetchild        filename = fn;
86159687Snetchild        file = new File(filename);
87159687Snetchild        stopFile = new File(filename+".stop");
88159687Snetchild        containsPortInfo = false;
89159687Snetchild        lock = null;
90159687Snetchild    }
91159687Snetchild
92159687Snetchild    private void initializeChannel() throws PortFileInaccessibleException {
93159687Snetchild        try {
94159687Snetchild            rwfile = new RandomAccessFile(file, "rw");
95159687Snetchild        } catch (FileNotFoundException e) {
96159687Snetchild            // Reached if file for instance already exists and is a directory
97159687Snetchild            throw new PortFileInaccessibleException(e);
98159687Snetchild        }
99159687Snetchild        // The rwfile should only be readable by the owner of the process
100159687Snetchild        // and no other! How do we do that on a RandomAccessFile?
101159687Snetchild        channel = rwfile.getChannel();
102159687Snetchild    }
103159687Snetchild
104159687Snetchild    /**
105159687Snetchild     * Lock the port file.
106159687Snetchild     */
107159687Snetchild    public void lock() throws IOException, InterruptedException {
108159687Snetchild        if (channel == null) {
109159687Snetchild            initializeChannel();
110159687Snetchild        }
111159687Snetchild        lockSem.acquire();
112159687Snetchild        lock = channel.lock();
113159687Snetchild    }
114159687Snetchild
115159687Snetchild    /**
116159687Snetchild     * Read the values from the port file in the file system.
117159687Snetchild     * Expects the port file to be locked.
118159687Snetchild     */
119159687Snetchild    public void getValues()  {
120159687Snetchild        containsPortInfo = false;
121159687Snetchild        if (lock == null) {
122159687Snetchild            // Not locked, remain ignorant about port file contents.
123159687Snetchild            return;
124159687Snetchild        }
125159687Snetchild        try {
126159687Snetchild            if (rwfile.length()>0) {
127159687Snetchild                rwfile.seek(0);
128159687Snetchild                int nr = rwfile.readInt();
129159687Snetchild                serverPort = rwfile.readInt();
130159687Snetchild                serverCookie = rwfile.readLong();
131159687Snetchild
132159687Snetchild                if (nr == magicNr) {
133159687Snetchild                    containsPortInfo = true;
134159687Snetchild                } else {
135159687Snetchild                    containsPortInfo = false;
136159687Snetchild                }
137159687Snetchild            }
138159687Snetchild        } catch (IOException e) {
139159687Snetchild            containsPortInfo = false;
140159687Snetchild        }
141159687Snetchild    }
142159687Snetchild
143159687Snetchild    /**
144159687Snetchild     * Did the locking and getValues succeed?
145159687Snetchild     */
146159687Snetchild    public boolean containsPortInfo() {
147159687Snetchild        return containsPortInfo;
148159687Snetchild    }
149159687Snetchild
150159687Snetchild    /**
151159687Snetchild     * If so, then we can acquire the tcp/ip port on localhost.
152159687Snetchild     */
153159687Snetchild    public int getPort() {
154159687Snetchild        Assert.check(containsPortInfo);
155159687Snetchild        return serverPort;
156159687Snetchild    }
157159687Snetchild
158159687Snetchild    /**
159159687Snetchild     * If so, then we can acquire the server cookie.
160159687Snetchild     */
161159687Snetchild    public long getCookie() {
162159687Snetchild        Assert.check(containsPortInfo);
163159687Snetchild        return serverCookie;
164159687Snetchild    }
165159687Snetchild
166159687Snetchild    /**
167159687Snetchild     * Store the values into the locked port file.
168159687Snetchild     */
169159687Snetchild    public void setValues(int port, long cookie) throws IOException {
170159687Snetchild        Assert.check(lock != null);
171159687Snetchild        rwfile.seek(0);
172159687Snetchild        // Write the magic nr that identifes a port file.
173159687Snetchild        rwfile.writeInt(magicNr);
174159687Snetchild        rwfile.writeInt(port);
175159687Snetchild        rwfile.writeLong(cookie);
176159687Snetchild        myServerPort = port;
177159687Snetchild        myServerCookie = cookie;
178159687Snetchild    }
179159687Snetchild
180159687Snetchild    /**
181159687Snetchild     * Delete the port file.
182159687Snetchild     */
183159687Snetchild    public void delete() throws IOException, InterruptedException {
184159687Snetchild        // Access to file must be closed before deleting.
185159687Snetchild        rwfile.close();
186159687Snetchild
187159687Snetchild        file.delete();
188159687Snetchild
189159687Snetchild        // Wait until file has been deleted (deletes are asynchronous on Windows!) otherwise we
190159687Snetchild        // might shutdown the server and prevent another one from starting.
191159687Snetchild        for (int i = 0; i < 10 && file.exists(); i++) {
192159687Snetchild            Thread.sleep(1000);
193159687Snetchild        }
194159687Snetchild        if (file.exists()) {
195159687Snetchild            throw new IOException("Failed to delete file.");
196159687Snetchild        }
197159687Snetchild    }
198159687Snetchild
199159687Snetchild    /**
200159687Snetchild     * Is the port file still there?
201159687Snetchild     */
202159687Snetchild    public boolean exists() throws IOException {
203159687Snetchild        return file.exists();
204159687Snetchild    }
205159687Snetchild
206159687Snetchild    /**
207159687Snetchild     * Is a stop file there?
208159687Snetchild     */
209159687Snetchild    public boolean markedForStop() throws IOException {
210159687Snetchild        if (stopFile.exists()) {
211159687Snetchild            try {
212159687Snetchild                stopFile.delete();
213159687Snetchild            } catch (Exception e) {
214159687Snetchild            }
215159687Snetchild            return true;
216159687Snetchild        }
217159687Snetchild        return false;
218159687Snetchild    }
219159687Snetchild
220159687Snetchild    /**
221159687Snetchild     * Unlock the port file.
222159687Snetchild     */
223159687Snetchild    public void unlock() throws IOException {
224159687Snetchild        if (lock == null) {
225159687Snetchild            return;
226159687Snetchild        }
227159687Snetchild        lock.release();
228159687Snetchild        lock = null;
229159687Snetchild        lockSem.release();
230159687Snetchild    }
231159687Snetchild
232159687Snetchild    /**
233159687Snetchild     * Wait for the port file to contain values that look valid.
234159687Snetchild     */
235159687Snetchild    public void waitForValidValues() throws IOException, InterruptedException {
236159687Snetchild        final int MS_BETWEEN_ATTEMPTS = 500;
237159687Snetchild        long startTime = System.currentTimeMillis();
238159687Snetchild        long timeout = startTime + getServerStartupTimeoutSeconds() * 1000;
239159687Snetchild        while (true) {
240159687Snetchild            Log.debug("Looking for valid port file values...");
241159687Snetchild            if (exists()) {
242159687Snetchild                lock();
243159687Snetchild                getValues();
244159687Snetchild                unlock();
245159687Snetchild            }
246159687Snetchild            if (containsPortInfo) {
247159687Snetchild                Log.debug("Valid port file values found after " + (System.currentTimeMillis() - startTime) + " ms");
248159687Snetchild                return;
249159687Snetchild            }
250159687Snetchild            if (System.currentTimeMillis() > timeout) {
251159687Snetchild                break;
252159687Snetchild            }
253159687Snetchild            Thread.sleep(MS_BETWEEN_ATTEMPTS);
254159687Snetchild        }
255159687Snetchild        throw new IOException("No port file values materialized. Giving up after " +
256159687Snetchild                                      (System.currentTimeMillis() - startTime) + " ms");
257159687Snetchild    }
258159687Snetchild
259159687Snetchild    /**
260159687Snetchild     * Check if the portfile still contains my values, assuming that I am the server.
261159687Snetchild     */
262159687Snetchild    public boolean stillMyValues() throws IOException, FileNotFoundException, InterruptedException {
263159687Snetchild        for (;;) {
264159687Snetchild            try {
265159687Snetchild                lock();
266159687Snetchild                getValues();
267159687Snetchild                unlock();
268159687Snetchild                if (containsPortInfo) {
269159687Snetchild                    if (serverPort == myServerPort &&
270159687Snetchild                        serverCookie == myServerCookie) {
271159687Snetchild                        // Everything is ok.
272159687Snetchild                        return true;
273159687Snetchild                    }
274159687Snetchild                    // Someone has overwritten the port file.
275159687Snetchild                    // Probably another javac server, lets quit.
276159687Snetchild                    return false;
277159687Snetchild                }
278159687Snetchild                // Something else is wrong with the portfile. Lets quit.
279159687Snetchild                return false;
280159687Snetchild            } catch (FileLockInterruptionException e) {
281159687Snetchild                continue;
282159687Snetchild            }
283159687Snetchild            catch (ClosedChannelException e) {
284159687Snetchild                // The channel has been closed since sjavac is exiting.
285159687Snetchild                return false;
286159687Snetchild            }
287159687Snetchild        }
288159687Snetchild    }
289159687Snetchild
290159687Snetchild    /**
291159687Snetchild     * Return the name of the port file.
292159687Snetchild     */
293159687Snetchild    public String getFilename() {
294159687Snetchild        return filename;
295159687Snetchild    }
296159687Snetchild
297159687Snetchild    private long getServerStartupTimeoutSeconds() {
298159687Snetchild        String str = System.getProperty("serverStartupTimeout");
299159687Snetchild        if (str != null) {
300159687Snetchild            try {
301159687Snetchild                return Integer.parseInt(str);
302159687Snetchild            } catch (NumberFormatException e) {
303159687Snetchild            }
304159687Snetchild        }
305159687Snetchild        return 60;
306159687Snetchild    }
307159687Snetchild}
308159687Snetchild