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