
com.sun.messaging.jmq.jmsserver.util.LockFile Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2000, 2017 Oracle and/or its affiliates. All rights reserved.
* Copyright 2021, 2022 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.messaging.jmq.jmsserver.util;
import static java.lang.System.Logger.Level.ERROR;
import java.io.*;
import java.nio.channels.FileLock;
import java.net.*;
import java.util.*;
import com.sun.messaging.jmq.jmsserver.comm.CommGlobals;
import com.sun.messaging.jmq.jmsserver.resources.*;
import java.lang.System.Logger;
/**
* This class encapsulates a broker lock file. The lock file makes sure that no two brokers using the same instance name
* are running at the same time (using different port numbers). The algorithm goes like this:
*
* Try to create a lock file in {@literal $JMQ_VARHOME/instances/lock} If lock didn't exist previously and was
* created then we got the lock write {@literal :hostname:port} to lock file and return. Else if lock file already
* exists read it. If contents of lock file match the instancename, hostname and port of this broker then the lock file
* was left over from a previous run of this broker and we assume we got the lock and return. Else try to connect to the
* broker on host:port to see if it is still up. If we connect to broker then we failed to get lock. return. Else assume
* the lock file is cruft. Remove it and try to acquire again.
*
*/
public class LockFile {
private static LockFile currentLockFile = null;
private static final Logger logger = System.getLogger(LockFile.class.getName());
private String hostname = null;
private String instance = null;
private String filePath = null;
private int port = 0;
private boolean isMyLock = false;
private LockFile() {
}
private LockFile(String instance, String hostname, int port) {
this.hostname = hostname;
this.instance = instance;
this.port = port;
}
public static synchronized void clearLock() {
currentLockFile = null;
}
/**
* Get the lock file for the specified instance of the broker. If no lock file exists one is created using the
* parameters provided. If one does exist it is loaded. The caller should use the isMyLock() method on the returned
* LockFile to determine if it acquired the lock, or if somebody else has it.
*
*/
public static synchronized LockFile getLock(String varhome, String instance, String hostname, int port, boolean useFileLock) throws IOException {
LockFile lf = null;
File file = new File(getLockFilePath(varhome, instance));
// Grab lock by creating lock file.
if (file.createNewFile()) {
// Got the lock! Lock file didn't exist and was created.
// Write info to it and register for it to be removed on VM exit
lf = new LockFile(instance, hostname, port);
lf.filePath = file.getCanonicalPath();
lf.writeLockFile(file, useFileLock, true);
lf.isMyLock = true;
file.deleteOnExit();
currentLockFile = lf;
return lf;
}
// Lock file already exists. Read in contents
lf = loadLockFile(file, useFileLock);
if (lf == null) {
lf = new LockFile(instance, hostname, port);
lf.filePath = file.getCanonicalPath();
lf.writeLockFile(file, useFileLock, true);
lf.isMyLock = true;
file.deleteOnExit();
currentLockFile = lf;
return lf;
}
lf.filePath = file.getCanonicalPath();
// Check if it is ours (maybe left over if we previously crashed).
if (port == lf.getPort() && equivalentHostNames(hostname, lf.getHost(), false) && instance.equals(lf.getInstance())) {
// It's ours! No need to read-write it.
file.deleteOnExit();
lf.isMyLock = true;
currentLockFile = lf;
return lf;
} else if (port == lf.getPort() && isSameIP(hostname, lf.getHost()) && instance.equals(lf.getInstance())) {
// update hostname if same ip with diff host name
lf.updateHostname(hostname, useFileLock);
file.deleteOnExit();
lf.isMyLock = true;
currentLockFile = lf;
return lf;
}
// Not ours. See if owner is still running
// Try opening socket to other broker's portmapper. If we
// can open a socket then the lock file is in use.
try (Socket unused = new Socket(InetAddress.getByName(lf.hostname), lf.port)) {
lf.isMyLock = false;
} catch (IOException e) {
// Looks like owner is not running. Take lock
if (!file.delete()) {
throw new IOException(CommGlobals.getBrokerResources().getString(BrokerResources.X_LOCKFILE_BADDEL));
}
// Lock file should be gone, resursive call should acquire it
return getLock(varhome, instance, hostname, port, useFileLock);
}
currentLockFile = lf;
return lf;
}
/**
* check if host1 and host 2 have the same IP address.
*
* @return true if we can obtain IPs from host1 and host2 and they are the equal.
*/
public static boolean isSameIP(String host1, String host2) {
boolean sflag = false;
try {
String addr1 = InetAddress.getByName(host1).getHostAddress();
String addr2 = InetAddress.getByName(host2).getHostAddress();
if (addr1.equals(addr2)) {
sflag = true;
}
} catch (Exception e) {
logger.log(ERROR, e.getMessage(), e);
}
return sflag;
}
/**
* Return the path to the lock file
*/
public static String getLockFilePath(String varhome, String instance) {
return varhome + File.separator + CommGlobals.INSTANCES_HOME_DIRECTORY + File.separator + instance + File.separator + "lock";
}
/**
* Returns true if this process acquired the lock. Returns false if another process has the lock
*/
public boolean isMyLock() {
return isMyLock;
}
public String getHost() {
return hostname;
}
public String getInstance() {
return instance;
}
public String getFilePath() {
return filePath;
}
public int getPort() {
return port;
}
@Override
public String toString() {
return instance + " " + hostname + ":" + port + " (" + isMyLock + ")";
}
/**
* Update the port number in the lock file. Needed because the broker's port number may change via admin while it is
* running
*/
public void updatePort(int port, boolean useFileLock) throws IOException {
File file = new File(filePath);
int oldPort = this.port;
this.port = port;
try {
writeLockFile(file, useFileLock);
} catch (IOException e) {
this.port = oldPort;
throw e;
}
}
/**
* Update the hostname in the lock file. Needed because the broker's hostname may change via admin while it is running
*/
public void updateHostname(String hostname, boolean useFileLock) throws IOException {
File file = new File(filePath);
String oldHostname = this.hostname;
this.hostname = hostname;
try {
writeLockFile(file, useFileLock);
} catch (IOException e) {
this.hostname = oldHostname;
throw e;
}
}
/**
* Load the lock file. Does not attempt to acquire it.
*
* @return null if file is empty and useFileLock true
*/
public static synchronized LockFile loadLockFile(File file, boolean useFileLock) throws IOException {
byte[] data = new byte[128];
LockFile lf = new LockFile();
FileInputStream fis = new FileInputStream(file);
FileLock filelock = null;
try {
if (useFileLock) {
if (file.length() == 0) {
// The other broker process may have just created it
return null;
} else {
// get shared-lock
filelock = fis.getChannel().tryLock(0L, Long.MAX_VALUE, true);
if (filelock == null) {
try { // try once more
Thread.sleep(500);
} catch (Exception e) {
/* ignore */}
filelock = fis.getChannel().tryLock(0L, Long.MAX_VALUE, true);
if (filelock == null) {
throw new IOException(CommGlobals.getBrokerResources().getKString(BrokerResources.X_OBTAIN_SHARED_LOCK_FILE, file.toString()));
}
}
}
}
fis.read(data);
String s = new String(data, "UTF8");
int i1 = s.indexOf(':');
if (i1 == -1) {
throw new IOException(CommGlobals.getBrokerResources().getKString(BrokerResources.X_LOCKFILE_CONTENT_FORMAT, file.toString(), "[" + s + "]"));
}
StringTokenizer st = new StringTokenizer(s.substring(0, i1), " \t\n\r\f");
lf.instance = st.nextToken();
int i2 = s.lastIndexOf(':');
if (i2 == -1 || i1 == i2) {
throw new IOException(CommGlobals.getBrokerResources().getKString(BrokerResources.X_LOCKFILE_CONTENT_FORMAT, file.toString(), "[" + s + "]"));
}
st = new StringTokenizer(s.substring(i2 + 1), " \t\n\r\f");
lf.port = Integer.parseInt(st.nextToken());
st = new StringTokenizer(s.substring(i1 + 1, i2), " \t\n\r\f");
lf.hostname = st.nextToken();
} finally {
if (filelock != null) {
filelock.release();
}
fis.close();
}
return lf;
}
/**
* Write the lock file. Assumes the file already exists.
*/
public void writeLockFile(File file, boolean useFileLock) throws IOException {
writeLockFile(file, useFileLock, false);
}
private synchronized void writeLockFile(File file, boolean useFileLock, boolean checkEmpty) throws IOException {
String data = instance + ":" + hostname + ":" + port + "\n";
FileOutputStream os = new FileOutputStream(file);
FileLock filelock = null;
try {
if (useFileLock) {
// get exclusive-lock
filelock = os.getChannel().tryLock();
if (filelock == null) {
try { // try once more
Thread.sleep(500);
} catch (Exception e) {
/* ignore */}
filelock = os.getChannel().tryLock();
if (filelock == null) {
throw new IOException(
CommGlobals.getBrokerResources().getKString(BrokerResources.X_OBTAIN_EXCLUSIVE_LOCK_FILE, file.toString(), this.toString()));
}
}
if (checkEmpty && file.length() != 0) {
throw new IOException(
CommGlobals.getBrokerResources().getKString(BrokerResources.X_OBTAIN_EXCLUSIVE_LOCK_FILE_EMPTY, file, this.toString()));
}
}
os.write(data.getBytes("UTF8"));
os.getChannel().force(false);
} finally {
if (filelock != null) {
filelock.release();
}
os.close();
}
return;
}
/**
* Get the current lock file
*/
public static LockFile getCurrentLockFile() {
return currentLockFile;
}
/**
* Check if two hostname strings are equivalent. Note this is just a simple string comparison.
*
* If "exact" is true then the two strings must match exactly. Otherwise one string can be an unqualified version of the
* other. For example if "exact" is false then the following are considered equivalent: foo.central,
* foo.central.sun.com, foo. But foo.east would not be equivalent.
*
* @param h1 First hostname
* @param h2 Second hostname
* @param exact True to perform an exact match. False to perform a unqualified match.
*
*/
public static boolean equivalentHostNames(String h1, String h2, boolean exact) {
if (exact) {
// Check for exact match
return h1.equals(h2);
}
// Split hostnames by dots and make sure each component matches
StringTokenizer st1 = new StringTokenizer(h1, ".");
StringTokenizer st2 = new StringTokenizer(h2, ".");
while (st1.hasMoreTokens() && st2.hasMoreTokens()) {
if (!st1.nextToken().equals(st2.nextToken())) {
return false;
}
}
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy