From e70ae8d05a78b2733d34953db05490ed5ee0b1eb Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 23 May 2025 12:17:03 +0100 Subject: [PATCH 01/10] AffinityLock.acquireCore should lock/unlock all cpus ie 2 when hyperthreaded --- .../net/openhft/affinity/AffinityLock.java | 19 ++++-- .../java/net/openhft/affinity/CpuLayout.java | 6 ++ .../java/net/openhft/affinity/LockCheck.java | 12 ++-- .../net/openhft/affinity/LockInventory.java | 12 ++-- .../openhft/affinity/impl/NoCpuLayout.java | 5 ++ .../affinity/impl/VanillaCpuLayout.java | 13 ++++ .../lockchecker/FileLockBasedLockChecker.java | 18 +++++- .../affinity/lockchecker/LockChecker.java | 2 +- .../openhft/affinity/AffinityLockTest.java | 60 +++++++++++++------ .../affinity/FileLockLockCheckTest.java | 6 +- .../net/openhft/affinity/LockCheckTest.java | 8 +-- .../impl/VanillaCpuLayoutPairTest.java | 56 +++++++++++++++++ 12 files changed, 170 insertions(+), 47 deletions(-) create mode 100644 affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPairTest.java diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java index 2fcdb4d95..79d576ac9 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,11 +185,11 @@ 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 (cpuId < 0 || cpuId >= PROCESSORS) { - throw new IllegalArgumentException("cpuId must be between 0 and " + (PROCESSORS - 1) + ": " + cpuId); + return LOCK_INVENTORY.noLock(); } return acquireLock(true, cpuId, AffinityStrategies.ANY); } @@ -204,7 +206,8 @@ public static AffinityLock acquireLock(int cpuId) { public static AffinityLock acquireLock(int[] cpus) { for (int cpu : cpus) { if (cpu < 0 || cpu >= PROCESSORS) { - throw new IllegalArgumentException("cpuId must be between 0 and " + (PROCESSORS - 1) + ": " + cpu); + LOGGER.warn("cpuId {} is out of range", cpu); + continue; } AffinityLock lock = tryAcquireLock(true, cpu); if (lock != null) { @@ -265,7 +268,7 @@ public static AffinityLock acquireLock(String desc) { } else if (desc.startsWith("csv:")) { String content = desc.substring(4); - int[] cpus = Arrays.asList(content.split(",")).stream() + int[] cpus = Arrays.stream(content.split(",")) .map(String::trim) .mapToInt(Integer::parseInt).toArray(); @@ -469,6 +472,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/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..ac9ca6cc1 100644 --- a/affinity/src/main/java/net/openhft/affinity/LockCheck.java +++ b/affinity/src/main/java/net/openhft/affinity/LockCheck.java @@ -55,8 +55,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 +70,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) { @@ -91,10 +91,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/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/lockchecker/FileLockBasedLockChecker.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java index a720efb67..265d8923f 100644 --- a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java +++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java @@ -98,14 +98,25 @@ 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) { @@ -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..62a6c2f52 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,7 @@ public interface LockChecker { boolean isLockFree(int id); - boolean obtainLock(int id, String metaInfo) throws IOException; + 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 b8a14dd77..943ebdad4 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java @@ -19,7 +19,6 @@ import net.openhft.affinity.impl.Utilities; import net.openhft.affinity.impl.VanillaCpuLayout; -import net.openhft.affinity.testimpl.TestFileLockBasedLockChecker; import org.hamcrest.MatcherAssert; import org.junit.Test; import org.slf4j.Logger; @@ -28,6 +27,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.List; @@ -43,20 +43,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(); @@ -86,10 +85,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(); @@ -109,8 +108,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(); @@ -253,11 +252,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() { @@ -308,7 +330,7 @@ public void testTooHighCpuId() { @Test public void testTooHighCpuId2() { - try (AffinityLock ignored = AffinityLock.acquireLock(new int[] {123456})) { + try (AffinityLock ignored = AffinityLock.acquireLock(new int[]{123456})) { assertNotNull(ignored); } } diff --git a/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java b/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java index 82b64435a..1d890d5fe 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,13 +58,13 @@ 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); diff --git a/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java b/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java index c7f04465b..40f5d18e5 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)); } @@ -59,13 +59,13 @@ 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); @@ -75,7 +75,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)); + } + } +} From 69157afc7beea956dd178da9d286b1c12fb4d6e6 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 23 May 2025 12:17:03 +0100 Subject: [PATCH 02/10] AffinityLock.acquireCore should lock/unlock all cpus ie 2 when hyperthreaded --- .../net/openhft/affinity/AffinityLock.java | 19 ++++-- .../java/net/openhft/affinity/CpuLayout.java | 6 ++ .../java/net/openhft/affinity/LockCheck.java | 12 ++-- .../net/openhft/affinity/LockInventory.java | 12 ++-- .../openhft/affinity/impl/NoCpuLayout.java | 5 ++ .../affinity/impl/VanillaCpuLayout.java | 13 +++++ .../lockchecker/FileLockBasedLockChecker.java | 18 +++++- .../affinity/lockchecker/LockChecker.java | 2 +- .../openhft/affinity/AffinityLockTest.java | 58 +++++++++++++------ .../affinity/FileLockLockCheckTest.java | 6 +- .../net/openhft/affinity/LockCheckTest.java | 8 +-- .../impl/VanillaCpuLayoutPairTest.java | 56 ++++++++++++++++++ 12 files changed, 169 insertions(+), 46 deletions(-) create mode 100644 affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPairTest.java diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java index 2fcdb4d95..79d576ac9 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,11 +185,11 @@ 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 (cpuId < 0 || cpuId >= PROCESSORS) { - throw new IllegalArgumentException("cpuId must be between 0 and " + (PROCESSORS - 1) + ": " + cpuId); + return LOCK_INVENTORY.noLock(); } return acquireLock(true, cpuId, AffinityStrategies.ANY); } @@ -204,7 +206,8 @@ public static AffinityLock acquireLock(int cpuId) { public static AffinityLock acquireLock(int[] cpus) { for (int cpu : cpus) { if (cpu < 0 || cpu >= PROCESSORS) { - throw new IllegalArgumentException("cpuId must be between 0 and " + (PROCESSORS - 1) + ": " + cpu); + LOGGER.warn("cpuId {} is out of range", cpu); + continue; } AffinityLock lock = tryAcquireLock(true, cpu); if (lock != null) { @@ -265,7 +268,7 @@ public static AffinityLock acquireLock(String desc) { } else if (desc.startsWith("csv:")) { String content = desc.substring(4); - int[] cpus = Arrays.asList(content.split(",")).stream() + int[] cpus = Arrays.stream(content.split(",")) .map(String::trim) .mapToInt(Integer::parseInt).toArray(); @@ -469,6 +472,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/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..ac9ca6cc1 100644 --- a/affinity/src/main/java/net/openhft/affinity/LockCheck.java +++ b/affinity/src/main/java/net/openhft/affinity/LockCheck.java @@ -55,8 +55,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 +70,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) { @@ -91,10 +91,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/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/lockchecker/FileLockBasedLockChecker.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java index a720efb67..265d8923f 100644 --- a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java +++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java @@ -98,14 +98,25 @@ 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) { @@ -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..62a6c2f52 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,7 @@ public interface LockChecker { boolean isLockFree(int id); - boolean obtainLock(int id, String metaInfo) throws IOException; + 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 75397348d..18e4e666d 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java @@ -19,7 +19,6 @@ import net.openhft.affinity.impl.Utilities; import net.openhft.affinity.impl.VanillaCpuLayout; -import net.openhft.affinity.testimpl.TestFileLockBasedLockChecker; import org.hamcrest.MatcherAssert; import org.junit.Test; import org.slf4j.Logger; @@ -28,6 +27,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 +44,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 +86,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 +109,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 +253,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() { diff --git a/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java b/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java index 82b64435a..1d890d5fe 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,13 +58,13 @@ 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); diff --git a/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java b/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java index c7f04465b..40f5d18e5 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)); } @@ -59,13 +59,13 @@ 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); @@ -75,7 +75,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)); + } + } +} From addd3eeab47396e1ed5495211239ea3b9af6d015 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 23 May 2025 12:41:22 +0100 Subject: [PATCH 03/10] Improve logging in FileLockBasedLockChecker to include additional CPU identifier on lock failure --- .../openhft/affinity/lockchecker/FileLockBasedLockChecker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 265d8923f..8b06f7e39 100644 --- a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java +++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java @@ -123,7 +123,7 @@ public synchronized boolean obtainLock(int id, int id2, String metaInfo) throws 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; } From 872cbffa41d94f5bb15c47b82c041e08ee66d18e Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 23 May 2025 12:44:31 +0100 Subject: [PATCH 04/10] Deprecate obtainLock method with single id parameter in LockChecker --- .../openhft/affinity/lockchecker/LockChecker.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 62a6c2f52..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,6 +26,18 @@ public interface LockChecker { boolean isLockFree(int id); + /** + * 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); From c22684498406c657c52a3479c78f85dd78538706 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 3 Jun 2025 10:49:25 +0100 Subject: [PATCH 05/10] Refactor AffinityLock methods to improve CPU ID validation and logging --- .../java/net/openhft/affinity/AffinityLock.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java index 79d576ac9..dee1eedad 100644 --- a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java +++ b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java @@ -188,10 +188,14 @@ public static AffinityLock acquireLock(boolean bind) { * @return A handle for an affinity lock, or no lock if no available CPU in the array */ public static AffinityLock acquireLock(int cpuId) { + checkCpuId(cpuId); + return acquireLock(true, cpuId, AffinityStrategies.ANY); + } + + private static void checkCpuId(int cpuId) { if (cpuId < 0 || cpuId >= PROCESSORS) { - return LOCK_INVENTORY.noLock(); + LOGGER.warn("cpuId must be between 0 and {}: {}", PROCESSORS - 1, cpuId); } - return acquireLock(true, cpuId, AffinityStrategies.ANY); } /** @@ -205,10 +209,7 @@ public static AffinityLock acquireLock(int cpuId) { */ public static AffinityLock acquireLock(int[] cpus) { for (int cpu : cpus) { - if (cpu < 0 || cpu >= PROCESSORS) { - LOGGER.warn("cpuId {} is out of range", cpu); - continue; - } + checkCpuId(cpu); AffinityLock lock = tryAcquireLock(true, cpu); if (lock != null) { LOGGER.info("Acquired lock on CPU {}", cpu); From df7941ec59ace9cb3761ec0b657f35f01b4c4f75 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 3 Jun 2025 10:57:12 +0100 Subject: [PATCH 06/10] Refactor CPU ID validation in AffinityLock and enhance test cases for invalid inputs --- .../net/openhft/affinity/AffinityLock.java | 9 ++++++--- .../net/openhft/affinity/AffinityLockTest.java | 18 +++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java index dee1eedad..625578b9a 100644 --- a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java +++ b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java @@ -188,14 +188,17 @@ public static AffinityLock acquireLock(boolean bind) { * @return A handle for an affinity lock, or no lock if no available CPU in the array */ public static AffinityLock acquireLock(int cpuId) { - checkCpuId(cpuId); + if (isInvalidCpuId(cpuId)) + return LOCK_INVENTORY.noLock(); return acquireLock(true, cpuId, AffinityStrategies.ANY); } - private static void checkCpuId(int cpuId) { + private static boolean isInvalidCpuId(int cpuId) { if (cpuId < 0 || cpuId >= PROCESSORS) { LOGGER.warn("cpuId must be between 0 and {}: {}", PROCESSORS - 1, cpuId); + return true; } + return false; } /** @@ -209,7 +212,7 @@ private static void checkCpuId(int cpuId) { */ public static AffinityLock acquireLock(int[] cpus) { for (int cpu : cpus) { - checkCpuId(cpu); + if (isInvalidCpuId(cpu)) continue; AffinityLock lock = tryAcquireLock(true, cpu); if (lock != null) { LOGGER.info("Acquired lock on CPU {}", cpu); diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java index 18e4e666d..76416c74d 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java @@ -332,19 +332,22 @@ public void acquireLockWithoutBindingDoesNotChangeAffinity() { assertEquals(before, Affinity.getAffinity()); } - @Test(expected = IllegalArgumentException.class) + @Test public void testTooHighCpuId() { - AffinityLock.acquireLock(123456); - } + AffinityLock lock = AffinityLock.acquireLock(123456); + assertFalse(lock.isBound()); + } - @Test(expected = IllegalArgumentException.class) + @Test public void testNegativeCpuId() { - AffinityLock.acquireLock(-1); + AffinityLock lock = AffinityLock.acquireLock(-1); + assertFalse(lock.isBound()); } - @Test(expected = IllegalArgumentException.class) + @Test public void testTooHighCpuId2() { - AffinityLock.acquireLock(new int[]{123456}); + AffinityLock lock = AffinityLock.acquireLock(new int[]{-1, 123456}); + assertFalse(lock.isBound()); } @Test(expected = IllegalStateException.class) @@ -363,6 +366,7 @@ public void bindingTwoThreadsToSameCpuThrows() throws InterruptedException { t.start(); while (!lock.isBound()) { + //noinspection BusyWait Thread.sleep(10); } From d16bf605a9d740f2142ef66a6197a272aff819ef Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 3 Jun 2025 10:49:25 +0100 Subject: [PATCH 07/10] Refactor AffinityLock methods to improve CPU ID validation and logging --- .../src/main/java/net/openhft/affinity/AffinityLock.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java index 625578b9a..b73a03f6d 100644 --- a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java +++ b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java @@ -201,6 +201,12 @@ private static boolean isInvalidCpuId(int cpuId) { return false; } + private static void checkCpuId(int cpuId) { + if (cpuId < 0 || cpuId >= PROCESSORS) { + LOGGER.warn("cpuId must be between 0 and {}: {}", PROCESSORS - 1, cpuId); + } + } + /** * Assign a cpu which can be bound to the current thread or another thread * Caller passes in an explicit set of preferred CPUs From 53f1286813efb4bcbd4d94ff9e46c6789dbbf145 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 23 May 2025 12:25:12 +0100 Subject: [PATCH 08/10] Add tests for AffinityLock behavior and exception handling --- .../java/net/openhft/affinity/AffinityLockTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java index 76416c74d..b1c560da7 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java @@ -333,6 +333,16 @@ public void acquireLockWithoutBindingDoesNotChangeAffinity() { } @Test + public void acquireLockWithoutBindingDoesNotChangeAffinity() { + BitSet before = (BitSet) Affinity.getAffinity().clone(); + try (AffinityLock lock = AffinityLock.acquireLock(false)) { + assertFalse(lock.isBound()); + assertEquals(before, Affinity.getAffinity()); + } + assertEquals(before, Affinity.getAffinity()); + } + + @Test(expected = IllegalArgumentException.class) public void testTooHighCpuId() { AffinityLock lock = AffinityLock.acquireLock(123456); assertFalse(lock.isBound()); @@ -344,7 +354,7 @@ public void testNegativeCpuId() { assertFalse(lock.isBound()); } - @Test + @Test(expected = IllegalArgumentException.class) public void testTooHighCpuId2() { AffinityLock lock = AffinityLock.acquireLock(new int[]{-1, 123456}); assertFalse(lock.isBound()); From 08f97564d4ce541d81a295d6fbdddeb74287d839 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Fri, 23 May 2025 12:33:41 +0100 Subject: [PATCH 09/10] Fixes for java 9+ support --- affinity/pom.xml | 4 ++ .../net/openhft/affinity/BootClassPath.java | 49 ++++++++++++++++--- .../java/net/openhft/affinity/LockCheck.java | 8 +-- .../openhft/affinity/impl/NullAffinity.java | 4 +- .../openhft/affinity/impl/OSXJNAAffinity.java | 4 +- .../affinity/impl/SolarisJNAAffinity.java | 4 +- .../net/openhft/affinity/impl/Utilities.java | 26 ++++++++++ .../affinity/impl/WindowsJNAAffinity.java | 5 +- .../openhft/affinity/AffinityLockTest.java | 20 ++------ .../openhft/affinity/BootClassPathTest.java | 2 - .../affinity/FileLockLockCheckTest.java | 33 +++++++++++++ .../net/openhft/affinity/LockCheckTest.java | 5 ++ 12 files changed, 126 insertions(+), 38 deletions(-) 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/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/LockCheck.java b/affinity/src/main/java/net/openhft/affinity/LockCheck.java index ac9ca6cc1..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() { @@ -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()) { 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/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/test/java/net/openhft/affinity/AffinityLockTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java index b1c560da7..8bafde68a 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java @@ -333,30 +333,18 @@ public void acquireLockWithoutBindingDoesNotChangeAffinity() { } @Test - public void acquireLockWithoutBindingDoesNotChangeAffinity() { - BitSet before = (BitSet) Affinity.getAffinity().clone(); - try (AffinityLock lock = AffinityLock.acquireLock(false)) { - assertFalse(lock.isBound()); - assertEquals(before, Affinity.getAffinity()); - } - assertEquals(before, Affinity.getAffinity()); - } - - @Test(expected = IllegalArgumentException.class) 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(expected = IllegalArgumentException.class) + @Test public void testTooHighCpuId2() { - AffinityLock lock = AffinityLock.acquireLock(new int[]{-1, 123456}); + AffinityLock lock = AffinityLock.acquireLock(new int[]{123456}); assertFalse(lock.isBound()); } 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 1d890d5fe..6be18c5d7 100644 --- a/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java +++ b/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java @@ -71,4 +71,37 @@ public void shouldNotBlowUpIfPidFileIsEmpty() throws Exception { 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 40f5d18e5..2612886ce 100644 --- a/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java +++ b/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java @@ -55,6 +55,11 @@ 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++; From 4685c33ada79c871dd4533fe44b128510cd5e15d Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 3 Jun 2025 11:12:31 +0100 Subject: [PATCH 10/10] Replace busy wait with Waiters utility for lock binding in AffinityLockTest --- .../test/java/net/openhft/affinity/AffinityLockTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java index 8bafde68a..b1f422871 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java @@ -19,6 +19,7 @@ import net.openhft.affinity.impl.Utilities; import net.openhft.affinity.impl.VanillaCpuLayout; +import net.openhft.chronicle.testframework.Waiters; import org.hamcrest.MatcherAssert; import org.junit.Test; import org.slf4j.Logger; @@ -363,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();