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