diff --git a/affinity/pom.xml b/affinity/pom.xml index f634d4435..53a072427 100644 --- a/affinity/pom.xml +++ b/affinity/pom.xml @@ -110,6 +110,10 @@ make-c + + linux + !arm + !dontMake diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java index 7c5701997..625578b9a 100644 --- a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java +++ b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java @@ -69,6 +69,7 @@ public class AffinityLock implements Closeable { * Logical ID of the CPU to which this lock belongs to. */ private final int cpuId; + private final int cpuId2; /** * CPU to which this lock belongs to is of general use. */ @@ -88,9 +89,10 @@ public class AffinityLock implements Closeable { Throwable boundHere; private boolean resetAffinity = true; - AffinityLock(int cpuId, boolean base, boolean reservable, LockInventory lockInventory) { + AffinityLock(int cpuId, int cpuId2, boolean base, boolean reservable, LockInventory lockInventory) { this.lockInventory = lockInventory; this.cpuId = cpuId; + this.cpuId2 = cpuId2; this.base = base; this.reservable = reservable; } @@ -133,7 +135,7 @@ private static BitSet getReservedAffinity0() { int end = reservedAffinity.length(); for (int i = 0; i < longs.length; i++) { int begin = Math.max(0, end - 16); - longs[i] = Long.parseLong(reservedAffinity.substring(begin, end), 16); + longs[i] = Long.parseUnsignedLong(reservedAffinity.substring(begin, end), 16); end = begin; } return BitSet.valueOf(longs); @@ -183,7 +185,7 @@ public static AffinityLock acquireLock(boolean bind) { * for defining your thread layout centrally and passing the handle via dependency injection. * * @param cpuId the CPU id to bind to - * @return A handle for an affinity lock. + * @return A handle for an affinity lock, or no lock if no available CPU in the array */ public static AffinityLock acquireLock(int cpuId) { if (isInvalidCpuId(cpuId)) @@ -474,6 +476,10 @@ public int cpuId() { return cpuId; } + public int cpuId2() { + return cpuId2; + } + /** * @return Was a cpu found to bind this lock to. */ diff --git a/affinity/src/main/java/net/openhft/affinity/BootClassPath.java b/affinity/src/main/java/net/openhft/affinity/BootClassPath.java index 5ae3ca125..b13d16684 100644 --- a/affinity/src/main/java/net/openhft/affinity/BootClassPath.java +++ b/affinity/src/main/java/net/openhft/affinity/BootClassPath.java @@ -21,7 +21,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.io.IOException; +import java.net.URI; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; @@ -39,21 +41,56 @@ enum BootClassPath { private static Set getResourcesOnBootClasspath() { final Logger logger = LoggerFactory.getLogger(BootClassPath.class); final Set resources = new HashSet<>(); + final String bootClassPath = System.getProperty("sun.boot.class.path", ""); - logger.trace("Boot class-path is: {}", bootClassPath); + if (!bootClassPath.isEmpty()) { + logger.trace("Boot class-path is: {}", bootClassPath); - final String pathSeparator = System.getProperty("path.separator"); - logger.trace("Path separator is: '{}'", pathSeparator); + final String pathSeparator = File.pathSeparator; + logger.trace("Path separator is: '{}'", pathSeparator); - final String[] pathElements = bootClassPath.split(pathSeparator); + final String[] pathElements = bootClassPath.split(pathSeparator); - for (final String pathElement : pathElements) { - resources.addAll(findResources(Paths.get(pathElement), logger)); + for (final String pathElement : pathElements) { + resources.addAll(findResources(Paths.get(pathElement), logger)); + } + } else { + resources.addAll(findResourcesInJrt(logger)); } return resources; } + private static Set findResourcesInJrt(final Logger logger) { + final Set jrtResources = new HashSet<>(); + try { + FileSystem fs; + try { + fs = FileSystems.getFileSystem(URI.create("jrt:/")); + } catch (FileSystemNotFoundException | ProviderNotFoundException e) { + fs = FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap()); + } + final Path modules = fs.getPath("/modules"); + Files.walkFileTree(modules, new SimpleFileVisitor() { + @Override + public @NotNull FileVisitResult visitFile(final @NotNull Path file, + final @NotNull BasicFileAttributes attrs) throws IOException { + if (file.getFileName().toString().endsWith(".class")) { + Path relative = modules.relativize(file); + if (relative.getNameCount() > 1) { + Path classPath = relative.subpath(1, relative.getNameCount()); + jrtResources.add(classPath.toString()); + } + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + logger.warn("Error walking jrt filesystem", e); + } + return jrtResources; + } + private static Set findResources(final Path path, final Logger logger) { if (!Files.exists(path)) { return Collections.emptySet(); diff --git a/affinity/src/main/java/net/openhft/affinity/CpuLayout.java b/affinity/src/main/java/net/openhft/affinity/CpuLayout.java index 7221d7dfb..12cbce61e 100644 --- a/affinity/src/main/java/net/openhft/affinity/CpuLayout.java +++ b/affinity/src/main/java/net/openhft/affinity/CpuLayout.java @@ -49,4 +49,10 @@ public interface CpuLayout { * @return which thread on a core this cpu is on. */ int threadId(int cpuId); + + /** + * @param cpuId the logical processor number + * @return the hyperthreaded pair number or 0 if not hyperthreaded. + */ + int pair(int cpuId); } diff --git a/affinity/src/main/java/net/openhft/affinity/LockCheck.java b/affinity/src/main/java/net/openhft/affinity/LockCheck.java index 259aaf40b..2953637c3 100644 --- a/affinity/src/main/java/net/openhft/affinity/LockCheck.java +++ b/affinity/src/main/java/net/openhft/affinity/LockCheck.java @@ -17,6 +17,7 @@ package net.openhft.affinity; +import net.openhft.affinity.impl.Utilities; import net.openhft.affinity.lockchecker.FileLockBasedLockChecker; import net.openhft.affinity.lockchecker.LockChecker; import org.slf4j.Logger; @@ -39,9 +40,7 @@ public enum LockCheck { private static final LockChecker lockChecker = FileLockBasedLockChecker.getInstance(); public static long getPID() { - String processName = - java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); - return Long.parseLong(processName.split("@")[0]); + return Utilities.currentProcessId(); } static boolean canOSSupportOperation() { @@ -55,8 +54,8 @@ public static boolean isCpuFree(int cpu) { return isLockFree(cpu); } - static boolean replacePid(int cpu, long processID) throws IOException { - return storePid(processID, cpu); + static boolean replacePid(int cpu, int cpu2, long processID) throws IOException { + return storePid(processID, cpu, cpu2); } public static boolean isProcessRunning(long pid) { @@ -70,8 +69,8 @@ public static boolean isProcessRunning(long pid) { * stores the pid in a file, named by the core, the pid is written to the file with the date * below */ - private synchronized static boolean storePid(long processID, int cpu) throws IOException { - return lockChecker.obtainLock(cpu, Long.toString(processID)); + private synchronized static boolean storePid(long processID, int cpu, int cpu2) throws IOException { + return lockChecker.obtainLock(cpu, cpu2, Long.toString(processID)); } private synchronized static boolean isLockFree(int id) { @@ -79,6 +78,9 @@ private synchronized static boolean isLockFree(int id) { } public static int getProcessForCpu(int core) throws IOException { + if (!canOSSupportOperation()) + return EMPTY_PID; + String meta = lockChecker.getMetaInfo(core); if (meta != null && !meta.isEmpty()) { @@ -91,10 +93,10 @@ public static int getProcessForCpu(int core) throws IOException { return EMPTY_PID; } - static boolean updateCpu(int cpu) throws IOException { + static boolean updateCpu(int cpu, int cpu2) throws IOException { if (!canOSSupportOperation()) return true; - return replacePid(cpu, getPID()); + return replacePid(cpu, cpu2, getPID()); } public static void releaseLock(int cpu) { diff --git a/affinity/src/main/java/net/openhft/affinity/LockInventory.java b/affinity/src/main/java/net/openhft/affinity/LockInventory.java index b3743d3b6..0fa41b114 100644 --- a/affinity/src/main/java/net/openhft/affinity/LockInventory.java +++ b/affinity/src/main/java/net/openhft/affinity/LockInventory.java @@ -82,7 +82,7 @@ private static boolean isAnyCpu(final int cpuId) { */ private static boolean updateLockForCurrentThread(final boolean bind, final AffinityLock al, final boolean wholeCore) throws ClosedByInterruptException { try { - if (LockCheck.updateCpu(al.cpuId())) { + if (LockCheck.updateCpu(al.cpuId(), wholeCore ? al.cpuId2() : 0)) { al.assignCurrentThread(bind, wholeCore); return true; } @@ -108,7 +108,9 @@ public final synchronized void set(CpuLayout cpuLayout) { final boolean base = AffinityLock.BASE_AFFINITY.get(i); final boolean reservable = AffinityLock.RESERVED_AFFINITY.get(i); LOGGER.trace("cpu {} base={} reservable= {}", i, base, reservable); - AffinityLock lock = logicalCoreLocks[i] = newLock(i, base, reservable); + assert logicalCoreLocks != null; + @SuppressWarnings("resource") + AffinityLock lock = logicalCoreLocks[i] = newLock(i, cpuLayout.pair(i), base, reservable); int layoutId = lock.cpuId(); int physicalCore = toPhysicalCore(layoutId); @@ -277,8 +279,8 @@ public final synchronized String dumpLocks() { return dumpLocks(logicalCoreLocks); } - protected AffinityLock newLock(int cpuId, boolean base, boolean reservable) { - return new AffinityLock(cpuId, base, reservable, this); + protected AffinityLock newLock(int cpuId, int cpuId2, boolean base, boolean reservable) { + return new AffinityLock(cpuId, cpuId2, base, reservable, this); } private void reset(CpuLayout cpuLayout) { @@ -301,6 +303,6 @@ private void releaseAffinityLock(final Thread t, final AffinityLock al, final St } public AffinityLock noLock() { - return newLock(AffinityLock.ANY_CPU, false, false); + return newLock(AffinityLock.ANY_CPU, 0, false, false); } } diff --git a/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java b/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java index b22c36cf7..2996bdf0d 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java @@ -64,4 +64,9 @@ public int coreId(int cpuId) { public int threadId(int cpuId) { return 0; } + + @Override + public int pair(int cpuId) { + return 0; + } } diff --git a/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java index 9ee9f1844..c9ebc183c 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java @@ -21,7 +21,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.management.ManagementFactory; import java.util.BitSet; /** @@ -48,8 +47,7 @@ public int getCpu() { @Override public int getProcessId() { - final String name = ManagementFactory.getRuntimeMXBean().getName(); - return Integer.parseInt(name.split("@")[0]); + return Utilities.currentProcessId(); } @Override diff --git a/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java index 9a944ecc6..39db80875 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java @@ -24,7 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.management.ManagementFactory; import java.util.BitSet; /** @@ -55,8 +54,7 @@ public int getCpu() { @Override public int getProcessId() { - final String name = ManagementFactory.getRuntimeMXBean().getName(); - return Integer.parseInt(name.split("@")[0]); + return Utilities.currentProcessId(); } @Override diff --git a/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java index 4b58eca56..ebe49b154 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java @@ -24,7 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.management.ManagementFactory; import java.util.BitSet; /** @@ -55,8 +54,7 @@ public int getCpu() { @Override public int getProcessId() { - final String name = ManagementFactory.getRuntimeMXBean().getName(); - return Integer.parseInt(name.split("@")[0]); + return Utilities.currentProcessId(); } @Override diff --git a/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java b/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java index 676f1567f..4d3bfd13b 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java @@ -79,4 +79,30 @@ private static boolean is64Bit0() { systemProp = System.getProperty("java.vm.version"); return systemProp != null && systemProp.contains("_64"); } + + /** + * Returns the current process id. Uses {@code ProcessHandle} when running + * on Java 9 or later and falls back to parsing + * {@code RuntimeMXBean#getName()} on earlier versions. + * + * @return the process id or {@code -1} if it cannot be determined + */ + public static int currentProcessId() { + try { + // Java 9+ provides ProcessHandle which has a pid() method. + Class phClass = Class.forName("java.lang.ProcessHandle"); + Object current = phClass.getMethod("current").invoke(null); + long pid = (Long) phClass.getMethod("pid").invoke(current); + return (int) pid; + } catch (Throwable ignored) { + // ignore and fallback to the pre-Java 9 approach + } + + try { + String name = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); + return Integer.parseInt(name.split("@")[0]); + } catch (Throwable e) { + return -1; + } + } } diff --git a/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java b/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java index 7c17d1788..74ff2d580 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java @@ -177,6 +177,19 @@ public int threadId(int cpuId) { return cpuDetails.get(cpuId).threadId; } + @Override + public int pair(int cpuId) { + for (int i = 0; i < cpuDetails.size(); i++) { + CpuInfo info = cpuDetails.get(i); + if (info.socketId == cpuDetails.get(cpuId).socketId && + info.coreId == cpuDetails.get(cpuId).coreId && + info.threadId != cpuDetails.get(cpuId).threadId) { + return i; + } + } + return 0; + } + @NotNull @Override public String toString() { diff --git a/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java index 27b3af156..c64bd15be 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java @@ -89,8 +89,9 @@ public void setAffinity(final BitSet affinity) { throw new IllegalStateException("SetThreadAffinityMask((" + pid + ") , &(" + affinity + ") ) errorNo=" + e.getErrorCode(), e); } BitSet affinity2 = getAffinity0(); - if (!affinity2.equals(affinity)) { - LoggerFactory.getLogger(WindowsJNAAffinity.class).warn("Tried to set affinity to " + affinity + " but was " + affinity2 + " you may have insufficient access rights"); + assert affinity2 != null; + if (!affinity2.intersects(affinity)) { + LoggerFactory.getLogger(WindowsJNAAffinity.class).warn("Tried to set affinity to {} but was {} you may have insufficient access rights", affinity, affinity2); } currentAffinity.set((BitSet) affinity.clone()); } diff --git a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java index a720efb67..8b06f7e39 100644 --- a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java +++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java @@ -98,21 +98,32 @@ public synchronized boolean isLockFree(int id) { } @Override - public synchronized boolean obtainLock(int id, String metaInfo) throws IOException { + public synchronized boolean obtainLock(int id, int id2, String metaInfo) throws IOException { int attempt = 0; while (attempt < MAX_LOCK_RETRIES) { try { LockReference lockReference = tryAcquireLockOnFile(id, metaInfo); if (lockReference != null) { - locks[id] = lockReference; - return true; + if (id2 <= 0) { + // no second lock to acquire, return success + locks[id] = lockReference; + return true; + } + LockReference lockReference2 = tryAcquireLockOnFile(id2, metaInfo); + if (lockReference2 != null) { + locks[id] = lockReference; + locks[id2] = lockReference2; + return true; + } else { + releaseLock(id); + } } return false; } catch (ConcurrentLockFileDeletionException e) { attempt++; } } - LOGGER.warn("Exceeded maximum retries for locking CPU {}, failing acquire", id); + LOGGER.warn("Exceeded maximum retries for locking CPU {}, {}, failing acquire", id, id2); return false; } @@ -163,6 +174,7 @@ private void writeMetaInfoToFile(FileChannel fc, String metaInfo) throws IOExcep byte[] content = String.format("%s%n%s", metaInfo, dfTL.get().format(new Date())).getBytes(); ByteBuffer buffer = ByteBuffer.wrap(content); while (buffer.hasRemaining()) { + //noinspection ResultOfMethodCallIgnored fc.write(buffer); } } diff --git a/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java index 8df4eafa1..79a783e95 100644 --- a/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java +++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java @@ -26,7 +26,19 @@ public interface LockChecker { boolean isLockFree(int id); - boolean obtainLock(int id, String metaInfo) throws IOException; + /** + * Obtain a lock for the given id. + */ + @Deprecated(/* to be removed in x.29 */) + default boolean obtainLock(int id, String metaInfo) throws IOException { + return obtainLock(id, 0, metaInfo); + } + + /** + * Obtain a lock for the given id and id2. The id2 is used to distinguish between + * multiple locks for the same core + */ + boolean obtainLock(int id, int id2, String metaInfo) throws IOException; boolean releaseLock(int id); diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java index bc059c6a8..b1f422871 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java @@ -19,7 +19,7 @@ import net.openhft.affinity.impl.Utilities; import net.openhft.affinity.impl.VanillaCpuLayout; -import net.openhft.affinity.testimpl.TestFileLockBasedLockChecker; +import net.openhft.chronicle.testframework.Waiters; import org.hamcrest.MatcherAssert; import org.junit.Test; import org.slf4j.Logger; @@ -28,6 +28,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.BitSet; @@ -44,20 +45,19 @@ public class AffinityLockTest extends BaseAffinityTest { private static final Logger logger = LoggerFactory.getLogger(AffinityLockTest.class); - private final TestFileLockBasedLockChecker lockChecker = new TestFileLockBasedLockChecker(); @Test public void dumpLocksI7() throws IOException { LockInventory lockInventory = new LockInventory(VanillaCpuLayout.fromCpuInfo("i7.cpuinfo")); AffinityLock[] locks = { - new AffinityLock(0, true, false, lockInventory), - new AffinityLock(1, false, false, lockInventory), - new AffinityLock(2, false, true, lockInventory), - new AffinityLock(3, false, true, lockInventory), - new AffinityLock(4, true, false, lockInventory), - new AffinityLock(5, false, false, lockInventory), - new AffinityLock(6, false, true, lockInventory), - new AffinityLock(7, false, true, lockInventory), + new AffinityLock(0, 0, true, false, lockInventory), + new AffinityLock(1, 5, false, false, lockInventory), + new AffinityLock(2, 6, false, true, lockInventory), + new AffinityLock(3, 7, false, true, lockInventory), + new AffinityLock(4, 0, true, false, lockInventory), + new AffinityLock(5, 1, false, false, lockInventory), + new AffinityLock(6, 2, false, true, lockInventory), + new AffinityLock(7, 3, false, true, lockInventory), }; locks[2].assignedThread = new Thread(new InterrupedThread(), "logger"); locks[2].assignedThread.start(); @@ -87,10 +87,10 @@ public void dumpLocksI7() throws IOException { public void dumpLocksI3() throws IOException { LockInventory lockInventory = new LockInventory(VanillaCpuLayout.fromCpuInfo("i3.cpuinfo")); AffinityLock[] locks = { - new AffinityLock(0, true, false, lockInventory), - new AffinityLock(1, false, true, lockInventory), - new AffinityLock(2, true, false, lockInventory), - new AffinityLock(3, false, true, lockInventory), + new AffinityLock(0, 0, true, false, lockInventory), + new AffinityLock(1, 3, false, true, lockInventory), + new AffinityLock(2, 0, true, false, lockInventory), + new AffinityLock(3, 1, false, true, lockInventory), }; locks[1].assignedThread = new Thread(new InterrupedThread(), "engine"); locks[1].assignedThread.start(); @@ -110,8 +110,8 @@ public void dumpLocksI3() throws IOException { public void dumpLocksCoreDuo() throws IOException { LockInventory lockInventory = new LockInventory(VanillaCpuLayout.fromCpuInfo("core.duo.cpuinfo")); AffinityLock[] locks = { - new AffinityLock(0, true, false, lockInventory), - new AffinityLock(1, false, true, lockInventory), + new AffinityLock(0, 0, true, false, lockInventory), + new AffinityLock(1, 0, false, true, lockInventory), }; locks[1].assignedThread = new Thread(new InterrupedThread(), "engine"); locks[1].assignedThread.start(); @@ -254,11 +254,34 @@ public void lockFilesShouldBeRemovedOnRelease() { } final AffinityLock lock = AffinityLock.acquireLock(); - assertTrue(Files.exists(Paths.get(lockChecker.doToFile(lock.cpuId()).getAbsolutePath()))); + Path lockFile = Paths.get(System.getProperty("java.io.tmpdir"), "cpu-" + lock.cpuId() + ".lock"); + assertTrue(Files.exists(lockFile)); lock.release(); - assertFalse(Files.exists(Paths.get(lockChecker.doToFile(lock.cpuId()).getAbsolutePath()))); + assertFalse(Files.exists(lockFile)); + } + + @Test + public void wholeCoreLockReservesAllLogicalCpus() throws IOException { + if (!Utilities.ISLINUX || !new File("/proc/cpuinfo").exists()) { + return; + } + AffinityLock.cpuLayout(VanillaCpuLayout.fromCpuInfo()); + + CpuLayout layout = AffinityLock.cpuLayout(); + try (AffinityLock lock = AffinityLock.acquireCore()) { + int socketId = layout.socketId(lock.cpuId()); + int coreId = layout.coreId(lock.cpuId()); + for (int i = 0; i < layout.cpus(); i++) { + if (layout.socketId(i) == socketId && layout.coreId(i) == coreId) { + assertFalse("CPU " + i + " should be reserved", LockCheck.isCpuFree(i)); + } + } + } + for (int i = 0; i < layout.cpus(); i++) { + assertTrue("CPU " + i + " should not be reserved", LockCheck.isCpuFree(i)); + } } private void displayStatus() { @@ -312,19 +335,17 @@ public void acquireLockWithoutBindingDoesNotChangeAffinity() { @Test public void testTooHighCpuId() { - AffinityLock lock = AffinityLock.acquireLock(123456); - assertFalse(lock.isBound()); + assertFalse(AffinityLock.acquireLock(123456).isBound()); } @Test public void testNegativeCpuId() { - AffinityLock lock = AffinityLock.acquireLock(-1); - assertFalse(lock.isBound()); + assertFalse(AffinityLock.acquireLock(-1).isBound()); } @Test public void testTooHighCpuId2() { - AffinityLock lock = AffinityLock.acquireLock(new int[]{-1, 123456}); + AffinityLock lock = AffinityLock.acquireLock(new int[]{123456}); assertFalse(lock.isBound()); } @@ -343,10 +364,7 @@ public void bindingTwoThreadsToSameCpuThrows() throws InterruptedException { }); t.start(); - while (!lock.isBound()) { - //noinspection BusyWait - Thread.sleep(10); - } + Waiters.waitForCondition("Waiting for lock to be bound", lock::isBound, 1000); try { lock.bind(); diff --git a/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java b/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java index bf0cb8268..9e1db8097 100644 --- a/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java +++ b/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java @@ -23,8 +23,6 @@ public class BootClassPathTest { @Test public void shouldDetectClassesOnClassPath() { - if (!System.getProperty("java.version").startsWith("1.8")) - return; assertTrue(BootClassPath.INSTANCE.has("java.lang.Thread")); assertTrue(BootClassPath.INSTANCE.has("java.lang.Runtime")); } diff --git a/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java b/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java index 82b64435a..6be18c5d7 100644 --- a/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java +++ b/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java @@ -45,7 +45,7 @@ public void before() { @Test public void test() throws IOException { Assert.assertTrue(LockCheck.isCpuFree(cpu)); - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); Assert.assertEquals(LockCheck.getPID(), LockCheck.getProcessForCpu(cpu)); } @@ -58,17 +58,50 @@ public void testPidOnLinux() { public void testReplace() throws IOException { cpu++; Assert.assertTrue(LockCheck.isCpuFree(cpu + 1)); - LockCheck.replacePid(cpu, 123L); + LockCheck.replacePid(cpu, 0, 123L); Assert.assertEquals(123L, LockCheck.getProcessForCpu(cpu)); } @Test public void shouldNotBlowUpIfPidFileIsEmpty() throws Exception { - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); final File file = lockChecker.doToFile(cpu); new RandomAccessFile(file, "rw").setLength(0); LockCheck.isCpuFree(cpu); } + + @Test + public void lockFileDeletedWhileHeld() throws Exception { + cpu++; + + Assert.assertTrue(LockCheck.isCpuFree(cpu)); + LockCheck.updateCpu(cpu, 0); + + File lockFile = lockChecker.doToFile(cpu); + Assert.assertTrue(lockFile.exists()); + + Assert.assertTrue("Could not delete lock file", lockFile.delete()); + Assert.assertFalse(lockFile.exists()); + + Assert.assertFalse("CPU should remain locked despite missing file", LockCheck.isCpuFree(cpu)); + Assert.assertEquals(LockCheck.getPID(), LockCheck.getProcessForCpu(cpu)); + + LockCheck.releaseLock(cpu); + + Assert.assertTrue("Lock should be free after release", LockCheck.isCpuFree(cpu)); + LockCheck.updateCpu(cpu, 0); + + lockFile = lockChecker.doToFile(cpu); + Assert.assertTrue("Lock file should be recreated", lockFile.exists()); + } + + @Test + public void getProcessForCpuReturnsEmptyPidWhenNoFile() throws IOException { + int freeCpu = 99; + File lockFile = lockChecker.doToFile(freeCpu); + Assert.assertFalse(lockFile.exists()); + Assert.assertEquals(Integer.MIN_VALUE, LockCheck.getProcessForCpu(freeCpu)); + } } diff --git a/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java b/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java index c7f04465b..2612886ce 100644 --- a/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java +++ b/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java @@ -46,7 +46,7 @@ public void before() { @Test public void test() throws IOException { Assert.assertTrue(LockCheck.isCpuFree(cpu)); - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); Assert.assertEquals(LockCheck.getPID(), LockCheck.getProcessForCpu(cpu)); } @@ -55,17 +55,22 @@ public void testPidOnLinux() { Assert.assertTrue(LockCheck.isProcessRunning(LockCheck.getPID())); } + @Test + public void testNegativePidOnLinux() { + Assert.assertFalse(LockCheck.isProcessRunning(-1)); + } + @Test public void testReplace() throws IOException { cpu++; Assert.assertTrue(LockCheck.isCpuFree(cpu + 1)); - LockCheck.replacePid(cpu, 123L); + LockCheck.replacePid(cpu, 0, 123L); Assert.assertEquals(123L, LockCheck.getProcessForCpu(cpu)); } @Test public void shouldNotBlowUpIfPidFileIsEmpty() throws Exception { - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); final File file = lockChecker.doToFile(cpu); new RandomAccessFile(file, "rw").setLength(0); @@ -75,7 +80,7 @@ public void shouldNotBlowUpIfPidFileIsEmpty() throws Exception { @Test public void shouldNotBlowUpIfPidFileIsCorrupt() throws Exception { - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); final File file = lockChecker.doToFile(cpu); try (final FileWriter writer = new FileWriter(file, false)) { diff --git a/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPairTest.java b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPairTest.java new file mode 100644 index 000000000..99986acd3 --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPairTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016-2025 chronicle.software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.openhft.affinity.impl; + +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link VanillaCpuLayout#pair(int)} using sample cpuinfo files. + */ +public class VanillaCpuLayoutPairTest { + + @Test + public void testPairForI7() throws IOException { + try (InputStream is = getClass().getClassLoader().getResourceAsStream("i7.cpuinfo")) { + VanillaCpuLayout layout = VanillaCpuLayout.fromCpuInfo(is); + assertEquals(4, layout.pair(0)); + assertEquals(5, layout.pair(1)); + assertEquals(6, layout.pair(2)); + assertEquals(7, layout.pair(3)); + assertEquals(0, layout.pair(4)); + assertEquals(1, layout.pair(5)); + assertEquals(2, layout.pair(6)); + assertEquals(3, layout.pair(7)); + } + } + + @Test + public void testPairForI3() throws IOException { + try (InputStream is = getClass().getClassLoader().getResourceAsStream("i3.cpuinfo")) { + VanillaCpuLayout layout = VanillaCpuLayout.fromCpuInfo(is); + assertEquals(2, layout.pair(0)); + assertEquals(3, layout.pair(1)); + assertEquals(0, layout.pair(2)); + assertEquals(1, layout.pair(3)); + } + } +}