PortFile.java revision 3202:a5066095d36e
1/*
2 * Copyright (c) 2012, 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.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) throws PortFileInaccessibleException {
85        filename = fn;
86        file = new File(filename);
87        stopFile = new File(filename+".stop");
88        try {
89            rwfile = new RandomAccessFile(file, "rw");
90        } catch (FileNotFoundException e) {
91            // Reached if file for instance already exists and is a directory
92            throw new PortFileInaccessibleException(e);
93        }
94        // The rwfile should only be readable by the owner of the process
95        // and no other! How do we do that on a RandomAccessFile?
96        channel = rwfile.getChannel();
97        containsPortInfo = false;
98        lock = null;
99    }
100
101    /**
102     * Lock the port file.
103     */
104    public void lock() throws IOException, InterruptedException {
105        lockSem.acquire();
106        lock = channel.lock();
107    }
108
109    /**
110     * Read the values from the port file in the file system.
111     * Expects the port file to be locked.
112     */
113    public void getValues()  {
114        containsPortInfo = false;
115        if (lock == null) {
116            // Not locked, remain ignorant about port file contents.
117            return;
118        }
119        try {
120            if (rwfile.length()>0) {
121                rwfile.seek(0);
122                int nr = rwfile.readInt();
123                serverPort = rwfile.readInt();
124                serverCookie = rwfile.readLong();
125
126                if (nr == magicNr) {
127                    containsPortInfo = true;
128                } else {
129                    containsPortInfo = false;
130                }
131            }
132        } catch (IOException e) {
133            containsPortInfo = false;
134        }
135    }
136
137    /**
138     * Did the locking and getValues succeed?
139     */
140    public boolean containsPortInfo() {
141        return containsPortInfo;
142    }
143
144    /**
145     * If so, then we can acquire the tcp/ip port on localhost.
146     */
147    public int getPort() {
148        Assert.check(containsPortInfo);
149        return serverPort;
150    }
151
152    /**
153     * If so, then we can acquire the server cookie.
154     */
155    public long getCookie() {
156        Assert.check(containsPortInfo);
157        return serverCookie;
158    }
159
160    /**
161     * Store the values into the locked port file.
162     */
163    public void setValues(int port, long cookie) throws IOException {
164        Assert.check(lock != null);
165        rwfile.seek(0);
166        // Write the magic nr that identifes a port file.
167        rwfile.writeInt(magicNr);
168        rwfile.writeInt(port);
169        rwfile.writeLong(cookie);
170        myServerPort = port;
171        myServerCookie = cookie;
172    }
173
174    /**
175     * Delete the port file.
176     */
177    public void delete() throws IOException, InterruptedException {
178        // Access to file must be closed before deleting.
179        rwfile.close();
180
181        file.delete();
182
183        // Wait until file has been deleted (deletes are asynchronous on Windows!) otherwise we
184        // might shutdown the server and prevent another one from starting.
185        for (int i = 0; i < 10 && file.exists(); i++) {
186            Thread.sleep(1000);
187        }
188        if (file.exists()) {
189            throw new IOException("Failed to delete file.");
190        }
191    }
192
193    /**
194     * Is the port file still there?
195     */
196    public boolean exists() throws IOException {
197        return file.exists();
198    }
199
200    /**
201     * Is a stop file there?
202     */
203    public boolean markedForStop() throws IOException {
204        if (stopFile.exists()) {
205            try {
206                stopFile.delete();
207            } catch (Exception e)
208            {}
209            return true;
210        }
211        return false;
212    }
213
214    /**
215     * Unlock the port file.
216     */
217    public void unlock() throws IOException {
218        Assert.check(lock != null);
219        lock.release();
220        lock = null;
221        lockSem.release();
222    }
223
224    /**
225     * Wait for the port file to contain values that look valid.
226     */
227    public void waitForValidValues() throws IOException, InterruptedException {
228        final int MAX_ATTEMPTS = 10;
229        final int MS_BETWEEN_ATTEMPTS = 500;
230        long startTime = System.currentTimeMillis();
231        for (int attempt = 0; ; attempt++) {
232            Log.debug("Looking for valid port file values...");
233            lock();
234            getValues();
235            unlock();
236            if (containsPortInfo) {
237                Log.debug("Valid port file values found after " + (System.currentTimeMillis() - startTime) + " ms");
238                return;
239            }
240            if (attempt >= MAX_ATTEMPTS) {
241                throw new IOException("No port file values materialized. Giving up after " +
242                                      (System.currentTimeMillis() - startTime) + " ms");
243            }
244            Thread.sleep(MS_BETWEEN_ATTEMPTS);
245        }
246    }
247
248    /**
249     * Check if the portfile still contains my values, assuming that I am the server.
250     */
251    public boolean stillMyValues() throws IOException, FileNotFoundException, InterruptedException {
252        for (;;) {
253            try {
254                lock();
255                getValues();
256                unlock();
257                if (containsPortInfo) {
258                    if (serverPort == myServerPort &&
259                        serverCookie == myServerCookie) {
260                        // Everything is ok.
261                        return true;
262                    }
263                    // Someone has overwritten the port file.
264                    // Probably another javac server, lets quit.
265                    return false;
266                }
267                // Something else is wrong with the portfile. Lets quit.
268                return false;
269            } catch (FileLockInterruptionException e) {
270                continue;
271            }
272            catch (ClosedChannelException e) {
273                // The channel has been closed since sjavac is exiting.
274                return false;
275            }
276        }
277    }
278
279    /**
280     * Return the name of the port file.
281     */
282    public String getFilename() {
283        return filename;
284    }
285}
286