ecc603ca9b
Reviewed-by: erikj, ihse
313 lines
9.8 KiB
Java
313 lines
9.8 KiB
Java
/*
|
|
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package javacserver.shared;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.RandomAccessFile;
|
|
import java.nio.channels.ClosedChannelException;
|
|
import java.nio.channels.FileChannel;
|
|
import java.nio.channels.FileLock;
|
|
import java.nio.channels.FileLockInterruptionException;
|
|
import java.util.concurrent.Semaphore;
|
|
import javacserver.util.Log;
|
|
|
|
/**
|
|
* The PortFile class mediates access to a short binary file containing the tcp/ip port (for the localhost)
|
|
* and a cookie necessary for the server answering on that port. The file can be locked using file system
|
|
* primitives to avoid race conditions when several javac clients are started at the same. Note that file
|
|
* system locking is not always supported on all operating systems and/or file systems.
|
|
*/
|
|
public class PortFile {
|
|
// Port file format:
|
|
// byte ordering: high byte first = big endian
|
|
// Magic nr, 4 byte int, first in file.
|
|
private static final int magicNr = 0x1174;
|
|
// Followed by a 4 byte int, with the port nr.
|
|
// Followed by a 8 byte long, with cookie nr.
|
|
|
|
private final String filename;
|
|
private final File file;
|
|
private final File stopFile;
|
|
private RandomAccessFile rwfile;
|
|
|
|
// FileLock used to solve inter JVM synchronization, lockSem used to avoid
|
|
// JVM internal OverlappingFileLockExceptions.
|
|
// Class invariant: lock.isValid() <-> lockSem.availablePermits() == 0
|
|
private FileLock lock;
|
|
private final Semaphore lockSem = new Semaphore(1);
|
|
|
|
private boolean containsPortInfo;
|
|
private int serverPort;
|
|
private long serverCookie;
|
|
private int myServerPort;
|
|
private long myServerCookie;
|
|
|
|
/**
|
|
* Create a new portfile.
|
|
* @param fn is the path to the file.
|
|
*/
|
|
public PortFile(String fn) {
|
|
filename = fn;
|
|
file = new File(filename);
|
|
stopFile = new File(filename+".stop");
|
|
containsPortInfo = false;
|
|
lock = null;
|
|
}
|
|
|
|
private void initializeChannel() throws PortFileInaccessibleException {
|
|
try {
|
|
rwfile = new RandomAccessFile(file, "rw");
|
|
} catch (FileNotFoundException e) {
|
|
// Reached if file for instance already exists and is a directory
|
|
throw new PortFileInaccessibleException(e);
|
|
}
|
|
// The rwfile should only be readable by the owner of the process
|
|
// and no other! How do we do that on a RandomAccessFile?
|
|
}
|
|
|
|
/**
|
|
* Lock the port file.
|
|
*/
|
|
public void lock() throws IOException, InterruptedException {
|
|
lockSem.acquire();
|
|
if (rwfile != null) {
|
|
throw new IllegalStateException("rwfile not null");
|
|
}
|
|
initializeChannel();
|
|
FileChannel channel = rwfile.getChannel();
|
|
lock = channel.lock();
|
|
}
|
|
|
|
/**
|
|
* Read the values from the port file in the file system.
|
|
* Expects the port file to be locked.
|
|
*/
|
|
public void getValues() {
|
|
containsPortInfo = false;
|
|
if (lock == null) {
|
|
throw new IllegalStateException("Must lock before calling getValues");
|
|
}
|
|
try {
|
|
if (rwfile.length()>0) {
|
|
rwfile.seek(0);
|
|
int nr = rwfile.readInt();
|
|
serverPort = rwfile.readInt();
|
|
serverCookie = rwfile.readLong();
|
|
|
|
if (nr == magicNr) {
|
|
containsPortInfo = true;
|
|
} else {
|
|
containsPortInfo = false;
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
containsPortInfo = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Did the locking and getValues succeed?
|
|
*/
|
|
public boolean containsPortInfo() {
|
|
return containsPortInfo;
|
|
}
|
|
|
|
/**
|
|
* If so, then we can acquire the tcp/ip port on localhost.
|
|
*/
|
|
public int getPort() {
|
|
return serverPort;
|
|
}
|
|
|
|
/**
|
|
* If so, then we can acquire the server cookie.
|
|
*/
|
|
public long getCookie() {
|
|
return serverCookie;
|
|
}
|
|
|
|
/**
|
|
* Store the values into the locked port file.
|
|
*/
|
|
public void setValues(int port, long cookie) throws IOException {
|
|
if (lock == null) {
|
|
throw new IllegalStateException("Must lock before calling setValues");
|
|
}
|
|
rwfile.seek(0);
|
|
// Write the magic nr that identifies a port file.
|
|
rwfile.writeInt(magicNr);
|
|
rwfile.writeInt(port);
|
|
rwfile.writeLong(cookie);
|
|
myServerPort = port;
|
|
myServerCookie = cookie;
|
|
}
|
|
|
|
/**
|
|
* Delete the port file.
|
|
*/
|
|
public void delete() throws IOException, InterruptedException {
|
|
if (!file.exists()) { // file deleted already
|
|
return;
|
|
}
|
|
// Keep trying until file has been deleted, otherwise we
|
|
// might shutdown the server and prevent another one from starting.
|
|
for (int i = 0; i < 10 && file.exists() && !file.delete(); i++) {
|
|
Thread.sleep(1000);
|
|
}
|
|
if (file.exists()) {
|
|
throw new IOException("Failed to delete file.");
|
|
}
|
|
// allow some time for late clients to connect
|
|
Thread.sleep(1000);
|
|
}
|
|
|
|
/**
|
|
* Is the port file still there?
|
|
*/
|
|
public boolean exists() throws IOException {
|
|
return file.exists();
|
|
}
|
|
|
|
/**
|
|
* Is a stop file there?
|
|
*/
|
|
public boolean markedForStop() throws IOException {
|
|
if (stopFile.exists()) {
|
|
try {
|
|
stopFile.delete();
|
|
} catch (Exception e) {
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Unlock the port file.
|
|
*/
|
|
public void unlock() throws IOException {
|
|
if (lock == null) {
|
|
throw new IllegalStateException("Not locked");
|
|
}
|
|
lock.release();
|
|
lock = null;
|
|
rwfile.close();
|
|
rwfile = null;
|
|
lockSem.release();
|
|
}
|
|
|
|
public boolean hasValidValues() throws IOException, InterruptedException {
|
|
if (exists()) {
|
|
lock();
|
|
getValues();
|
|
unlock();
|
|
|
|
if (containsPortInfo()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Wait for the port file to contain values that look valid.
|
|
*/
|
|
public void waitForValidValues() throws IOException, InterruptedException {
|
|
final int MS_BETWEEN_ATTEMPTS = 500;
|
|
long startTime = System.currentTimeMillis();
|
|
long timeout = startTime + getServerStartupTimeoutSeconds() * 1000;
|
|
while (true) {
|
|
Log.debug("Looking for valid port file values...");
|
|
if (exists()) {
|
|
lock();
|
|
getValues();
|
|
unlock();
|
|
}
|
|
if (containsPortInfo) {
|
|
Log.debug("Valid port file values found after " + (System.currentTimeMillis() - startTime) + " ms");
|
|
return;
|
|
}
|
|
if (System.currentTimeMillis() > timeout) {
|
|
break;
|
|
}
|
|
Thread.sleep(MS_BETWEEN_ATTEMPTS);
|
|
}
|
|
throw new IOException("No port file values materialized. Giving up after " +
|
|
(System.currentTimeMillis() - startTime) + " ms");
|
|
}
|
|
|
|
/**
|
|
* Check if the portfile still contains my values, assuming that I am the server.
|
|
*/
|
|
public boolean stillMyValues() throws IOException, FileNotFoundException, InterruptedException {
|
|
for (;;) {
|
|
try {
|
|
lock();
|
|
getValues();
|
|
unlock();
|
|
if (containsPortInfo) {
|
|
if (serverPort == myServerPort &&
|
|
serverCookie == myServerCookie) {
|
|
// Everything is ok.
|
|
return true;
|
|
}
|
|
// Someone has overwritten the port file.
|
|
// Probably another javac server, lets quit.
|
|
return false;
|
|
}
|
|
// Something else is wrong with the portfile. Lets quit.
|
|
return false;
|
|
} catch (FileLockInterruptionException e) {
|
|
continue;
|
|
}
|
|
catch (ClosedChannelException e) {
|
|
// The channel has been closed since the server is exiting.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the name of the port file.
|
|
*/
|
|
public String getFilename() {
|
|
return filename;
|
|
}
|
|
|
|
private long getServerStartupTimeoutSeconds() {
|
|
String str = System.getProperty("serverStartupTimeout");
|
|
if (str != null) {
|
|
try {
|
|
return Integer.parseInt(str);
|
|
} catch (NumberFormatException e) {
|
|
}
|
|
}
|
|
return 60;
|
|
}
|
|
}
|