PortFile.java revision 3277:178ce5786775
1189251Ssam/*
2189251Ssam * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
3189251Ssam * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4189251Ssam *
5189251Ssam * This code is free software; you can redistribute it and/or modify it
6189251Ssam * under the terms of the GNU General Public License version 2 only, as
7189251Ssam * published by the Free Software Foundation.  Oracle designates this
8189251Ssam * 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.File;
29import java.io.FileNotFoundException;
30import java.io.IOException;
31import java.io.RandomAccessFile;
32import java.nio.channels.ClosedChannelException;
33import java.nio.channels.FileChannel;
34import java.nio.channels.FileLock;
35import java.nio.channels.FileLockInterruptionException;
36import java.util.concurrent.Semaphore;
37
38import com.sun.tools.javac.util.Assert;
39import com.sun.tools.sjavac.Log;
40import com.sun.tools.sjavac.client.PortFileInaccessibleException;
41
42/**
43 * The PortFile class mediates access to a short binary file containing the tcp/ip port (for the localhost)
44 * and a cookie necessary for the server answering on that port. The file can be locked using file system
45 * primitives to avoid race conditions when several javac clients are started at the same. Note that file
46 * system locking is not always supported on a all operating systems and/or file systems.
47 *
48 *  <p><b>This is NOT part of any supported API.
49 *  If you write code that depends on this, you do so at your own risk.
50 *  This code and its internal interfaces are subject to change or
51 *  deletion without notice.</b>
52 */
53public class PortFile {
54
55    // Port file format:
56    // byte ordering: high byte first = big endian
57    // Magic nr, 4 byte int, first in file.
58    private final static int magicNr = 0x1174;
59    // Followed by a 4 byte int, with the port nr.
60    // Followed by a 8 byte long, with cookie nr.
61
62    private String filename;
63    private File file;
64    private File stopFile;
65    private RandomAccessFile rwfile;
66    private FileChannel channel;
67
68    // FileLock used to solve inter JVM synchronization, lockSem used to avoid
69    // JVM internal OverlappingFileLockExceptions.
70    // Class invariant: lock.isValid() <-> lockSem.availablePermits() == 0
71    private FileLock lock;
72    private Semaphore lockSem = new Semaphore(1);
73
74    private boolean containsPortInfo;
75    private int serverPort;
76    private long serverCookie;
77    private int myServerPort;
78    private long myServerCookie;
79
80    /**
81     * Create a new portfile.
82     * @param fn is the path to the file.
83     */
84    public PortFile(String fn) {
85        filename = fn;
86        file = new File(filename);
87        stopFile = new File(filename+".stop");
88        containsPortInfo = false;
89        lock = null;
90    }
91
92    private void initializeChannel() throws PortFileInaccessibleException {
93        try {
94            rwfile = new RandomAccessFile(file, "rw");
95        } catch (FileNotFoundException e) {
96            // Reached if file for instance already exists and is a directory
97            throw new PortFileInaccessibleException(e);
98        }
99        // The rwfile should only be readable by the owner of the process
100        // and no other! How do we do that on a RandomAccessFile?
101        channel = rwfile.getChannel();
102    }
103
104    /**
105     * Lock the port file.
106     */
107    public void lock() throws IOException, InterruptedException {
108        if (channel == null) {
109            initializeChannel();
110        }
111        lockSem.acquire();
112        lock = channel.lock();
113    }
114
115    /**
116     * Read the values from the port file in the file system.
117     * Expects the port file to be locked.
118     */
119    public void getValues()  {
120        containsPortInfo = false;
121        if (lock == null) {
122            // Not locked, remain ignorant about port file contents.
123            return;
124        }
125        try {
126            if (rwfile.length()>0) {
127                rwfile.seek(0);
128                int nr = rwfile.readInt();
129                serverPort = rwfile.readInt();
130                serverCookie = rwfile.readLong();
131
132                if (nr == magicNr) {
133                    containsPortInfo = true;
134                } else {
135                    containsPortInfo = false;
136                }
137            }
138        } catch (IOException e) {
139            containsPortInfo = false;
140        }
141    }
142
143    /**
144     * Did the locking and getValues succeed?
145     */
146    public boolean containsPortInfo() {
147        return containsPortInfo;
148    }
149
150    /**
151     * If so, then we can acquire the tcp/ip port on localhost.
152     */
153    public int getPort() {
154        Assert.check(containsPortInfo);
155        return serverPort;
156    }
157
158    /**
159     * If so, then we can acquire the server cookie.
160     */
161    public long getCookie() {
162        Assert.check(containsPortInfo);
163        return serverCookie;
164    }
165
166    /**
167     * Store the values into the locked port file.
168     */
169    public void setValues(int port, long cookie) throws IOException {
170        Assert.check(lock != null);
171        rwfile.seek(0);
172        // Write the magic nr that identifes a port file.
173        rwfile.writeInt(magicNr);
174        rwfile.writeInt(port);
175        rwfile.writeLong(cookie);
176        myServerPort = port;
177        myServerCookie = cookie;
178    }
179
180    /**
181     * Delete the port file.
182     */
183    public void delete() throws IOException, InterruptedException {
184        // Access to file must be closed before deleting.
185        rwfile.close();
186
187        file.delete();
188
189        // Wait until file has been deleted (deletes are asynchronous on Windows!) otherwise we
190        // might shutdown the server and prevent another one from starting.
191        for (int i = 0; i < 10 && file.exists(); i++) {
192            Thread.sleep(1000);
193        }
194        if (file.exists()) {
195            throw new IOException("Failed to delete file.");
196        }
197    }
198
199    /**
200     * Is the port file still there?
201     */
202    public boolean exists() throws IOException {
203        return file.exists();
204    }
205
206    /**
207     * Is a stop file there?
208     */
209    public boolean markedForStop() throws IOException {
210        if (stopFile.exists()) {
211            try {
212                stopFile.delete();
213            } catch (Exception e) {
214            }
215            return true;
216        }
217        return false;
218    }
219
220    /**
221     * Unlock the port file.
222     */
223    public void unlock() throws IOException {
224        if (lock == null) {
225            return;
226        }
227        lock.release();
228        lock = null;
229        lockSem.release();
230    }
231
232    /**
233     * Wait for the port file to contain values that look valid.
234     */
235    public void waitForValidValues() throws IOException, InterruptedException {
236        final int MS_BETWEEN_ATTEMPTS = 500;
237        long startTime = System.currentTimeMillis();
238        long timeout = startTime + getServerStartupTimeoutSeconds() * 1000;
239        while (true) {
240            Log.debug("Looking for valid port file values...");
241            if (exists()) {
242                lock();
243                getValues();
244                unlock();
245            }
246            if (containsPortInfo) {
247                Log.debug("Valid port file values found after " + (System.currentTimeMillis() - startTime) + " ms");
248                return;
249            }
250            if (System.currentTimeMillis() > timeout) {
251                break;
252            }
253            Thread.sleep(MS_BETWEEN_ATTEMPTS);
254        }
255        throw new IOException("No port file values materialized. Giving up after " +
256                                      (System.currentTimeMillis() - startTime) + " ms");
257    }
258
259    /**
260     * Check if the portfile still contains my values, assuming that I am the server.
261     */
262    public boolean stillMyValues() throws IOException, FileNotFoundException, InterruptedException {
263        for (;;) {
264            try {
265                lock();
266                getValues();
267                unlock();
268                if (containsPortInfo) {
269                    if (serverPort == myServerPort &&
270                        serverCookie == myServerCookie) {
271                        // Everything is ok.
272                        return true;
273                    }
274                    // Someone has overwritten the port file.
275                    // Probably another javac server, lets quit.
276                    return false;
277                }
278                // Something else is wrong with the portfile. Lets quit.
279                return false;
280            } catch (FileLockInterruptionException e) {
281                continue;
282            }
283            catch (ClosedChannelException e) {
284                // The channel has been closed since sjavac is exiting.
285                return false;
286            }
287        }
288    }
289
290    /**
291     * Return the name of the port file.
292     */
293    public String getFilename() {
294        return filename;
295    }
296
297    private long getServerStartupTimeoutSeconds() {
298        String str = System.getProperty("serverStartupTimeout");
299        if (str != null) {
300            try {
301                return Integer.parseInt(str);
302            } catch (NumberFormatException e) {
303            }
304        }
305        return 60;
306    }
307}
308