-
Notifications
You must be signed in to change notification settings - Fork 33
/
LockFile.java
373 lines (319 loc) · 13.2 KB
/
LockFile.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/*
* 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 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.*;
/**
* 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/<instancename>lock} If lock didn't exist previously and was
* created then we got the lock write {@literal <instancename>: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 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 synchronized static 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 s = 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) {
e.printStackTrace();
}
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;
}
}