Skip to content

Commit d9c51c3

Browse files
committed
Close OpenHFT#46 - use FileLock on lock files rather than presence/absence of file itself
1 parent ac66233 commit d9c51c3

12 files changed

Lines changed: 490 additions & 75 deletions

affinity/src/main/java/net/openhft/affinity/LockCheck.java

Lines changed: 34 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@
1717

1818
package net.openhft.affinity;
1919

20-
import org.jetbrains.annotations.NotNull;
20+
import net.openhft.affinity.lockchecker.FileLockBasedLockChecker;
21+
import net.openhft.affinity.lockchecker.LockChecker;
2122
import org.slf4j.Logger;
2223
import org.slf4j.LoggerFactory;
2324

2425
import java.io.*;
25-
import java.text.SimpleDateFormat;
26-
import java.util.Date;
2726

2827
/**
2928
* @author Rob Austin.
@@ -35,61 +34,49 @@ enum LockCheck {
3534
private static final String OS = System.getProperty("os.name").toLowerCase();
3635
static final boolean IS_LINUX = OS.startsWith("linux");
3736
private static final int EMPTY_PID = Integer.MIN_VALUE;
38-
private static SimpleDateFormat df = new SimpleDateFormat("yyyy.MM" + ".dd 'at' HH:mm:ss z");
37+
38+
39+
private static final LockChecker lockChecker = FileLockBasedLockChecker.getInstance();
3940

4041
static long getPID() {
4142
String processName =
4243
java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
4344
return Long.parseLong(processName.split("@")[0]);
4445
}
4546

46-
public static boolean isCpuFree(int cpu) {
47+
static boolean canOSSupportOperation() {
48+
return IS_LINUX;
49+
}
4750

48-
if (!IS_LINUX)
51+
public static boolean isCpuFree(int cpu) {
52+
if (!canOSSupportOperation())
4953
return true;
5054

51-
final File file = toFile(cpu);
52-
final boolean exists = file.exists();
53-
54-
if (!exists) {
55+
if (isLockFree(cpu)) {
5556
return true;
5657
} else {
5758
int currentProcess = 0;
5859
try {
59-
currentProcess = getProcessForCpu(file);
60+
currentProcess = getProcessForCpu(cpu);
6061
} catch (RuntimeException | IOException e) {
6162
LOGGER.warn("Failed to determine process on cpu " + cpu, e);
6263
e.printStackTrace();
6364
return true;
6465
}
65-
if (currentProcess == EMPTY_PID) {
66-
file.delete();
67-
return true;
68-
}
6966
if (!isProcessRunning(currentProcess)) {
70-
file.delete();
67+
lockChecker.releaseLock(cpu);
7168
return true;
7269
}
7370
return false;
7471
}
7572
}
7673

77-
@NotNull
78-
static File toFile(int core) {
79-
return new File(tmpDir(), "cpu-" + core + ".lock");
80-
}
81-
82-
static void replacePid(int core, long processID) throws IOException {
83-
replacePid(toFile(core), processID);
84-
}
85-
86-
private static void replacePid(File file, long processID) throws IOException {
87-
file.delete();
88-
storePid(processID, file);
74+
static void replacePid(int cpu, long processID) throws IOException {
75+
storePid(processID, cpu);
8976
}
9077

9178
static boolean isProcessRunning(long pid) {
92-
if (IS_LINUX)
79+
if (canOSSupportOperation())
9380
return new File("/proc/" + pid).exists();
9481
else
9582
throw new UnsupportedOperationException("this is only supported on LINUX");
@@ -99,55 +86,41 @@ static boolean isProcessRunning(long pid) {
9986
* stores the pid in a file, named by the core, the pid is written to the file with the date
10087
* below
10188
*/
102-
private synchronized static void storePid(long processID, File coreFile) throws IOException {
103-
try (Writer writer = new BufferedWriter(new OutputStreamWriter(
104-
new FileOutputStream(coreFile, false), "utf-8"))) {
105-
String processIDStr = Long.toString(processID);
106-
writer.write(processIDStr + "\n" + df.format(new Date()));
89+
private synchronized static void storePid(long processID, int cpu) throws IOException {
90+
if(!lockChecker.obtainLock(cpu, Long.toString(processID))) {
91+
throw new IOException(String.format("Cannot obtain file lock for cpu %d", cpu));
10792
}
10893
}
10994

110-
static int getProcessForCpu(int core) throws IOException {
111-
return getProcessForCpu(toFile(core));
95+
private synchronized static boolean isLockFree(int id) {
96+
return lockChecker.isLockFree(id);
11297
}
11398

114-
private static int getProcessForCpu(@NotNull File coreFile) throws IOException {
115-
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
116-
new FileInputStream(coreFile), "utf-8"))) {
99+
static int getProcessForCpu(int core) throws IOException {
100+
String meta = lockChecker.getMetaInfo(core);
117101

118-
final String firstLine = reader.readLine();
119-
if (firstLine == null) {
120-
LOGGER.warn("Empty lock file {}", coreFile.getAbsolutePath());
121-
return EMPTY_PID;
122-
}
123-
String s = firstLine.trim();
102+
if(meta != null && !meta.isEmpty()) {
124103
try {
125-
return Integer.parseInt(s);
126-
} catch (RuntimeException e) {
127-
LOGGER.warn("Corrupt lock file {}: first line = '{}'", coreFile.getAbsolutePath(), firstLine);
128-
e.printStackTrace();
129-
return EMPTY_PID;
104+
return Integer.parseInt(meta);
105+
} catch(NumberFormatException e) {
106+
//nothing
130107
}
131108
}
132-
}
133-
134-
private static File tmpDir() {
135-
final File tempDir = new File(System.getProperty("java.io.tmpdir"));
136-
137-
if (!tempDir.exists())
138-
tempDir.mkdirs();
139-
140-
return tempDir;
109+
return EMPTY_PID;
141110
}
142111

143112
static void updateCpu(int cpu) {
144-
if (!IS_LINUX)
113+
if (!canOSSupportOperation())
145114
return;
146115
try {
147-
replacePid(toFile(cpu), getPID());
116+
replacePid(cpu, getPID());
148117
} catch (IOException e) {
149118
LOGGER.warn("Failed to update lock file for cpu " + cpu, e);
150119
e.printStackTrace();
151120
}
152121
}
122+
123+
public static void releaseLock(int cpu) {
124+
lockChecker.releaseLock(cpu);
125+
}
153126
}

affinity/src/main/java/net/openhft/affinity/LockInventory.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,10 @@ public final synchronized AffinityLock acquireLock(boolean bind, int cpuId, Affi
108108
updateLockForCurrentThread(bind, required, false);
109109
return required;
110110
}
111+
LOGGER.warn("Unable to acquire lock on CPU {} for thread {}, trying to find another CPU",
112+
cpuId, Thread.currentThread());
111113
}
112114

