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