Skip to content

Added tests for edge cases #179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
04268d4
Added tests for edge cases
peter-lawrey May 23, 2025
e70ae8d
AffinityLock.acquireCore should lock/unlock all cpus ie 2 when hypert…
peter-lawrey May 23, 2025
526cfff
Add tests for AffinityLock behavior and exception handling
peter-lawrey May 23, 2025
69157af
AffinityLock.acquireCore should lock/unlock all cpus ie 2 when hypert…
peter-lawrey May 23, 2025
addd3ee
Improve logging in FileLockBasedLockChecker to include additional CPU…
peter-lawrey May 23, 2025
c280448
Merge remote-tracking branch 'origin/acquireCore-fix' into acquireCor…
peter-lawrey May 23, 2025
872cbff
Deprecate obtainLock method with single id parameter in LockChecker
peter-lawrey May 23, 2025
2d931c3
Refactor AffinityLock methods to improve CPU ID validation and logging
peter-lawrey Jun 3, 2025
fa423ea
Refactor CPU ID validation in AffinityLock and enhance test cases for…
peter-lawrey Jun 3, 2025
cf3383a
Merge branch 'code-analysis' into added-tests
peter-lawrey Jun 3, 2025
c226844
Refactor AffinityLock methods to improve CPU ID validation and logging
peter-lawrey Jun 3, 2025
df7941e
Refactor CPU ID validation in AffinityLock and enhance test cases for…
peter-lawrey Jun 3, 2025
d16bf60
Refactor AffinityLock methods to improve CPU ID validation and logging
peter-lawrey Jun 3, 2025
4c7628d
Merge branch 'added-tests' into acquireCore-fix
peter-lawrey Jun 3, 2025
53f1286
Add tests for AffinityLock behavior and exception handling
peter-lawrey May 23, 2025
08f9756
Fixes for java 9+ support
peter-lawrey May 23, 2025
4685c33
Replace busy wait with Waiters utility for lock binding in AffinityLo…
peter-lawrey Jun 3, 2025
61dda34
Merge pull request #181 from OpenHFT/java9-fixes
peter-lawrey Jun 3, 2025
422fcfc
Merge pull request #180 from OpenHFT/acquireCore-fix
peter-lawrey Jun 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions affinity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@
<profile>
<id>make-c</id>
<activation>
<os>
<family>linux</family>
<arch>!arm</arch>
</os>
<property>
<name>!dontMake</name>
</property>
Expand Down
21 changes: 15 additions & 6 deletions affinity/src/main/java/net/openhft/affinity/AffinityLock.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -183,17 +185,20 @@ 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) {
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;
}

/**
Expand All @@ -207,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);
Expand Down Expand Up @@ -471,6 +476,10 @@ public int cpuId() {
return cpuId;
}

public int cpuId2() {
return cpuId2;
}

/**
* @return Was a cpu found to bind this lock to.
*/
Expand Down
49 changes: 43 additions & 6 deletions affinity/src/main/java/net/openhft/affinity/BootClassPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,21 +41,56 @@ enum BootClassPath {
private static Set<String> getResourcesOnBootClasspath() {
final Logger logger = LoggerFactory.getLogger(BootClassPath.class);
final Set<String> 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<String> findResourcesInJrt(final Logger logger) {
final Set<String> 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<Path>() {
@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<String> findResources(final Path path, final Logger logger) {
if (!Files.exists(path)) {
return Collections.emptySet();
Expand Down
6 changes: 6 additions & 0 deletions affinity/src/main/java/net/openhft/affinity/CpuLayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
20 changes: 11 additions & 9 deletions affinity/src/main/java/net/openhft/affinity/LockCheck.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand All @@ -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) {
Expand All @@ -70,15 +69,18 @@ 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) {
return lockChecker.isLockFree(id);
}

public static int getProcessForCpu(int core) throws IOException {
if (!canOSSupportOperation())
return EMPTY_PID;

String meta = lockChecker.getMetaInfo(core);

if (meta != null && !meta.isEmpty()) {
Expand All @@ -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) {
Expand Down
12 changes: 7 additions & 5 deletions affinity/src/main/java/net/openhft/affinity/LockInventory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,9 @@ public int coreId(int cpuId) {
public int threadId(int cpuId) {
return 0;
}

@Override
public int pair(int cpuId) {
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.management.ManagementFactory;
import java.util.BitSet;

/**
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.management.ManagementFactory;
import java.util.BitSet;

/**
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.management.ManagementFactory;
import java.util.BitSet;

/**
Expand Down Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions affinity/src/main/java/net/openhft/affinity/impl/Utilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -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&nbsp;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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Loading