113-
LOGGER.warn("Unable to acquire lock on CPU {} for thread {}, trying to find another CPU",
114-
cpuId, Thread.currentThread());
115-
116115
for (AffinityStrategy strategy : strategies) {
117116
// consider all processors except cpu 0 which is usually used by the OS.
118117
// if you have only one core, this library is not appropriate in any case.
@@ -215,11 +214,6 @@ private void releaseAffinityLock(final Thread t, final AffinityLock al, final St
215214
al.bound = false;
216215
al.boundHere = null;
217216

218-
final String lockFilePath = LockCheck.toFile(al.cpuId()).getAbsolutePath();
219-
try {
220-
Files.delete(Paths.get(lockFilePath));
221-
} catch (IOException e) {
222-
LOGGER.warn("Failed to delete lock file at " + lockFilePath);
223-
}
217+
LockCheck.releaseLock(al.cpuId());
224218
}
225219
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package net.openhft.affinity.lockchecker;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
7+
import java.io.*;
8+
import java.text.SimpleDateFormat;
9+
import java.util.Date;
10+
11+
/**
12+
* @author Tom Shercliff
13+
*/
14+
public class FileBasedLockChecker implements LockChecker {
15+
16+
private static final Logger LOGGER = LoggerFactory.getLogger(FileBasedLockChecker.class);
17+
protected static final SimpleDateFormat df = new SimpleDateFormat("yyyy.MM" + ".dd 'at' HH:mm:ss z");
18+
19+
private static final LockChecker instance = new FileBasedLockChecker();
20+
public static LockChecker getInstance() {
21+
return instance;
22+
}
23+
24+
protected FileBasedLockChecker() {
25+
//nothing
26+
}
27+
28+
@Override
29+
public boolean isLockFree(int id) {
30+
return !toFile(id).exists();
31+
}
32+
33+
@Override
34+
public boolean obtainLock(int id, String metaInfo) throws IOException {
35+
File file = toFile(id);
36+
file.delete();
37+
38+
try (Writer writer = new BufferedWriter(new OutputStreamWriter(
39+
new FileOutputStream(file, false), "utf-8"))) {
40+
writer.write(metaInfo + "\n" + df.format(new Date()));
41+
return true;
42+
}
43+
}
44+
45+
@Override
46+
public boolean releaseLock(int id) {
47+
return toFile(id).delete();
48+
}
49+
50+
@Override
51+
public String getMetaInfo(int id) throws IOException {
52+
File file = toFile(id);
53+
54+
if(!file.exists()) {
55+
return null;
56+
}
57+
58+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "utf-8"))) {
59+
final String firstLine = reader.readLine();
60+
if (firstLine == null) {
61+
LOGGER.error(String.format("Empty lock file %s%n", file.getAbsolutePath()));
62+
return null;
63+
}
64+
return firstLine.trim();
65+
}
66+
}
67+
68+
@NotNull
69+
protected File toFile(int id) {
70+
return new File(tmpDir(), "cpu-" + id + ".lock");
71+
}
72+
73+
static File tmpDir() {
74+
final File tempDir = new File(System.getProperty("java.io.tmpdir"));
75+
76+
if (!tempDir.exists())
77+
tempDir.mkdirs();
78+
79+
return tempDir;
80+
}
81+
}

0 commit comments

Comments
 (0)