diff --git a/affinity/src/main/java/java/lang/ThreadLifecycleListener.java b/affinity/src/main/java/java/lang/ThreadLifecycleListener.java
deleted file mode 100644
index 5df73921a..000000000
--- a/affinity/src/main/java/java/lang/ThreadLifecycleListener.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package java.lang;
-
-/**
- * A listener for various events in a Thread's life: creation, termination, etc.
- */
-public interface ThreadLifecycleListener {
-
- /**
- * The specified thread is about to be started.
- * @param t the thread which is being started
- */
- void started(Thread t);
-
- /**
- * The specified thread failed to start.
- * @param t the thread that had a failed start
- */
- void startFailed(Thread t);
-
- /**
- * The specified thread has been terminated.
- * @param t the thread that has been terminated
- */
- void terminated(Thread t);
-}
diff --git a/affinity/src/main/java/java/lang/ThreadTrackingGroup.java b/affinity/src/main/java/java/lang/ThreadTrackingGroup.java
deleted file mode 100644
index ad1ce10e8..000000000
--- a/affinity/src/main/java/java/lang/ThreadTrackingGroup.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package java.lang;
-
-/**
- * A wrapper of {@link java.lang.ThreadGroup} that tracks the creation and termination of threads.
- */
-public class ThreadTrackingGroup extends ThreadGroup {
-
- /**
- * Listener to be notified of various events in thread lifecycles.
- */
- private final ThreadLifecycleListener listener;
-
- public ThreadTrackingGroup(ThreadGroup parent, ThreadLifecycleListener listener) {
- super(parent, ThreadTrackingGroup.class.getSimpleName().toLowerCase() + System.identityHashCode(listener));
- this.listener = listener;
- }
-
- @Override
- void add(Thread t) {
- // System.out.println("ThreadTrackingGroup.add: " + t); //todo: remove
- super.add(t);
- listener.started(t);
- }
-
- @Override
- void threadStartFailed(Thread t) {
- super.threadStartFailed(t);
- listener.startFailed(t);
- }
-
- @Override
- void threadTerminated(Thread t) {
- super.threadTerminated(t);
- listener.terminated(t);
- }
-}
diff --git a/affinity/src/main/java/net/openhft/affinity/Affinity.java b/affinity/src/main/java/net/openhft/affinity/Affinity.java
index 5fb4807e9..8dadd8a15 100644
--- a/affinity/src/main/java/net/openhft/affinity/Affinity.java
+++ b/affinity/src/main/java/net/openhft/affinity/Affinity.java
@@ -1,21 +1,9 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
+import com.sun.jna.Native;
import net.openhft.affinity.impl.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@@ -27,12 +15,13 @@
import java.util.BitSet;
/**
- * Library to wrap low level JNI or JNA calls. Can be called without needing to know the actual implementation used.
+ * Library to wrap low level JNI or JNA calls. Can be called without needing to know the actual
+ * implementation used.
*
* @author peter.lawrey
*/
public enum Affinity {
- ;
+ ; // none
static final Logger LOGGER = LoggerFactory.getLogger(Affinity.class);
@NotNull
private static final IAffinity AFFINITY_IMPL;
@@ -126,7 +115,7 @@ private static boolean isMacJNAAffinityUsable() {
return true;
} else {
- LOGGER.warn("MAX OSX JNA-based affinity not usable due to JNA not being available!");
+ LOGGER.warn("MAC OSX JNA-based affinity not usable due to JNA not being available!");
return false;
}
}
@@ -153,16 +142,16 @@ public static BitSet getAffinity() {
return AFFINITY_IMPL.getAffinity();
}
+ public static void setAffinity(final BitSet affinity) {
+ AFFINITY_IMPL.setAffinity(affinity);
+ }
+
public static void setAffinity(int cpu) {
BitSet affinity = new BitSet(Runtime.getRuntime().availableProcessors());
affinity.set(cpu);
setAffinity(affinity);
}
- public static void setAffinity(final BitSet affinity) {
- AFFINITY_IMPL.setAffinity(affinity);
- }
-
public static int getCpu() {
return AFFINITY_IMPL.getCpu();
}
@@ -185,35 +174,37 @@ public static void setThreadId() {
}
public static boolean isJNAAvailable() {
- if (JNAAvailable == null)
- try {
- Class.forName("com.sun.jna.Platform");
- JNAAvailable = true;
- } catch (ClassNotFoundException ignored) {
+ if (JNAAvailable == null) {
+ int majorVersion = Integer.parseInt(Native.VERSION.split("\\.")[0]);
+ if (majorVersion < 5) {
+ LOGGER.warn("Affinity library requires JNA version >= 5");
JNAAvailable = false;
+ } else {
+ try {
+ Class.forName("com.sun.jna.Platform");
+ JNAAvailable = true;
+ } catch (ClassNotFoundException ignored) {
+ JNAAvailable = false;
+ }
}
+ }
return JNAAvailable;
}
public static AffinityLock acquireLock() {
- return isNonForkingAffinityAvailable() ? NonForkingAffinityLock.acquireLock() : AffinityLock.acquireLock();
+ return AffinityLock.acquireLock();
}
public static AffinityLock acquireCore() {
- return isNonForkingAffinityAvailable() ? NonForkingAffinityLock.acquireCore() : AffinityLock.acquireCore();
+ return AffinityLock.acquireCore();
}
public static AffinityLock acquireLock(boolean bind) {
- return isNonForkingAffinityAvailable() ? NonForkingAffinityLock.acquireLock(bind) : AffinityLock.acquireLock(bind);
+ return AffinityLock.acquireLock(bind);
}
public static AffinityLock acquireCore(boolean bind) {
- return isNonForkingAffinityAvailable() ? NonForkingAffinityLock.acquireCore(bind) : AffinityLock.acquireCore(bind);
- }
-
- private static boolean isNonForkingAffinityAvailable() {
- BootClassPath bootClassPath = BootClassPath.INSTANCE;
- return bootClassPath.has("java.lang.ThreadTrackingGroup") && bootClassPath.has("java.lang.ThreadLifecycleListener");
+ return AffinityLock.acquireCore(bind);
}
public static void resetToBaseAffinity() {
diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java
index d1234c06e..cf0ed4ca0 100644
--- a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java
+++ b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
import net.openhft.affinity.impl.NoCpuLayout;
@@ -25,11 +12,12 @@
import java.io.Closeable;
import java.io.File;
-import java.io.IOException;
+import java.util.Arrays;
import java.util.BitSet;
/**
- * This utility class support locking a thread to a single core, or reserving a whole core for a thread.
+ * This utility class support locking a thread to a single core, or reserving a whole core for a
+ * thread.
*
* @author peter.lawrey
*/
@@ -38,26 +26,36 @@ public class AffinityLock implements Closeable {
public static final String AFFINITY_RESERVED = "affinity.reserved";
// TODO It seems like on virtualized platforms .availableProcessors() value can change at
// TODO runtime. We should think about how to adopt to such change
- public static final int PROCESSORS = Runtime.getRuntime().availableProcessors();
- public static final BitSet BASE_AFFINITY = Affinity.getAffinity();
- public static final BitSet RESERVED_AFFINITY = getReservedAffinity0();
+ public static final int PROCESSORS;
+
+ public static final BitSet BASE_AFFINITY;
+ public static final BitSet RESERVED_AFFINITY;
+ static final int ANY_CPU = -1;
private static final Logger LOGGER = LoggerFactory.getLogger(AffinityLock.class);
- private static final LockInventory LOCK_INVENTORY = new LockInventory(new NoCpuLayout(PROCESSORS));
+ private static final LockInventory LOCK_INVENTORY;
static {
+ int processors = Runtime.getRuntime().availableProcessors();
+ VanillaCpuLayout cpuLayout = null;
try {
if (new File("/proc/cpuinfo").exists()) {
- cpuLayout(VanillaCpuLayout.fromCpuInfo());
+ cpuLayout = VanillaCpuLayout.fromCpuInfo();
+ processors = cpuLayout.cpus();
}
- } catch (IOException e) {
+ } catch (Throwable e) {
LOGGER.warn("Unable to load /proc/cpuinfo", e);
}
+ PROCESSORS = processors;
+ BASE_AFFINITY = Affinity.getAffinity();
+ RESERVED_AFFINITY = getReservedAffinity0();
+ LOCK_INVENTORY = new LockInventory(cpuLayout == null ? new NoCpuLayout(PROCESSORS) : cpuLayout);
}
/**
* 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.
*/
@@ -67,27 +65,28 @@ public class AffinityLock implements Closeable {
*/
private final boolean reservable;
/**
- * An inventory build from the CPU layout which keeps track of the various locks
- * belonging to each CPU.
+ * An inventory build from the CPU layout which keeps track of the various locks belonging to
+ * each CPU.
*/
private final LockInventory lockInventory;
boolean bound = false;
@Nullable
Thread assignedThread;
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;
}
/**
- * Set the CPU layout for this machine. CPUs which are not mentioned will be ignored.
- *
- * Changing the layout will have no impact on thread which have already been assigned.
- * It only affects subsequent assignments.
+ * Set the CPU layout for this machine. CPUs which are not mentioned will be ignored.
+ * Changing the layout will have no impact on thread which have already been assigned. It only
+ * affects subsequent assignments.
*
* @param cpuLayout for this application to use for this machine.
*/
@@ -110,18 +109,21 @@ private static BitSet getReservedAffinity0() {
reserverable.set(1, PROCESSORS, true);
reserverable.andNot(BASE_AFFINITY);
if (reserverable.isEmpty() && PROCESSORS > 1) {
- LoggerFactory.getLogger(AffinityLock.class).info("No isolated CPUs found, so assuming CPUs 1 to {} available.", (PROCESSORS - 1));
- reserverable = new BitSet(PROCESSORS);
- // make the first CPU unavailable
- reserverable.set(1, PROCESSORS - 1, true);
- reserverable.set(0, false);
+ // make all but first CPUs available
+ reserverable.set(1, PROCESSORS);
return reserverable;
}
return reserverable;
}
- long[] longs = new long[1];
- longs[0] = Long.parseLong(reservedAffinity, 16);
+ reservedAffinity = reservedAffinity.trim();
+ long[] longs = new long[1 + (reservedAffinity.length() - 1) / 16];
+ int end = reservedAffinity.length();
+ for (int i = 0; i < longs.length; i++) {
+ int begin = Math.max(0, end - 16);
+ longs[i] = Long.parseUnsignedLong(reservedAffinity.substring(begin, end), 16);
+ end = begin;
+ }
return BitSet.valueOf(longs);
}
@@ -134,10 +136,17 @@ public static AffinityLock acquireLock() {
return acquireLock(true);
}
+ static class Warnings {
+ static void warmNoReservedCPUs() {
+ if (RESERVED_AFFINITY.isEmpty() && PROCESSORS > 1) {
+ LoggerFactory.getLogger(AffinityLock.class).info("No isolated CPUs found, so assuming CPUs 1 to {} available.", (PROCESSORS - 1));
+ }
+ }
+ }
+
/**
- * Assign any free core to this thread.
- *
- * In reality, only one cpu is assigned, the rest of the threads for that core are reservable so they are not used.
+ * Assign any free core to this thread.
In reality, only one cpu is assigned, the rest of
+ * the threads for that core are reservable so they are not used.
*
* @return A handle for the current AffinityLock.
*/
@@ -146,34 +155,167 @@ public static AffinityLock acquireCore() {
}
/**
- * Assign a cpu which can be bound to the current thread or another thread.
- *
- * This can be used for defining your thread layout centrally and passing the handle via dependency injection.
+ * Assign a cpu which can be bound to the current thread or another thread.
This can be used
+ * for defining your thread layout centrally and passing the handle via dependency injection.
*
- * @param bind if true, bind the current thread, if false, reserve a cpu which can be bound later.
+ * @param bind if true, bind the current thread, if false, reserve a cpu which can be bound
+ * later.
* @return A handle for an affinity lock.
*/
public static AffinityLock acquireLock(boolean bind) {
- return acquireLock(bind, -1, AffinityStrategies.ANY);
+ return acquireLock(bind, ANY_CPU, AffinityStrategies.ANY);
+ }
+
+ /**
+ * Assign a cpu which can be bound to the current thread or another thread.
This can be used
+ * 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, or no lock if no available CPU in the array
+ */
+ public static AffinityLock acquireLock(int cpuId) {
+ if (isInvalidCpuId(cpuId))
+ return LOCK_INVENTORY.noLock();
+ return acquireLock(true, cpuId, AffinityStrategies.ANY);
+ }
+
+ 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;
+ }
+
+ /**
+ * Assign a cpu which can be bound to the current thread or another thread
+ * Caller passes in an explicit set of preferred CPUs
+ * The first available CPU is used, and the lock returned
+ * If all CPUs in the set are unavailable then no lock is obtained
+ *
+ * @param cpus the array of available CPUs to bind to
+ * @return A handle for an affinity lock, or nolock if no available CPU in the array
+ */
+ public static AffinityLock acquireLock(int[] cpus) {
+ for (int cpu : cpus) {
+ if (isInvalidCpuId(cpu)) continue;
+ AffinityLock lock = tryAcquireLock(true, cpu);
+ if (lock != null) {
+ LOGGER.info("Acquired lock on CPU {}", cpu);
+ return lock;
+ }
+ }
+
+ LOGGER.warn("Failed to lock any CPU in explicit list {}", Arrays.toString(cpus));
+ return LOCK_INVENTORY.noLock();
+ }
+
+ /**
+ * Allocate from the end.
+ *
+ * @param n positive number to allocate from.
+ * @return the lock acquired
+ */
+ public static AffinityLock acquireLockLastMinus(int n) {
+ return acquireLock(true, PROCESSORS - n, AffinityStrategies.ANY);
+ }
+
+ /**
+ * Use a description to allocate a cpu.
+ *
+ * - "N" being a positive integer means allocate this CPU,
+ * - "last" or "last-N" means allocate from the end,
+ * - "csv:1,2,5,6" eg means allocate first free core from the provided
+ * - "any" means allow any
+ * - "none" or null means
+ * - "0" is not allowed
+ *
+ *
+ * @param desc of which cpu to pick
+ * @return the AffinityLock obtained
+ */
+ public static AffinityLock acquireLock(String desc) {
+ if (desc == null)
+ return LOCK_INVENTORY.noLock();
+
+ desc = desc.toLowerCase();
+ int cpuId;
+ if (desc.startsWith("last")) {
+ String last = desc.substring(4);
+ int lastN;
+ if (last.isEmpty())
+ lastN = 0;
+ else
+ try {
+ lastN = Integer.parseInt(last);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Cannot parse '" + desc + "'", e);
+ }
+ if (lastN > 0)
+ throw new IllegalArgumentException("Cannot parse '" + desc + "'");
+
+ cpuId = PROCESSORS + lastN - 1;
+
+ } else if (desc.startsWith("csv:")) {
+ String content = desc.substring(4);
+ int[] cpus = Arrays.stream(content.split(","))
+ .map(String::trim)
+ .mapToInt(Integer::parseInt).toArray();
+
+ return acquireLock(cpus);
+
+ } else if (desc.equals("none")) {
+ return LOCK_INVENTORY.noLock();
+
+ } else if (desc.equals("any")) {
+ return acquireLock();
+
+ } else {
+ try {
+ cpuId = Integer.parseInt(desc);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Cannot parse '" + desc + "'", e);
+ }
+ }
+ if (cpuId <= 0) {
+ LOGGER.warn("Cannot allocate 0 or negative cpuIds '{}'", desc);
+ return LOCK_INVENTORY.noLock();
+ }
+ return acquireLock(cpuId);
}
/**
* Assign a core(and all its cpus) which can be bound to the current thread or another thread.
- *
- * This can be used for defining your thread layout centrally and passing the handle via dependency injection.
+ *
This can be used for defining your thread layout centrally and passing the handle via
+ * dependency injection.
*
- * @param bind if true, bind the current thread, if false, reserve a cpu which can be bound later.
+ * @param bind if true, bind the current thread, if false, reserve a cpu which can be bound
+ * later.
* @return A handle for an affinity lock.
*/
public static AffinityLock acquireCore(boolean bind) {
- return acquireCore(bind, -1, AffinityStrategies.ANY);
+ return acquireCore(bind, ANY_CPU, AffinityStrategies.ANY);
}
private static AffinityLock acquireLock(boolean bind, int cpuId, @NotNull AffinityStrategy... strategies) {
+ Warnings.warmNoReservedCPUs();
return LOCK_INVENTORY.acquireLock(bind, cpuId, strategies);
}
+ /**
+ * Try to acquire a lock on the specified core
+ * Returns lock if successful, or null if cpu cannot be acquired
+ *
+ * @param bind - if true, bind the current thread; if false, reserve a cpu which can be bound later
+ * @param cpuId - the cpu to lock
+ * @return - A handle to an affinity lock on success; null if failed to lock
+ */
+ private static AffinityLock tryAcquireLock(boolean bind, int cpuId) {
+ return LOCK_INVENTORY.tryAcquireLock(bind, cpuId);
+ }
+
private static AffinityLock acquireCore(boolean bind, int cpuId, @NotNull AffinityStrategy... strategies) {
+ Warnings.warmNoReservedCPUs();
return LOCK_INVENTORY.acquireCore(bind, cpuId, strategies);
}
@@ -187,12 +329,31 @@ public static String dumpLocks() {
private static boolean areAssertionsEnabled() {
boolean debug = false;
+ //noinspection AssertWithSideEffects
assert debug = true;
+ //noinspection ConstantValue
return debug;
}
/**
- * Assigning the current thread has a side effect of preventing the lock being used again until it is released.
+ * @return Whether to reset the affinity, false indicates the thread is about to die anyway.
+ */
+ public boolean resetAffinity() {
+ return resetAffinity;
+ }
+
+ /**
+ * @param resetAffinity Whether to reset the affinity, false indicates the thread is about to die anyway.
+ * @return this
+ */
+ public AffinityLock resetAffinity(boolean resetAffinity) {
+ this.resetAffinity = resetAffinity;
+ return this;
+ }
+
+ /**
+ * Assigning the current thread has a side effect of preventing the lock being used again until
+ * it is released.
*
* @param bind whether to bind the thread as well
* @param wholeCore whether to reserve all the thread in the same core.
@@ -227,7 +388,7 @@ public void bind(boolean wholeCore) {
} else if (cpuId >= 0) {
bound = true;
assignedThread = Thread.currentThread();
- LOGGER.info("Assigning cpu {} to {}", cpuId, assignedThread);
+ LOGGER.info("Assigning cpu {} to {} on thread id {}", cpuId, assignedThread, Affinity.getThreadId());
}
if (cpuId >= 0) {
BitSet affinity = new BitSet();
@@ -236,8 +397,14 @@ public void bind(boolean wholeCore) {
}
}
- final boolean canReserve() {
- if (!reservable) return false;
+ final boolean canReserve(boolean specified) {
+
+ if (!specified && !reservable)
+ return false;
+
+ if (!LockCheck.isCpuFree(cpuId))
+ return false;
+
if (assignedThread != null) {
if (assignedThread.isAlive()) {
return false;
@@ -249,10 +416,10 @@ final boolean canReserve() {
}
/**
- * Give another affinity lock relative to this one based on a list of strategies.
- *
- * The strategies are evaluated in order to (like a search path) to find the next appropriate thread.
- * If ANY is not the last strategy, a warning is logged and no cpu is assigned (leaving the OS to choose)
+ * Give another affinity lock relative to this one based on a list of strategies.
The
+ * strategies are evaluated in order to (like a search path) to find the next appropriate
+ * thread. If ANY is not the last strategy, a warning is logged and no cpu is assigned (leaving
+ * the OS to choose)
*
* @param strategies To determine if you want the same/different core/socket.
* @return A matching AffinityLock.
@@ -265,7 +432,12 @@ public AffinityLock acquireLock(AffinityStrategy... strategies) {
* Release the current AffinityLock which can be discarded.
*/
public void release() {
- lockInventory.release();
+ if (cpuId == ANY_CPU)
+ return;
+ // expensive if not actually used.
+ boolean resetAffinity = this.resetAffinity;
+ this.resetAffinity = true;
+ lockInventory.release(resetAffinity);
}
@Override
@@ -273,10 +445,11 @@ public void close() {
release();
}
+ @SuppressWarnings({"deprecation", "removal"})
@Override
protected void finalize() throws Throwable {
if (bound) {
- LOGGER.warn("Affinity lock for " + assignedThread + " was discarded rather than release()d in a controlled manner.", boundHere);
+ LOGGER.warn("Affinity lock for {} was discarded rather than release()d in a controlled manner.", assignedThread, boundHere);
release();
}
super.finalize();
@@ -289,6 +462,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/AffinityStrategies.java b/affinity/src/main/java/net/openhft/affinity/AffinityStrategies.java
index 71b498c42..65ee0cbf9 100644
--- a/affinity/src/main/java/net/openhft/affinity/AffinityStrategies.java
+++ b/affinity/src/main/java/net/openhft/affinity/AffinityStrategies.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
/**
@@ -22,6 +9,7 @@
* @author peter.lawrey
*/
public enum AffinityStrategies implements AffinityStrategy {
+
/**
* Any free cpu.
*/
diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityStrategy.java b/affinity/src/main/java/net/openhft/affinity/AffinityStrategy.java
index 4fe63a4e3..400c6d3c6 100644
--- a/affinity/src/main/java/net/openhft/affinity/AffinityStrategy.java
+++ b/affinity/src/main/java/net/openhft/affinity/AffinityStrategy.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
/**
diff --git a/affinity/src/main/java/net/openhft/affinity/AffinitySupport.java b/affinity/src/main/java/net/openhft/affinity/AffinitySupport.java
deleted file mode 100644
index 3fa119abe..000000000
--- a/affinity/src/main/java/net/openhft/affinity/AffinitySupport.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package net.openhft.affinity;
-
-/**
- * For backward compatibility with Affinity 2.x
- */
-@Deprecated
-public class AffinitySupport {
-
- public static int getThreadId() {
- return Affinity.getThreadId();
- }
-
- public static void setThreadId() {
- Affinity.setThreadId();
- }
-
-}
diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityThreadFactory.java b/affinity/src/main/java/net/openhft/affinity/AffinityThreadFactory.java
index 70c396617..6e1d67e5a 100644
--- a/affinity/src/main/java/net/openhft/affinity/AffinityThreadFactory.java
+++ b/affinity/src/main/java/net/openhft/affinity/AffinityThreadFactory.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
import org.jetbrains.annotations.NotNull;
@@ -52,20 +39,22 @@ public AffinityThreadFactory(String name, boolean daemon, @NotNull AffinityStrat
public synchronized Thread newThread(@NotNull final Runnable r) {
String name2 = id <= 1 ? name : (name + '-' + id);
id++;
- Thread t = new Thread(new Runnable() {
- @Override
- public void run() {
- AffinityLock al = lastAffinityLock == null ? AffinityLock.acquireLock() : lastAffinityLock.acquireLock(strategies);
- try {
- if (al.cpuId() >= 0)
- lastAffinityLock = al;
- r.run();
- } finally {
- al.release();
- }
+ Thread t = new Thread(() -> {
+ try (AffinityLock ignored = acquireLockBasedOnLast()) {
+ //noinspection ConstantValue
+ assert ignored != null;
+ r.run();
}
}, name2);
t.setDaemon(daemon);
return t;
}
+
+ private synchronized AffinityLock acquireLockBasedOnLast() {
+ AffinityLock al = lastAffinityLock == null ? AffinityLock.acquireLock(false) : lastAffinityLock.acquireLock(strategies);
+ al.bind();
+ if (al.cpuId() >= 0)
+ lastAffinityLock = al;
+ return al;
+ }
}
diff --git a/affinity/src/main/java/net/openhft/affinity/BootClassPath.java b/affinity/src/main/java/net/openhft/affinity/BootClassPath.java
index d37cc6d46..85efa3279 100644
--- a/affinity/src/main/java/net/openhft/affinity/BootClassPath.java
+++ b/affinity/src/main/java/net/openhft/affinity/BootClassPath.java
@@ -1,58 +1,132 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
+import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sun.misc.URLClassPath;
import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
enum BootClassPath {
INSTANCE;
- private final URLClassPath bootClassPath = new URLClassPath(getBootClassPathURLs());
+ private final Set bootClassPathResources = Collections.unmodifiableSet(getResourcesOnBootClasspath());
- public final boolean has(String binaryClassName) {
- String resourceClassName = binaryClassName.replace('.', '/').concat(".class");
- return bootClassPath.getResource(resourceClassName, false) != null;
+ 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", "");
+ if (!bootClassPath.isEmpty()) {
+ logger.trace("Boot class-path is: {}", bootClassPath);
+
+ final String pathSeparator = File.pathSeparator;
+ logger.trace("Path separator is: '{}'", pathSeparator);
+
+ final String[] pathElements = bootClassPath.split(pathSeparator);
+
+ for (final String pathElement : pathElements) {
+ resources.addAll(findResources(Paths.get(pathElement), logger));
+ }
+ } else {
+ resources.addAll(findResourcesInJrt(logger));
+ }
+
+ return resources;
}
- private URL[] getBootClassPathURLs() {
- Logger LOGGER = LoggerFactory.getLogger(BootClassPath.class);
+ private static Set findResourcesInJrt(final Logger logger) {
+ final Set jrtResources = new HashSet<>();
try {
- String bootClassPath = System.getProperty("sun.boot.class.path");
- LOGGER.trace("Boot class-path is: {}", bootClassPath);
+ 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;
+ }
- String pathSeparator = System.getProperty("path.separator");
- LOGGER.trace("Path separator is: '{}'", pathSeparator);
+ private static Set findResources(final Path path, final Logger logger) {
+ if (!Files.exists(path)) {
+ return Collections.emptySet();
+ }
- String[] pathElements = bootClassPath.split(pathSeparator);
- URL[] pathURLs = new URL[pathElements.length];
- for (int i = 0; i < pathElements.length; i++) {
- pathURLs[i] = new File(pathElements[i]).toURI().toURL();
+ if (Files.isDirectory(path)) {
+ return findResourcesInDirectory(path, logger);
+ }
+
+ return findResourcesInJar(path, logger);
+ }
+
+ private static Set findResourcesInJar(final Path path, final Logger logger) {
+ final Set jarResources = new HashSet<>();
+ try (final JarFile jarFile = new JarFile(path.toFile())) {
+ final Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ final JarEntry jarEntry = entries.nextElement();
+ if (jarEntry.getName().endsWith(".class")) {
+ jarResources.add(jarEntry.getName());
+ }
}
+ } catch (IOException e) {
+ logger.warn("Not a jar file: {}", path);
+ }
+
+ return jarResources;
+ }
- return pathURLs;
- } catch (MalformedURLException e) {
- LOGGER.warn("Parsing the boot class-path failed! Reason: {}", e.getMessage());
- return new URL[0];
+ private static Set findResourcesInDirectory(final Path path, final Logger logger) {
+ final Set dirResources = new HashSet<>();
+ try {
+ Files.walkFileTree(path, new SimpleFileVisitor() {
+ @Override
+ public @NotNull FileVisitResult visitFile(final @NotNull Path file, final @NotNull BasicFileAttributes attrs) throws IOException {
+ if (file.getFileName().toString().endsWith(".class")) {
+ dirResources.add(path.relativize(file).toString());
+ }
+ return super.visitFile(file, attrs);
+ }
+ });
+ } catch (IOException e) {
+ logger.warn("Error walking dir: {}", path, e);
}
+
+ return dirResources;
+ }
+
+ public final boolean has(String binaryClassName) {
+ final String resourceClassName = binaryClassName.replace('.', '/').concat(".class");
+ return bootClassPathResources.contains(resourceClassName);
}
}
diff --git a/affinity/src/main/java/net/openhft/affinity/CpuLayout.java b/affinity/src/main/java/net/openhft/affinity/CpuLayout.java
index 8293cc03a..0cbdd6412 100644
--- a/affinity/src/main/java/net/openhft/affinity/CpuLayout.java
+++ b/affinity/src/main/java/net/openhft/affinity/CpuLayout.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
/**
@@ -48,4 +35,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/IAffinity.java b/affinity/src/main/java/net/openhft/affinity/IAffinity.java
index 672e895d7..c473a5400 100644
--- a/affinity/src/main/java/net/openhft/affinity/IAffinity.java
+++ b/affinity/src/main/java/net/openhft/affinity/IAffinity.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
import java.util.BitSet;
diff --git a/affinity/src/main/java/net/openhft/affinity/LockCheck.java b/affinity/src/main/java/net/openhft/affinity/LockCheck.java
new file mode 100644
index 000000000..e3c485fed
--- /dev/null
+++ b/affinity/src/main/java/net/openhft/affinity/LockCheck.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+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;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Rob Austin.
+ */
+public enum LockCheck {
+ ; // none
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(LockCheck.class);
+ private static final String OS = System.getProperty("os.name").toLowerCase();
+ static final boolean IS_LINUX = OS.startsWith("linux");
+ private static final int EMPTY_PID = Integer.MIN_VALUE;
+
+ private static final LockChecker lockChecker = FileLockBasedLockChecker.getInstance();
+
+ public static long getPID() {
+ return Utilities.currentProcessId();
+ }
+
+ static boolean canOSSupportOperation() {
+ return IS_LINUX;
+ }
+
+ public static boolean isCpuFree(int cpu) {
+ if (!canOSSupportOperation())
+ return true;
+
+ return isLockFree(cpu);
+ }
+
+ static boolean replacePid(int cpu, int cpu2, long processID) throws IOException {
+ return storePid(processID, cpu, cpu2);
+ }
+
+ public static boolean isProcessRunning(long pid) {
+ if (canOSSupportOperation())
+ return new File("/proc/" + pid).exists();
+ else
+ throw new UnsupportedOperationException("this is only supported on LINUX");
+ }
+
+ /**
+ * 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, 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()) {
+ try {
+ return Integer.parseInt(meta);
+ } catch (NumberFormatException e) {
+ //nothing
+ }
+ }
+ return EMPTY_PID;
+ }
+
+ static boolean updateCpu(int cpu, int cpu2) throws IOException {
+ if (!canOSSupportOperation())
+ return true;
+ return replacePid(cpu, cpu2, getPID());
+ }
+
+ public static void releaseLock(int cpu) {
+ lockChecker.releaseLock(cpu);
+ }
+}
diff --git a/affinity/src/main/java/net/openhft/affinity/LockInventory.java b/affinity/src/main/java/net/openhft/affinity/LockInventory.java
index 25d798584..b54615219 100644
--- a/affinity/src/main/java/net/openhft/affinity/LockInventory.java
+++ b/affinity/src/main/java/net/openhft/affinity/LockInventory.java
@@ -1,28 +1,21 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
+import net.openhft.affinity.impl.NullAffinity;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.nio.channels.ClosedByInterruptException;
+import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
+import static net.openhft.affinity.Affinity.getAffinityImpl;
+
class LockInventory {
private static final Logger LOGGER = LoggerFactory.getLogger(LockInventory.class);
@@ -30,7 +23,7 @@ class LockInventory {
* The locks belonging to physical cores. Since a physical core can host multiple logical cores
* the relationship is one to many.
*/
- private final NavigableMap physicalCoreLocks = new TreeMap();
+ private final NavigableMap physicalCoreLocks = new TreeMap<>();
private CpuLayout cpuLayout;
/**
* The lock belonging to each logical core. 1-to-1 relationship
@@ -46,12 +39,48 @@ public static String dumpLocks(@NotNull AffinityLock[] locks) {
for (int i = 0; i < locks.length; i++) {
AffinityLock al = locks[i];
sb.append(i).append(": ");
- sb.append(al.toString());
+ sb.append(al);
sb.append('\n');
}
return sb.toString();
}
+ private static boolean anyStrategyMatches(final int cpuOne, final int cpuTwo, final AffinityStrategy[] strategies) {
+ for (AffinityStrategy strategy : strategies) {
+ if (strategy.matches(cpuOne, cpuTwo)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isAnyCpu(final int cpuId) {
+ return cpuId == AffinityLock.ANY_CPU;
+ }
+
+ /**
+ * Update the lock for the current thread
+ *
+ * @param bind Whether to also bind the thread to the core
+ * @param al The lock to update
+ * @param wholeCore Whether to bind the whole core
+ * @return true if the lock was acquired, false otherwise
+ */
+ private static boolean updateLockForCurrentThread(final boolean bind, final AffinityLock al, final boolean wholeCore) throws ClosedByInterruptException {
+ try {
+ if (LockCheck.updateCpu(al.cpuId(), wholeCore ? al.cpuId2() : 0)) {
+ al.assignCurrentThread(bind, wholeCore);
+ return true;
+ }
+ } catch (ClosedByInterruptException e) {
+ throw e;
+
+ } catch (IOException e) {
+ LOGGER.info("Error occurred acquiring lock, trying another {}", String.valueOf(e));
+ }
+ return false;
+ }
+
public final synchronized CpuLayout getCpuLayout() {
return cpuLayout;
}
@@ -64,9 +93,10 @@ public final synchronized void set(CpuLayout cpuLayout) {
for (int i = 0; i < cpuLayout.cpus(); i++) {
final boolean base = AffinityLock.BASE_AFFINITY.get(i);
final boolean reservable = AffinityLock.RESERVED_AFFINITY.get(i);
-
- LOGGER.trace("cpu " + i + " base={} reservable= {}", i, base, reservable);
- AffinityLock lock = logicalCoreLocks[i] = newLock(i, base, reservable);
+ LOGGER.trace("cpu {} base={} reservable= {}", 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);
@@ -76,24 +106,91 @@ public final synchronized void set(CpuLayout cpuLayout) {
}
locks[cpuLayout.threadId(layoutId)] = lock;
}
+ shrink(physicalCoreLocks);
+ }
+
+ /**
+ * If some CPUs are hyper-threaded, but not others, fix up the HT CPUs
+ */
+ private void shrink(NavigableMap physicalCoreLocks) {
+ for (Map.Entry e : physicalCoreLocks.entrySet()) {
+ final AffinityLock[] locks = e.getValue();
+ for (int i = 0; i < locks.length; i++) {
+ if (locks[i] == null) {
+ final AffinityLock[] locks2 = new AffinityLock[i];
+ System.arraycopy(locks, 0, locks2, 0, i);
+ physicalCoreLocks.put(e.getKey(), locks2);
+ break;
+ }
+ }
+ }
}
public final synchronized AffinityLock acquireLock(boolean bind, int cpuId, AffinityStrategy... strategies) {
- for (AffinityStrategy strategy : strategies) {
- // consider all processors except cpu 0 which is usually used by the OS.
- // if you have only one core, this library is not appropriate in any case.
- for (int i = logicalCoreLocks.length - 1; i > 0; i--) {
- AffinityLock al = logicalCoreLocks[i];
- if (al.canReserve() && (cpuId < 0 || strategy.matches(cpuId, al.cpuId()))) {
- al.assignCurrentThread(bind, false);
- return al;
+ if (getAffinityImpl() instanceof NullAffinity)
+ return noLock();
+
+ final boolean specificCpuRequested = !isAnyCpu(cpuId);
+ try {
+ if (specificCpuRequested && cpuId != 0) {
+ if (cpuId > logicalCoreLocks.length) {
+ LOGGER.warn("Unable to acquire lock on CPU {} for thread {}, as not enough CPUs",
+ cpuId, Thread.currentThread());
+ return noLock();
+ }
+
+ final AffinityLock required = logicalCoreLocks[cpuId];
+ if (required.canReserve(true)
+ && anyStrategyMatches(cpuId, cpuId, strategies)
+ && updateLockForCurrentThread(bind, required, false)) {
+ return required;
}
+ LOGGER.warn("Unable to acquire lock on CPU {} for thread {}, trying to find another CPU",
+ cpuId, Thread.currentThread());
}
+
+ for (AffinityStrategy strategy : strategies) {
+ // consider all processors except cpu 0 which is usually used by the OS.
+ // if you have only one core, this library is not appropriate in any case.
+ for (int i = logicalCoreLocks.length - 1; i > 0; i--) {
+ AffinityLock al = logicalCoreLocks[i];
+ if (al.canReserve(false)
+ && (isAnyCpu(cpuId) || strategy.matches(cpuId, al.cpuId()))
+ && updateLockForCurrentThread(bind, al, false)) {
+ return al;
+ }
+ }
+ }
+ } catch (ClosedByInterruptException e) {
+ Thread.currentThread().interrupt();
+ return noLock();
}
LOGGER.warn("No reservable CPU for {}", Thread.currentThread());
- return newLock(-1, false, false);
+ return noLock();
+ }
+
+ public final synchronized AffinityLock tryAcquireLock(boolean bind, int cpuId) {
+ if (getAffinityImpl() instanceof NullAffinity)
+ return null;
+ if (cpuId > logicalCoreLocks.length)
+ return null;
+ final AffinityLock required = logicalCoreLocks[cpuId];
+ try {
+ if (required.canReserve(true)
+ && updateLockForCurrentThread(bind, required, false)) {
+ return required;
+ }
+ } catch (ClosedByInterruptException e) {
+ Thread.currentThread().interrupt();
+ return noLock();
+ }
+
+ LOGGER.warn("Unable to acquire lock on CPU {} for thread {}, trying to find another CPU",
+ cpuId, Thread.currentThread());
+
+ return null;
}
public final synchronized AffinityLock acquireCore(boolean bind, int cpuId, AffinityStrategy... strategies) {
@@ -101,12 +198,18 @@ public final synchronized AffinityLock acquireCore(boolean bind, int cpuId, Affi
LOOP:
for (AffinityLock[] als : physicalCoreLocks.descendingMap().values()) {
for (AffinityLock al : als)
- if (!al.canReserve() || !strategy.matches(cpuId, al.cpuId()))
+ if (!al.canReserve(false) || !strategy.matches(cpuId, al.cpuId()))
continue LOOP;
final AffinityLock al = als[0];
- al.assignCurrentThread(bind, true);
- return al;
+ try {
+ if (updateLockForCurrentThread(bind, al, true)) {
+ return al;
+ }
+ } catch (ClosedByInterruptException e) {
+ Thread.currentThread().interrupt();
+ return noLock();
+ }
}
}
@@ -144,32 +247,26 @@ public final synchronized void bindWholeCore(int logicalCoreID) {
}
}
- public final synchronized void release() {
+ public final synchronized void release(boolean resetAffinity) {
Thread t = Thread.currentThread();
for (AffinityLock al : logicalCoreLocks) {
Thread at = al.assignedThread;
if (at == t) {
- LOGGER.info("Releasing cpu {} from {}", al.cpuId(), t);
- al.assignedThread = null;
- al.bound = false;
- al.boundHere = null;
-
+ releaseAffinityLock(t, al, "Releasing cpu {} from {}");
} else if (at != null && !at.isAlive()) {
- LOGGER.warn("Releasing cpu {} from {} as it is not alive.", al.cpuId(), t);
- al.assignedThread = null;
- al.bound = false;
- al.boundHere = null;
+ releaseAffinityLock(t, al, "Releasing cpu {} from {} as it is not alive.");
}
}
- Affinity.resetToBaseAffinity();
+ if (resetAffinity)
+ Affinity.resetToBaseAffinity();
}
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) {
@@ -181,4 +278,17 @@ private void reset(CpuLayout cpuLayout) {
private int toPhysicalCore(int layoutId) {
return cpuLayout.socketId(layoutId) * cpuLayout.coresPerSocket() + cpuLayout.coreId(layoutId);
}
-}
\ No newline at end of file
+
+ private void releaseAffinityLock(final Thread t, final AffinityLock al, final String format) {
+ LOGGER.info(format, al.cpuId(), t);
+ al.assignedThread = null;
+ al.bound = false;
+ al.boundHere = null;
+
+ LockCheck.releaseLock(al.cpuId());
+ }
+
+ public AffinityLock noLock() {
+ return newLock(AffinityLock.ANY_CPU, 0, false, false);
+ }
+}
diff --git a/affinity/src/main/java/net/openhft/affinity/MicroJitterSampler.java b/affinity/src/main/java/net/openhft/affinity/MicroJitterSampler.java
old mode 100755
new mode 100644
index 09d914145..30e27d221
--- a/affinity/src/main/java/net/openhft/affinity/MicroJitterSampler.java
+++ b/affinity/src/main/java/net/openhft/affinity/MicroJitterSampler.java
@@ -1,45 +1,9 @@
/*
- * Copyright 2014 Higher Frequency Trading
- *
- * http://www.higherfrequencytrading.com
- *
- * 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.
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
import java.io.PrintStream;
-/* e.g.
-After 2430 seconds, the average per hour was
-2us 78400
-3us 122703
-4us 345238
-6us 216098
-8us 78694
-10us 3977528
-14us 114495
-20us 4931
-30us 203
-40us 35
-60us 18
-80us 11
-100us 9
-140us 132
-200us 85
-300us 473
-400us 5
-1ms 24
- */
/**
* User: peter.lawrey Date: 30/06/13 Time: 13:13
@@ -54,30 +18,62 @@ public class MicroJitterSampler {
20 * 1000 * 1000, 50 * 1000 * 1000, 100 * 1000 * 1000
};
private static final double UTIL = Double.parseDouble(System.getProperty("util", "50"));
- // static final int CPU = Integer.getInteger("cpu", 0);
+ private static final boolean BUSYWAIT = Boolean.parseBoolean(System.getProperty("busywait", "false"));
+ private static final String CPU = System.getProperty("cpu", "none");
+
private final int[] count = new int[DELAY.length];
private long totalTime = 0;
+ private static void pause() throws InterruptedException {
+ if (BUSYWAIT) {
+ long now = System.nanoTime();
+ //noinspection StatementWithEmptyBody
+ while (System.nanoTime() - now < 1_000_000) ;
+ } else {
+ Thread.sleep(1);
+ }
+ }
+
public static void main(String... ignored) throws InterruptedException {
- AffinityLock al = AffinityLock.acquireLock();
-
- // warmup.
- new MicroJitterSampler().sample(1000 * 1000 * 1000);
-
- MicroJitterSampler microJitterSampler = new MicroJitterSampler();
- while (!Thread.currentThread().isInterrupted()) {
- if (UTIL >= 100) {
- microJitterSampler.sample(30L * 1000 * 1000 * 1000);
- } else {
- long sampleLength = (long) ((1 / (1 - UTIL / 100) - 1) * 1000 * 1000);
- for (int i = 0; i < 30 * 1000; i += 2) {
- microJitterSampler.sample(sampleLength);
- //noinspection BusyWait
- Thread.sleep(1);
- }
+ MicroJitterSampler sampler = new MicroJitterSampler();
+
+ Thread t = new Thread(sampler::run);
+ t.start();
+ t.join();
+ }
+
+ private void once() throws InterruptedException {
+ if (UTIL >= 100) {
+ sample(30L * 1000 * 1000 * 1000);
+ } else {
+ long sampleLength = (long) ((1 / (1 - UTIL / 100) - 1) * 1000 * 1000);
+ for (int i = 0; i < 30 * 1000; i += 2) {
+ sample(sampleLength);
+ //noinspection BusyWait
+ pause();
}
+ }
+ }
- microJitterSampler.print(System.out);
+ public void run() {
+ try (final AffinityLock lock = AffinityLock.acquireLock(CPU)) {
+ assert lock != null;
+ boolean first = true;
+ System.out.println("Warming up...");
+ while (!Thread.currentThread().isInterrupted()) {
+ once();
+
+ if (first) {
+ reset();
+ first = false;
+ System.out.println("Warmup complete. Running jitter tests...");
+ continue;
+ }
+
+ print(System.out);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
}
@@ -88,6 +84,12 @@ private static String asString(long timeNS) {
timeNS / 1000000000 + "sec";
}
+ void reset() {
+ for (int i = 0; i < DELAY.length; ++i)
+ count[i] = 0;
+ totalTime = 0;
+ }
+
void sample(long intervalNS) {
long prev = System.nanoTime();
long end = prev + intervalNS;
@@ -116,4 +118,84 @@ void print(PrintStream ps) {
}
ps.println();
}
-}
\ No newline at end of file
+}
+/* e.g.
+Ubuntu 20.04, Ryzen 5950X with an isolated CPU. (init 3) sudo cpupower -c {cpu} -g performance, run from command line
+After 3600 seconds, the average per hour was
+2us 2571
+3us 2304
+4us 376
+6us 1
+10us 1
+20us 1
+30us 1
+40us 2
+60us 1
+
+Ubuntu 20.04, Ryzen 5950X with an isolated CPU. (init 5) sudo cpupower -c {cpu} -g performance, run from command line
+After 3600 seconds, the average per hour was
+2us 2157
+3us 3444
+4us 3654
+6us 135
+8us 4
+14us 1
+20us 1
+40us 2
+60us 1
+
+Ubuntu 20.04, Ryzen 5950X with an isolated CPU. (init 5) sudo cpupower -c {cpu} -g performance, run from IntelliJ CE
+After 7200 seconds, the average per hour was
+2us 2189
+3us 3341
+4us 2335
+6us 191
+8us 4
+14us 1
+20us 1
+
+Windows 10 i7-4770 laptop
+After 1845 seconds, the average per hour was
+2us 2435969
+3us 548812
+4us 508041
+6us 60320
+8us 25374
+10us 1832324
+14us 2089216
+20us 391901
+30us 16063
+40us 6440
+60us 2617
+80us 1487
+100us 1241
+140us 826
+200us 2108
+300us 601
+400us 159
+600us 129
+800us 215
+1ms 155
+2ms 229
+5ms 24
+10ms 38
+20ms 32
+
+On an Centos 7 machine with an isolated CPU.
+After 2145 seconds, the average per hour was
+2us 781271
+3us 1212123
+4us 13504
+6us 489
+8us 2
+10us 3032577
+14us 17475
+20us 628
+30us 645
+40us 1301
+60us 1217
+80us 1306
+100us 1526
+140us 22
+
+ */
diff --git a/affinity/src/main/java/net/openhft/affinity/NonForkingAffinityLock.java b/affinity/src/main/java/net/openhft/affinity/NonForkingAffinityLock.java
deleted file mode 100644
index 55aadaf5b..000000000
--- a/affinity/src/main/java/net/openhft/affinity/NonForkingAffinityLock.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package net.openhft.affinity;
-
-import net.openhft.affinity.impl.NoCpuLayout;
-import org.jetbrains.annotations.NotNull;
-
-import java.lang.reflect.Field;
-
-public class NonForkingAffinityLock extends AffinityLock implements ThreadLifecycleListener {
-
- private static final Field GROUP_FIELD = makeThreadFieldModifiable("group");
-
- private static final Field TARGET_FIELD = makeThreadFieldModifiable("target");
-
- private static final LockInventory LOCK_INVENTORY = new LockInventory(new NoCpuLayout(PROCESSORS)) {
- @Override
- protected AffinityLock newLock(int cpuId, boolean base, boolean reservable) {
- return new NonForkingAffinityLock(cpuId, base, reservable, this);
- }
- };
-
- NonForkingAffinityLock(int cpuId, boolean base, boolean reservable, LockInventory lockInventory) {
- super(cpuId, base, reservable, lockInventory);
- }
-
- /**
- * Assign any free cpu to this thread.
- *
- * @return A handle for the current AffinityLock.
- */
- public static AffinityLock acquireLock() {
- return acquireLock(true);
- }
-
- /**
- * Assign any free core to this thread.
- *
- * In reality, only one cpu is assigned, the rest of the threads for that core are reservable so they are not used.
- *
- * @return A handle for the current AffinityLock.
- */
- public static AffinityLock acquireCore() {
- return acquireCore(true);
- }
-
- /**
- * Assign a cpu which can be bound to the current thread or another thread.
- *
- * This can be used for defining your thread layout centrally and passing the handle via dependency injection.
- *
- * @param bind if true, bind the current thread, if false, reserve a cpu which can be bound later.
- * @return A handle for an affinity lock.
- */
- public static AffinityLock acquireLock(boolean bind) {
- return acquireLock(bind, -1, AffinityStrategies.ANY);
- }
-
- /**
- * Assign a core(and all its cpus) which can be bound to the current thread or another thread.
- *
- * This can be used for defining your thread layout centrally and passing the handle via dependency injection.
- *
- * @param bind if true, bind the current thread, if false, reserve a cpu which can be bound later.
- * @return A handle for an affinity lock.
- */
- public static AffinityLock acquireCore(boolean bind) {
- return acquireCore(bind, -1, AffinityStrategies.ANY);
- }
-
- private static AffinityLock acquireLock(boolean bind, int cpuId, @NotNull AffinityStrategy... strategies) {
- return LOCK_INVENTORY.acquireLock(bind, cpuId, strategies);
- }
-
- private static AffinityLock acquireCore(boolean bind, int cpuId, @NotNull AffinityStrategy... strategies) {
- return LOCK_INVENTORY.acquireCore(bind, cpuId, strategies);
- }
-
- /**
- * Set the CPU layout for this machine. CPUs which are not mentioned will be ignored.
- *
- * Changing the layout will have no impact on thread which have already been assigned.
- * It only affects subsequent assignments.
- *
- * @param cpuLayout for this application to use for this machine.
- */
- public static void cpuLayout(@NotNull CpuLayout cpuLayout) {
- LOCK_INVENTORY.set(cpuLayout);
- }
-
- /**
- * @return The current CpuLayout for the application.
- */
- @NotNull
- public static CpuLayout cpuLayout() {
- return LOCK_INVENTORY.getCpuLayout();
- }
-
- /**
- * @return All the current locks as a String.
- */
- @NotNull
- public static String dumpLocks() {
- return LOCK_INVENTORY.dumpLocks();
- }
-
- private static Field makeThreadFieldModifiable(String fieldName) {
- try {
- Field field = Thread.class.getDeclaredField(fieldName);
- field.setAccessible(true);
- return field;
- } catch (NoSuchFieldException e) {
- throw new RuntimeException(Thread.class.getName() + " class doesn't have a " + fieldName + " field! Quite unexpected!");
- }
- }
-
- private static void changeGroupOfThread(Thread thread, ThreadGroup group) {
- try {
- GROUP_FIELD.set(thread, group);
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Failed changing " + Thread.class.getName() + "'s the '" + GROUP_FIELD.getName() + "' field! Reason: " + e.getMessage());
- }
- }
-
- private static void wrapRunnableOfThread(Thread thread, final AffinityLock lock) {
- try {
- final Runnable originalRunnable = (Runnable) TARGET_FIELD.get(thread);
- TARGET_FIELD.set(
- thread,
- new Runnable() {
- @Override
- public void run() {
- lock.release();
- originalRunnable.run();
- }
- }
- );
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Failed wrapping " + Thread.class.getName() + "'s '" + TARGET_FIELD.getName() + "' field! Reason: " + e.getMessage());
- }
- }
-
- @Override
- public void bind(boolean wholeCore) {
- super.bind(wholeCore);
- Thread thread = Thread.currentThread();
- changeGroupOfThread(thread, new ThreadTrackingGroup(thread.getThreadGroup(), this));
- }
-
- @Override
- public void release() {
- Thread thread = Thread.currentThread();
- changeGroupOfThread(thread, thread.getThreadGroup().getParent());
- super.release();
- }
-
- @Override
- public void started(Thread t) {
- wrapRunnableOfThread(t, this);
- }
-
- @Override
- public void startFailed(Thread t) {
- }
-
- @Override
- public void terminated(Thread t) {
- }
-}
diff --git a/affinity/src/main/java/net/openhft/affinity/impl/LinuxHelper.java b/affinity/src/main/java/net/openhft/affinity/impl/LinuxHelper.java
index 42dde87c9..a52404bba 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/LinuxHelper.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/LinuxHelper.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
import com.sun.jna.*;
@@ -22,6 +9,7 @@
import java.util.Arrays;
import java.util.BitSet;
+import java.util.Collections;
import java.util.List;
public class LinuxHelper {
@@ -39,7 +27,7 @@ public class LinuxHelper {
ver = new VersionHelper(uname.getRealeaseVersion());
}
} catch (Throwable e) {
- //logger.warn("Failed to determine Linux version: " + e);
+ //Jvm.warn().on(getClass(), "Failed to determine Linux version: " + e);
}
version = ver;
@@ -65,6 +53,10 @@ cpu_set_t sched_getaffinity() {
}
public static void sched_setaffinity(final BitSet affinity) {
+ sched_setaffinity(0, affinity);
+ }
+
+ public static void sched_setaffinity(final int pid, final BitSet affinity) {
final CLibrary lib = CLibrary.INSTANCE;
final cpu_set_t cpuset = new cpu_set_t();
final int size = version.isSameOrNewer(VERSION_2_6) ? cpu_set_t.SIZE_OF_CPU_SET_T : NativeLong.SIZE;
@@ -78,12 +70,12 @@ public static void sched_setaffinity(final BitSet affinity) {
}
}
try {
- if (lib.sched_setaffinity(0, size, cpuset) != 0) {
- throw new IllegalStateException("sched_setaffinity(0, " + size +
+ if (lib.sched_setaffinity(pid, size, cpuset) != 0) {
+ throw new IllegalStateException("sched_setaffinity(" + pid + ", " + size +
", 0x" + Utilities.toHexString(affinity) + ") failed; errno=" + Native.getLastError());
}
} catch (LastErrorException e) {
- throw new IllegalStateException("sched_setaffinity(0, " + size +
+ throw new IllegalStateException("sched_setaffinity(" + pid + ", " + size +
", 0x" + Utilities.toHexString(affinity) + ") failed; errno=" + e.getErrorCode(), e);
}
}
@@ -152,7 +144,7 @@ public static int syscall(int number, Object... args) {
}
interface CLibrary extends Library {
- CLibrary INSTANCE = (CLibrary) Native.loadLibrary(LIBRARY_NAME, CLibrary.class);
+ CLibrary INSTANCE = Native.load(LIBRARY_NAME, CLibrary.class);
int sched_setaffinity(final int pid,
final int cpusetsize,
@@ -225,7 +217,7 @@ static int length(final byte[] data) {
}
@Override
- protected List getFieldOrder() {
+ protected List getFieldOrder() {
return FIELD_ORDER;
}
@@ -233,7 +225,7 @@ public String getSysname() {
return new String(sysname, 0, length(sysname));
}
- @SuppressWarnings({"UnusedDeclaration"})
+ @SuppressWarnings("unused")
public String getNodename() {
return new String(nodename, 0, length(nodename));
}
@@ -264,7 +256,7 @@ public String getMachine() {
return new String(machine, 0, length(machine));
}
- @SuppressWarnings({"UnusedDeclaration"})
+ @SuppressWarnings("UnusedDeclaration")
public String getDomainname() {
return new String(domainname, 0, length(domainname));
}
@@ -280,7 +272,7 @@ public static class cpu_set_t extends Structure {
static final int __CPU_SETSIZE = 1024;
static final int __NCPUBITS = 8 * NativeLong.SIZE;
static final int SIZE_OF_CPU_SET_T = (__CPU_SETSIZE / __NCPUBITS) * NativeLong.SIZE;
- static List FIELD_ORDER = Arrays.asList("__bits");
+ static List FIELD_ORDER = Collections.singletonList("__bits");
public NativeLong[] __bits = new NativeLong[__CPU_SETSIZE / __NCPUBITS];
public cpu_set_t() {
@@ -289,10 +281,10 @@ public cpu_set_t() {
}
}
- @SuppressWarnings({"UnusedDeclaration"})
+ @SuppressWarnings("UnusedDeclaration")
public static void __CPU_ZERO(cpu_set_t cpuset) {
for (NativeLong bits : cpuset.__bits) {
- bits.setValue(0l);
+ bits.setValue(0L);
}
}
@@ -301,28 +293,28 @@ public static int __CPUELT(int cpu) {
}
public static long __CPUMASK(int cpu) {
- return 1l << (cpu % __NCPUBITS);
+ return 1L << (cpu % __NCPUBITS);
}
- @SuppressWarnings({"UnusedDeclaration"})
+ @SuppressWarnings("UnusedDeclaration")
public static void __CPU_SET(int cpu, cpu_set_t cpuset) {
cpuset.__bits[__CPUELT(cpu)].setValue(
cpuset.__bits[__CPUELT(cpu)].longValue() | __CPUMASK(cpu));
}
- @SuppressWarnings({"UnusedDeclaration"})
+ @SuppressWarnings("UnusedDeclaration")
public static void __CPU_CLR(int cpu, cpu_set_t cpuset) {
cpuset.__bits[__CPUELT(cpu)].setValue(
cpuset.__bits[__CPUELT(cpu)].longValue() & ~__CPUMASK(cpu));
}
- @SuppressWarnings({"UnusedDeclaration"})
+ @SuppressWarnings("UnusedDeclaration")
public static boolean __CPU_ISSET(int cpu, cpu_set_t cpuset) {
return (cpuset.__bits[__CPUELT(cpu)].longValue() & __CPUMASK(cpu)) != 0;
}
@Override
- protected List getFieldOrder() {
+ protected List getFieldOrder() {
return FIELD_ORDER;
}
}
diff --git a/affinity/src/main/java/net/openhft/affinity/impl/LinuxJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/LinuxJNAAffinity.java
index a060539e2..d16fc3beb 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/LinuxJNAAffinity.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/LinuxJNAAffinity.java
@@ -1,21 +1,6 @@
/*
- *
- * * Copyright (C) 2016 higherfrequencytrading.com
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU Lesser General Public License as published by
- * * the Free Software Foundation, either version 3 of the License.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU Lesser General Public License for more details.
- * *
- * * You should have received a copy of the GNU Lesser General Public License
- * * along with this program. If not, see .
- *
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
import com.sun.jna.NativeLong;
@@ -31,14 +16,17 @@ public enum LinuxJNAAffinity implements IAffinity {
public static final boolean LOADED;
private static final Logger LOGGER = LoggerFactory.getLogger(LinuxJNAAffinity.class);
private static final int PROCESS_ID;
- private static final int SYS_gettid = Platform.is64Bit() ? 186 : 224;
+ private static final int SYS_gettid = Platform.isPPC() ? 207 : Platform.is64Bit() ? 186 : 224;
private static final Object[] NO_ARGS = {};
+ private static final String OS = System.getProperty("os.name").toLowerCase();
+ private static final boolean IS_LINUX = OS.startsWith("linux");
+
static {
int pid = -1;
try {
pid = LinuxHelper.getpid();
- } catch (Exception ignored) {
+ } catch (NoClassDefFoundError | Exception ignored) {
}
PROCESS_ID = pid;
}
@@ -48,15 +36,15 @@ public enum LinuxJNAAffinity implements IAffinity {
try {
INSTANCE.getAffinity();
loaded = true;
- } catch (UnsatisfiedLinkError e) {
- LOGGER.warn("Unable to load jna library {}", e);
+ } catch (NoClassDefFoundError | UnsatisfiedLinkError e) {
+ if (IS_LINUX)
+ LOGGER.warn("Unable to load jna library", e);
}
LOADED = loaded;
}
private final ThreadLocal THREAD_ID = new ThreadLocal<>();
-
@Override
public BitSet getAffinity() {
final LinuxHelper.cpu_set_t cpuset = LinuxHelper.sched_getaffinity();
@@ -70,7 +58,6 @@ public BitSet getAffinity() {
return ret;
}
- // TODO: FIXME!!! CHANGE IAffinity TO SUPPORT PLATFORMS WITH 64+ CORES FIXME!!!
@Override
public void setAffinity(final BitSet affinity) {
LinuxHelper.sched_setaffinity(affinity);
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 eadc7247a..e57b1decc 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
import net.openhft.affinity.CpuLayout;
@@ -63,4 +50,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 4118c367e..4c5e8e304 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java
@@ -1,26 +1,12 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
import net.openhft.affinity.IAffinity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.lang.management.ManagementFactory;
import java.util.BitSet;
/**
@@ -37,7 +23,7 @@ public BitSet getAffinity() {
@Override
public void setAffinity(final BitSet affinity) {
- LOGGER.trace("unable to set mask to {} as the JNIa nd JNA libraries and not loaded", Utilities.toHexString(affinity));
+ LOGGER.trace("unable to set mask to {} as the JNI and JNA libraries not loaded", Utilities.toHexString(affinity));
}
@Override
@@ -47,8 +33,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 8d73c5643..fd020fd21 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
import com.sun.jna.LastErrorException;
@@ -23,7 +10,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.lang.management.ManagementFactory;
import java.util.BitSet;
/**
@@ -44,7 +30,7 @@ public BitSet getAffinity() {
@Override
public void setAffinity(final BitSet affinity) {
- LOGGER.trace("unable to set mask to {} as the JNIa nd JNA libraries and not loaded", Utilities.toHexString(affinity));
+ LOGGER.trace("unable to set mask to {} as the JNI and JNA libraries not loaded", Utilities.toHexString(affinity));
}
@Override
@@ -54,8 +40,7 @@ public int getCpu() {
@Override
public int getProcessId() {
- final String name = ManagementFactory.getRuntimeMXBean().getName();
- return Integer.parseInt(name.split("@")[0]);
+ return Utilities.currentProcessId();
}
@Override
@@ -71,8 +56,7 @@ public int getThreadId() {
}
interface CLibrary extends Library {
- CLibrary INSTANCE = (CLibrary)
- Native.loadLibrary("libpthread.dylib", CLibrary.class);
+ CLibrary INSTANCE = Native.load("libpthread.dylib", CLibrary.class);
int pthread_self() throws LastErrorException;
}
diff --git a/affinity/src/main/java/net/openhft/affinity/impl/PosixJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/PosixJNAAffinity.java
index d39671f36..39aed93ab 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/PosixJNAAffinity.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/PosixJNAAffinity.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
import com.sun.jna.*;
@@ -43,9 +30,7 @@ public enum PosixJNAAffinity implements IAffinity {
private static final Logger LOGGER = LoggerFactory.getLogger(PosixJNAAffinity.class);
private static final String LIBRARY_NAME = Platform.isWindows() ? "msvcrt" : "c";
private static final int PROCESS_ID;
- private static final boolean ISLINUX = "Linux".equals(System.getProperty("os.name"));
- private static final boolean IS64BIT = is64Bit0();
- private static final int SYS_gettid = is64Bit() ? 186 : 224;
+ private static final int SYS_gettid = Utilities.is64Bit() ? 186 : 224;
private static final Object[] NO_ARGS = {};
static {
@@ -64,30 +49,12 @@ public enum PosixJNAAffinity implements IAffinity {
INSTANCE.getAffinity();
loaded = true;
} catch (UnsatisfiedLinkError e) {
- LOGGER.warn("Unable to load jna library {}", e);
+ LOGGER.warn("Unable to load jna library", e);
}
LOADED = loaded;
}
- private final ThreadLocal THREAD_ID = new ThreadLocal();
-
- public static boolean is64Bit() {
- return IS64BIT;
- }
-
- private static boolean is64Bit0() {
- String systemProp;
- systemProp = System.getProperty("com.ibm.vm.bitmode");
- if (systemProp != null) {
- return "64".equals(systemProp);
- }
- systemProp = System.getProperty("sun.arch.data.model");
- if (systemProp != null) {
- return "64".equals(systemProp);
- }
- systemProp = System.getProperty("java.vm.version");
- return systemProp != null && systemProp.contains("_64");
- }
+ private final ThreadLocal THREAD_ID = new ThreadLocal<>();
@Override
public BitSet getAffinity() {
@@ -197,7 +164,7 @@ public int getProcessId() {
@Override
public int getThreadId() {
- if (ISLINUX) {
+ if (Utilities.ISLINUX) {
Integer tid = THREAD_ID.get();
if (tid == null)
THREAD_ID.set(tid = CLibrary.INSTANCE.syscall(SYS_gettid, NO_ARGS));
@@ -210,8 +177,7 @@ public int getThreadId() {
* @author BegemoT
*/
interface CLibrary extends Library {
- CLibrary INSTANCE = (CLibrary)
- Native.loadLibrary(LIBRARY_NAME, CLibrary.class);
+ CLibrary INSTANCE = Native.load(LIBRARY_NAME, CLibrary.class);
int sched_setaffinity(final int pid,
final int cpusetsize,
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 f68855efe..fcb5b8db0 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
import com.sun.jna.LastErrorException;
@@ -23,7 +10,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.lang.management.ManagementFactory;
import java.util.BitSet;
/**
@@ -44,7 +30,7 @@ public BitSet getAffinity() {
@Override
public void setAffinity(final BitSet affinity) {
- LOGGER.trace("unable to set mask to {} as the JNIa nd JNA libraries and not loaded", Utilities.toHexString(affinity));
+ LOGGER.trace("unable to set mask to {} as the JNI and JNA libraries not loaded", Utilities.toHexString(affinity));
}
@Override
@@ -54,8 +40,7 @@ public int getCpu() {
@Override
public int getProcessId() {
- final String name = ManagementFactory.getRuntimeMXBean().getName();
- return Integer.parseInt(name.split("@")[0]);
+ return Utilities.currentProcessId();
}
@Override
@@ -71,8 +56,7 @@ public int getThreadId() {
}
interface CLibrary extends Library {
- CLibrary INSTANCE = (CLibrary)
- Native.loadLibrary("c", CLibrary.class);
+ CLibrary INSTANCE = Native.load("c", CLibrary.class);
int pthread_self() throws LastErrorException;
}
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 66975ed7c..babf6b360 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java
@@ -1,13 +1,23 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
package net.openhft.affinity.impl;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.BitSet;
-/**
+/*
* Created by andre on 20/06/15.
*/
-public class Utilities {
+public final class Utilities {
+ public static final boolean ISLINUX = "Linux".equals(System.getProperty("os.name"));
+ static final boolean IS64BIT = is64Bit0();
+
+ private Utilities() {
+ throw new InstantiationError("Must not instantiate this class");
+ }
+
/**
* Creates a hexademical representation of the bit set
*
@@ -18,8 +28,8 @@ public static String toHexString(final BitSet set) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(out);
final long[] longs = set.toLongArray();
- for (int i = 0; i < longs.length; i++) {
- writer.write(Long.toHexString(longs[i]));
+ for (long aLong : longs) {
+ writer.write(Long.toHexString(aLong));
}
writer.flush();
@@ -30,11 +40,55 @@ public static String toBinaryString(BitSet set) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(out);
final long[] longs = set.toLongArray();
- for (int i = 0; i < longs.length; i++) {
- writer.write(Long.toBinaryString(longs[i]));
+ for (long aLong : longs) {
+ writer.write(Long.toBinaryString(aLong));
}
writer.flush();
return new String(out.toByteArray(), java.nio.charset.StandardCharsets.UTF_8);
}
+
+ public static boolean is64Bit() {
+ return IS64BIT;
+ }
+
+ private static boolean is64Bit0() {
+ String systemProp;
+ systemProp = System.getProperty("com.ibm.vm.bitmode");
+ if (systemProp != null) {
+ return "64".equals(systemProp);
+ }
+ systemProp = System.getProperty("sun.arch.data.model");
+ if (systemProp != null) {
+ return "64".equals(systemProp);
+ }
+ 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 28970076f..4fac94fe4 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java
@@ -1,26 +1,15 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
import net.openhft.affinity.CpuLayout;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.slf4j.LoggerFactory;
import java.io.*;
+import java.nio.charset.StandardCharsets;
import java.util.*;
import static java.lang.Integer.parseInt;
@@ -29,7 +18,7 @@
* @author peter.lawrey
*/
public class VanillaCpuLayout implements CpuLayout {
- public static final int MAX_CPUS_SUPPORTED = 64;
+ public static final int MAX_CPUS_SUPPORTED = 256;
@NotNull
private final List cpuDetails;
@@ -39,9 +28,9 @@ public class VanillaCpuLayout implements CpuLayout {
VanillaCpuLayout(@NotNull List cpuDetails) {
this.cpuDetails = cpuDetails;
- SortedSet sockets = new TreeSet(),
- cores = new TreeSet(),
- threads = new TreeSet();
+ SortedSet sockets = new TreeSet<>(),
+ cores = new TreeSet<>(),
+ threads = new TreeSet<>();
for (CpuInfo cpuDetail : cpuDetails) {
sockets.add(cpuDetail.socketId);
cores.add((cpuDetail.socketId << 16) + cpuDetail.coreId);
@@ -59,7 +48,7 @@ public class VanillaCpuLayout implements CpuLayout {
for (CpuInfo detail : cpuDetails) {
error.append(detail).append('\n');
}
- throw new AssertionError(error);
+ LoggerFactory.getLogger(VanillaCpuLayout.class).warn(error.toString());
}
}
@@ -77,7 +66,7 @@ public static VanillaCpuLayout fromProperties(InputStream is) throws IOException
@NotNull
public static VanillaCpuLayout fromProperties(@NotNull Properties prop) {
- List cpuDetails = new ArrayList();
+ List cpuDetails = new ArrayList<>();
for (int i = 0; i < MAX_CPUS_SUPPORTED; i++) {
String line = prop.getProperty("" + i);
if (line == null) break;
@@ -112,11 +101,11 @@ private static InputStream openFile(String filename) throws FileNotFoundExceptio
@NotNull
public static VanillaCpuLayout fromCpuInfo(InputStream is) throws IOException {
- BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+ BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
String line;
- List cpuDetails = new ArrayList();
+ List cpuDetails = new ArrayList<>();
CpuInfo details = new CpuInfo();
- Map threadCount = new LinkedHashMap();
+ Map threadCount = new LinkedHashMap<>();
while ((line = br.readLine()) != null) {
if (line.trim().isEmpty()) {
@@ -174,6 +163,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/VersionHelper.java b/affinity/src/main/java/net/openhft/affinity/impl/VersionHelper.java
index af73f00d3..08a01ab19 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/VersionHelper.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/VersionHelper.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
public class VersionHelper {
@@ -29,7 +16,7 @@ public VersionHelper(int major_, int minor_, int release_) {
}
public VersionHelper(String ver) {
- if (ver != null && (ver = ver.trim()).length() > 0) {
+ if (ver != null && !(ver = ver.trim()).isEmpty()) {
final String[] parts = ver.split("\\.");
major = parts.length > 0 ? Integer.parseInt(parts[0]) : 0;
minor = parts.length > 1 ? Integer.parseInt(parts[1]) : 0;
@@ -45,7 +32,7 @@ public String toString() {
}
public boolean equals(Object o) {
- if (o != null && (o instanceof VersionHelper)) {
+ if (o instanceof VersionHelper) {
VersionHelper ver = (VersionHelper) o;
return this.major == ver.major
&& this.minor == ver.minor
@@ -60,7 +47,7 @@ public int hashCode() {
return (major << 16) | (minor << 8) | release;
}
- @SuppressWarnings({"UnusedDeclaration"})
+ @SuppressWarnings("unused")
public boolean majorMinorEquals(final VersionHelper ver) {
return ver != null
&& this.major == ver.major
@@ -69,9 +56,10 @@ public boolean majorMinorEquals(final VersionHelper ver) {
public boolean isSameOrNewer(final VersionHelper ver) {
return ver != null
- && this.major >= ver.major
- && this.minor >= ver.minor
- && this.release >= ver.release;
+ && (this.major > ver.major
+ || this.major == ver.major
+ && (this.minor > ver.minor
+ || this.minor == ver.minor
+ && this.release >= ver.release));
}
}
-
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 e443b3321..bdeef6710 100644
--- a/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java
+++ b/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java
@@ -1,29 +1,15 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
-import com.sun.jna.LastErrorException;
-import com.sun.jna.Library;
-import com.sun.jna.Native;
-import com.sun.jna.PointerType;
+import com.sun.jna.*;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinDef;
+import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.ptr.LongByReference;
import net.openhft.affinity.IAffinity;
+import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,6 +27,7 @@ public enum WindowsJNAAffinity implements IAffinity {
INSTANCE;
public static final boolean LOADED;
private static final Logger LOGGER = LoggerFactory.getLogger(WindowsJNAAffinity.class);
+ private static final ThreadLocal currentAffinity = new ThreadLocal<>();
static {
boolean loaded = false;
@@ -57,26 +44,11 @@ public enum WindowsJNAAffinity implements IAffinity {
@Override
public BitSet getAffinity() {
- final CLibrary lib = CLibrary.INSTANCE;
- final LongByReference cpuset1 = new LongByReference(0);
- final LongByReference cpuset2 = new LongByReference(0);
- try {
-
- final int ret = lib.GetProcessAffinityMask(-1, cpuset1, cpuset2);
- // Successful result is positive, according to the docs
- // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683213%28v=vs.85%29.aspx
- if (ret <= 0) {
- throw new IllegalStateException("GetProcessAffinityMask(( -1 ), &(" + cpuset1 + "), &(" + cpuset2 + ") ) return " + ret);
- }
-
- long[] longs = new long[1];
- longs[0] = cpuset1.getValue();
- return BitSet.valueOf(longs);
- } catch (Exception e) {
- LOGGER.error(e.getMessage(), e);
- }
-
- return new BitSet();
+ BitSet bitSet = currentAffinity.get();
+ if (bitSet != null)
+ return bitSet;
+ BitSet longs = getAffinity0();
+ return longs != null ? longs : new BitSet();
}
@Override
@@ -98,10 +70,43 @@ public void setAffinity(final BitSet affinity) {
int pid = getTid();
try {
- lib.SetThreadAffinityMask(pid, aff);
+ lib.SetThreadAffinityMask(handle(pid), aff);
} catch (LastErrorException e) {
throw new IllegalStateException("SetThreadAffinityMask((" + pid + ") , &(" + affinity + ") ) errorNo=" + e.getErrorCode(), e);
}
+ BitSet affinity2 = getAffinity0();
+ 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());
+ }
+
+ @Nullable
+ private BitSet getAffinity0() {
+ final CLibrary lib = CLibrary.INSTANCE;
+ final LongByReference cpuset1 = new LongByReference(0);
+ final LongByReference cpuset2 = new LongByReference(0);
+ try {
+
+ final int ret = lib.GetProcessAffinityMask(handle(-1), cpuset1, cpuset2);
+ // Successful result is positive, according to the docs
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683213%28v=vs.85%29.aspx
+ if (ret <= 0) {
+ throw new IllegalStateException("GetProcessAffinityMask(( -1 ), &(" + cpuset1 + "), &(" + cpuset2 + ") ) return " + ret);
+ }
+
+ long[] longs = new long[1];
+ longs[0] = cpuset1.getValue();
+ return BitSet.valueOf(longs);
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ private WinNT.HANDLE handle(int pid) {
+ return new WinNT.HANDLE(new Pointer(pid));
}
public int getTid() {
@@ -136,11 +141,11 @@ public int getThreadId() {
* @author BegemoT
*/
private interface CLibrary extends Library {
- CLibrary INSTANCE = (CLibrary) Native.loadLibrary("kernel32", CLibrary.class);
+ CLibrary INSTANCE = Native.load("kernel32", CLibrary.class);
- int GetProcessAffinityMask(final int pid, final PointerType lpProcessAffinityMask, final PointerType lpSystemAffinityMask) throws LastErrorException;
+ int GetProcessAffinityMask(final WinNT.HANDLE pid, final PointerType lpProcessAffinityMask, final PointerType lpSystemAffinityMask) throws LastErrorException;
- void SetThreadAffinityMask(final int pid, final WinDef.DWORD lpProcessAffinityMask) throws LastErrorException;
+ void SetThreadAffinityMask(final WinNT.HANDLE pid, final WinDef.DWORD lpProcessAffinityMask) throws LastErrorException;
int GetCurrentThread() throws LastErrorException;
}
diff --git a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java
new file mode 100644
index 000000000..eb2394ddd
--- /dev/null
+++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.lockchecker;
+
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import static java.nio.file.StandardOpenOption.*;
+import static net.openhft.affinity.impl.VanillaCpuLayout.MAX_CPUS_SUPPORTED;
+
+public class FileLockBasedLockChecker implements LockChecker {
+
+ private static final int MAX_LOCK_RETRIES = 5;
+ private static final ThreadLocal dfTL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy.MM" + ".dd 'at' HH:mm:ss z"));
+ private static final FileAttribute> LOCK_FILE_ATTRIBUTES = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-rw-rw-"));
+ private static final Set LOCK_FILE_OPEN_OPTIONS = new HashSet<>(Arrays.asList(READ, WRITE, CREATE, SYNC));
+ private static final Logger LOGGER = LoggerFactory.getLogger(FileLockBasedLockChecker.class);
+ private static final FileLockBasedLockChecker instance = new FileLockBasedLockChecker();
+ private final LockReference[] locks = new LockReference[MAX_CPUS_SUPPORTED];
+
+ protected FileLockBasedLockChecker() {
+ //nothing
+ }
+
+ public static LockChecker getInstance() {
+ return instance;
+ }
+
+ @Override
+ public synchronized boolean isLockFree(int id) {
+ // check if this process already has the lock
+ if (locks[id] != null) {
+ return false;
+ }
+
+ // check if another process has the lock
+ File lockFile = toFile(id);
+ try (final FileChannel channel = FileChannel.open(lockFile.toPath(), READ)) {
+ // if we can acquire a shared lock, nobody has an exclusive lock
+ try (final FileLock fileLock = channel.tryLock(0, Long.MAX_VALUE, true)) {
+ if (fileLock != null && fileLock.isValid()) {
+ if (!lockFile.delete()) { // try and clean up the orphaned lock file
+ LOGGER.debug("Couldn't delete orphaned lock file {}", lockFile);
+ }
+ return true;
+ } else {
+ // another process has an exclusive lock
+ return false;
+ }
+ } catch (OverlappingFileLockException e) {
+ // someone else (in the same JVM) has an exclusive lock
+ /*
+ * This shouldn't happen under normal circumstances, we have the singleton
+ * {@link #locks} array to prevent overlapping locks from the same JVM, but
+ * it can occur when there are multiple classloaders in the JVM
+ */
+ return false;
+ }
+ } catch (NoSuchFileException e) {
+ // no lock file exists, nobody has the lock
+ return true;
+ } catch (IOException e) {
+ LOGGER.warn("An unexpected error occurred checking if the lock was free, assuming it's not", e);
+ return false;
+ }
+ }
+
+ @Override
+ 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) {
+ 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, id2);
+ return false;
+ }
+
+ /**
+ * Attempts to acquire an exclusive lock on the core lock file.
+ *
+ * It will fail if another process already has an exclusive lock on the file.
+ *
+ * @param id The CPU ID to acquire
+ * @param metaInfo The meta-info to write to the file upon successful acquisition
+ * @return The {@link LockReference} if the lock was successfully acquired, null otherwise
+ * @throws IOException If an IOException occurs creating or writing to the file
+ * @throws ConcurrentLockFileDeletionException If another process deleted the file between us opening it and locking it
+ */
+ private LockReference tryAcquireLockOnFile(int id, String metaInfo) throws IOException, ConcurrentLockFileDeletionException {
+ final File lockFile = toFile(id);
+ final FileChannel fileChannel = FileChannel.open(lockFile.toPath(), LOCK_FILE_OPEN_OPTIONS, LOCK_FILE_ATTRIBUTES); // NOSONAR
+ try {
+ final FileLock fileLock = fileChannel.tryLock(0, Long.MAX_VALUE, false);
+ if (fileLock == null) {
+ // someone else has a lock (exclusive or shared), fail to acquire
+ closeQuietly(fileChannel);
+ return null;
+ } else {
+ if (!lockFile.exists()) {
+ // someone deleted the file between us opening it and acquiring the lock, signal to retry
+ closeQuietly(fileLock, fileChannel);
+ throw new ConcurrentLockFileDeletionException();
+ } else {
+ // we have the lock, the file exists. That's success.
+ writeMetaInfoToFile(fileChannel, metaInfo);
+ return new LockReference(fileChannel, fileLock);
+ }
+ }
+ } catch (OverlappingFileLockException e) {
+ // someone else (in the same JVM) has a lock, fail to acquire
+ /*
+ * This shouldn't happen under normal circumstances, we have the singleton
+ * {@link #locks} array to prevent overlapping locks from the same JVM, but
+ * it can occur when there are multiple classloaders in the JVM
+ */
+ closeQuietly(fileChannel);
+ return null;
+ }
+ }
+
+ private void writeMetaInfoToFile(FileChannel fc, String metaInfo) throws IOException {
+ 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);
+ }
+ }
+
+ @Override
+ public synchronized boolean releaseLock(int id) {
+ if (locks[id] != null) {
+ final File lockFile = toFile(id);
+ if (!lockFile.delete()) {
+ LOGGER.warn("Couldn't delete lock file on release: {}", lockFile);
+ }
+ closeQuietly(locks[id].lock, locks[id].channel);
+ locks[id] = null;
+ return true;
+ }
+ return false;
+ }
+
+ private void closeQuietly(AutoCloseable... closeables) {
+ for (AutoCloseable closeable : closeables) {
+ try {
+ if (closeable != null) {
+ closeable.close();
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Error closing {}", closeable.getClass().getName(), e);
+ }
+ }
+ }
+
+ @Override
+ public String getMetaInfo(int id) throws IOException {
+ final File file = toFile(id);
+
+ LockReference lr = locks[id];
+ if (lr != null) {
+ return readMetaInfoFromLockFileChannel(file, lr.channel);
+ } else {
+ try (FileChannel fc = FileChannel.open(file.toPath(), READ)) {
+ return readMetaInfoFromLockFileChannel(file, fc);
+ } catch (NoSuchFileException e) {
+ return null;
+ }
+ }
+ }
+
+ private String readMetaInfoFromLockFileChannel(File lockFile, FileChannel lockFileChannel) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(64);
+ int len = lockFileChannel.read(buffer, 0);
+ String content = len < 1 ? "" : new String(buffer.array(), 0, len);
+ if (content.isEmpty()) {
+ LOGGER.warn("Empty lock file {}", lockFile.getAbsolutePath());
+ return null;
+ }
+ return content.substring(0, content.indexOf("\n"));
+ }
+
+ @NotNull
+ protected File toFile(int id) {
+ assert id >= 0;
+ return new File(tmpDir(), "cpu-" + id + ".lock");
+ }
+
+ private File tmpDir() {
+ final File tempDir = new File(System.getProperty("java.io.tmpdir"));
+
+ if (!tempDir.exists())
+ tempDir.mkdirs();
+
+ return tempDir;
+ }
+
+ /**
+ * Thrown when another process deleted the lock file between us opening the file and acquiring the lock
+ */
+ static class ConcurrentLockFileDeletionException extends Exception {
+ private static final long serialVersionUID = 0L;
+ }
+}
diff --git a/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java
new file mode 100644
index 000000000..ddbe0d49c
--- /dev/null
+++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.lockchecker;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Shercliff
+ */
+
+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);
+
+ String getMetaInfo(int id) throws IOException;
+}
diff --git a/affinity/src/main/java/net/openhft/affinity/lockchecker/LockReference.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockReference.java
new file mode 100644
index 000000000..10e2b1bdf
--- /dev/null
+++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockReference.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.lockchecker;
+
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+
+/**
+ * @author Tom Shercliff
+ */
+
+public class LockReference {
+ protected final FileChannel channel;
+ protected final FileLock lock;
+
+ public LockReference(final FileChannel channel, final FileLock lock) {
+ this.channel = channel;
+ this.lock = lock;
+ }
+
+ public FileChannel getChannel() {
+ return channel;
+ }
+}
diff --git a/affinity/src/main/java/net/openhft/affinity/main/AffinityTestMain.java b/affinity/src/main/java/net/openhft/affinity/main/AffinityTestMain.java
new file mode 100644
index 000000000..fcaa3281a
--- /dev/null
+++ b/affinity/src/main/java/net/openhft/affinity/main/AffinityTestMain.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.main;
+
+import net.openhft.affinity.Affinity;
+import net.openhft.affinity.AffinityLock;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @author Tom Shercliff
+ */
+public class AffinityTestMain {
+
+ public static void main(String[] args) {
+
+ int cpus;
+ if (args.length == 0) {
+ cpus = AffinityLock.cpuLayout().cpus() / 12;
+ } else {
+ cpus = Integer.parseInt(args[0]);
+ }
+
+ for (int i = 0; i < cpus; i++) {
+ acquireAndDoWork();
+ }
+ }
+
+ private static void acquireAndDoWork() {
+
+ Thread t = new Thread(() -> {
+ final SimpleDateFormat df = new SimpleDateFormat("yyyy.MM" + ".dd 'at' HH:mm:ss z");
+ try (AffinityLock al = Affinity.acquireLock()) {
+ String threadName = Thread.currentThread().getName();
+ System.out.println("Thread (" + threadName + ") locked onto cpu " + al.cpuId());
+
+ while (true) {
+ System.out.println(df.format(new Date()) + " - Thread (" + threadName + ") doing work on cpu " + al.cpuId() + ". IsAllocated = " + al.isAllocated() + ", isBound = " + al.isBound() + ". " + al);
+
+ try {
+ //noinspection BusyWait
+ Thread.sleep(10000L);
+ } catch (InterruptedException e) {
+ //nothing
+ }
+ }
+ }
+ });
+ t.start();
+ }
+}
diff --git a/affinity/src/main/java/net/openhft/ticker/ITicker.java b/affinity/src/main/java/net/openhft/ticker/ITicker.java
index 6e04f4645..3a9ab33d3 100644
--- a/affinity/src/main/java/net/openhft/ticker/ITicker.java
+++ b/affinity/src/main/java/net/openhft/ticker/ITicker.java
@@ -1,23 +1,25 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.ticker;
/**
- * Created by peter on 13/07/15.
+ * Abstraction of a high resolution time source used throughout the library.
+ *
+ * Implementations may be based on {@link System#nanoTime()} or platform
+ * specific timers such as the processor's time stamp counter accessed via
+ * JNI. The {@linkplain #ticks() tick values} returned are therefore
+ * implementation dependent. They always increase monotonically but the unit
+ * they represent can vary from nanoseconds to CPU cycles.
+ *
+ * Utility methods are provided to convert these raw ticks into conventional
+ * time units. For example {@link #toNanos(long)} converts the supplied number
+ * of ticks to nanoseconds and {@link #toMicros(double)} converts them to
+ * microseconds.
+ *
+ * This interface is typically accessed via the {@link net.openhft.ticker.Ticker}
+ * helper class which selects the best available implementation for the
+ * running platform.
*/
public interface ITicker {
long nanoTime();
diff --git a/affinity/src/main/java/net/openhft/ticker/Ticker.java b/affinity/src/main/java/net/openhft/ticker/Ticker.java
index 9fffdaafe..4d057cf2a 100644
--- a/affinity/src/main/java/net/openhft/ticker/Ticker.java
+++ b/affinity/src/main/java/net/openhft/ticker/Ticker.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.ticker;
import net.openhft.ticker.impl.JNIClock;
@@ -35,6 +22,10 @@ public final class Ticker {
}
}
+ private Ticker() {
+ throw new InstantiationError("Must not instantiate this class");
+ }
+
/**
* @return The current value of the system timer, in nanoseconds.
*/
@@ -53,4 +44,4 @@ public static long toNanos(long ticks) {
public static double toMicros(long ticks) {
return INSTANCE.toMicros(ticks);
}
-}
\ No newline at end of file
+}
diff --git a/affinity/src/main/java/net/openhft/ticker/impl/JNIClock.java b/affinity/src/main/java/net/openhft/ticker/impl/JNIClock.java
index e3bd78439..c50c3826c 100644
--- a/affinity/src/main/java/net/openhft/ticker/impl/JNIClock.java
+++ b/affinity/src/main/java/net/openhft/ticker/impl/JNIClock.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.ticker.impl;
import net.openhft.ticker.ITicker;
@@ -64,13 +51,14 @@ static long tscToNano(final long tsc) {
return (tsc * RDTSC_FACTOR) >> FACTOR_BITS;
}
+ @SuppressWarnings("StatementWithEmptyBody")
private static void estimateFrequency(int factor) {
final long start = System.nanoTime();
long now;
- while ((now = System.nanoTime()) == start) {
+ while (System.nanoTime() == start) {
}
- long end = start + factor * 1000000;
+ long end = start + factor * 1000000L;
final long start0 = rdtsc0();
while ((now = System.nanoTime()) < end) {
}
@@ -102,4 +90,4 @@ public long toNanos(long ticks) {
public double toMicros(double ticks) {
return ticks * RDTSC_MICRO_FACTOR;
}
-}
\ No newline at end of file
+}
diff --git a/affinity/src/main/java/net/openhft/ticker/impl/SystemClock.java b/affinity/src/main/java/net/openhft/ticker/impl/SystemClock.java
index 1d7e5d445..cbbd3545e 100644
--- a/affinity/src/main/java/net/openhft/ticker/impl/SystemClock.java
+++ b/affinity/src/main/java/net/openhft/ticker/impl/SystemClock.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.ticker.impl;
import net.openhft.ticker.ITicker;
@@ -46,5 +33,4 @@ public long toNanos(long ticks) {
public double toMicros(double ticks) {
return ticks / 1e3;
}
-
-}
\ No newline at end of file
+}
diff --git a/affinity/src/main/java/software/chronicle/enterprise/internals/impl/NativeAffinity.java b/affinity/src/main/java/software/chronicle/enterprise/internals/impl/NativeAffinity.java
index 2e1212f19..aa501a8f0 100644
--- a/affinity/src/main/java/software/chronicle/enterprise/internals/impl/NativeAffinity.java
+++ b/affinity/src/main/java/software/chronicle/enterprise/internals/impl/NativeAffinity.java
@@ -1,3 +1,6 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
package software.chronicle.enterprise.internals.impl;
import net.openhft.affinity.IAffinity;
@@ -25,6 +28,7 @@ public enum NativeAffinity implements IAffinity {
private native static long rdtsc0();
+ @SuppressWarnings("restricted")
private static boolean loadAffinityNativeLibrary() {
try {
System.loadLibrary("CEInternals");
@@ -35,19 +39,16 @@ private static boolean loadAffinityNativeLibrary() {
}
@Override
- public BitSet getAffinity()
- {
+ public BitSet getAffinity() {
final byte[] buff = getAffinity0();
- if (buff == null)
- {
+ if (buff == null) {
return null;
}
return BitSet.valueOf(buff);
}
@Override
- public void setAffinity(BitSet affinity)
- {
+ public void setAffinity(BitSet affinity) {
setAffinity0(affinity.toByteArray());
}
diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockBindMain.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockBindMain.java
index 2d2fe87de..b1ed7effe 100644
--- a/affinity/src/test/java/net/openhft/affinity/AffinityLockBindMain.java
+++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockBindMain.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
import static net.openhft.affinity.AffinityStrategies.*;
@@ -21,7 +8,11 @@
/**
* @author peter.lawrey
*/
-public class AffinityLockBindMain {
+public final class AffinityLockBindMain {
+ private AffinityLockBindMain() {
+ throw new InstantiationError("Must not instantiate this class");
+ }
+
public static void main(String... args) throws InterruptedException {
AffinityLock al = AffinityLock.acquireLock();
try {
diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockDumpLocksTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockDumpLocksTest.java
new file mode 100644
index 000000000..dd87e4f4e
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockDumpLocksTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity;
+
+import net.openhft.affinity.impl.VanillaCpuLayout;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+public class AffinityLockDumpLocksTest extends BaseAffinityTest {
+
+ private static void supressUnusedWarning(AutoCloseable c) {
+ // do nothing
+ }
+
+ @Test
+ public void dumpLocksListsThreadsHoldingLocks() throws Exception {
+ Assume.assumeTrue(new File("/proc/cpuinfo").exists());
+
+ AffinityLock.cpuLayout(VanillaCpuLayout.fromCpuInfo());
+ int nThreads = Math.min(3, Math.max(1, AffinityLock.PROCESSORS - 1));
+ CountDownLatch acquired = new CountDownLatch(nThreads);
+ CountDownLatch release = new CountDownLatch(1);
+ List threads = new ArrayList<>();
+
+ for (int i = 0; i < nThreads; i++) {
+ String name = "worker-" + i;
+ Thread t = new Thread(() -> {
+ try (AffinityLock lock = AffinityLock.acquireLock()) {
+ supressUnusedWarning(lock);
+ acquired.countDown();
+ release.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }, name);
+ threads.add(t);
+ t.start();
+ }
+
+ assertTrue("threads failed to acquire locks", acquired.await(5, TimeUnit.SECONDS));
+
+ String dump = AffinityLock.dumpLocks();
+ for (Thread t : threads) {
+ assertTrue("Missing entry for " + t.getName(), dump.contains(t.getName()));
+ }
+
+ release.countDown();
+ for (Thread t : threads) {
+ t.join();
+ }
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockMain.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockMain.java
index 92bf70a44..cf7388ec6 100644
--- a/affinity/src/test/java/net/openhft/affinity/AffinityLockMain.java
+++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockMain.java
@@ -1,25 +1,16 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
/**
* @author peter.lawrey
*/
-public class AffinityLockMain {
+public final class AffinityLockMain {
+ private AffinityLockMain() {
+ throw new InstantiationError("Must not instantiate this class");
+ }
+
public static void main(String... args) throws InterruptedException {
AffinityLock al = AffinityLock.acquireLock();
try {
diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockReleaseTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockReleaseTest.java
new file mode 100644
index 000000000..dc964296f
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockReleaseTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity;
+
+import net.openhft.affinity.impl.VanillaCpuLayout;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit test to verify that releasing an {@link AffinityLock} restores the
+ * affinity mask back to {@link AffinityLock#BASE_AFFINITY}.
+ */
+public class AffinityLockReleaseTest extends BaseAffinityTest {
+
+ @Test
+ public void acquireAndReleaseShouldRestoreBaseAffinity() throws Exception {
+ if (!new File("/proc/cpuinfo").exists()) {
+ System.out.println("Cannot run affinity test as this system doesn't have a /proc/cpuinfo file");
+ return;
+ }
+
+ // initialise CPU layout from the running machine so acquireLock works
+ AffinityLock.cpuLayout(VanillaCpuLayout.fromCpuInfo());
+
+ assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity());
+ AffinityLock lock = AffinityLock.acquireLock();
+ assertEquals(1, Affinity.getAffinity().cardinality());
+ lock.release();
+ assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity());
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java
index 7d6458ab1..4e8ff5519 100644
--- a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java
+++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java
@@ -1,52 +1,57 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
+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;
import org.slf4j.LoggerFactory;
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;
import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
+import static net.openhft.affinity.AffinityLock.PROCESSORS;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
/**
* @author peter.lawrey
*/
-@SuppressWarnings("ALL")
-public class AffinityLockTest {
+public class AffinityLockTest extends BaseAffinityTest {
private static final Logger logger = LoggerFactory.getLogger(AffinityLockTest.class);
+
+ /**
+ * In Java 21 the toString contents of Thread changed to include an ID. This breaks the tests here in Java 21.
+ * Strip out the thread ID here so that existing tests continue to pass.
+ */
+ private static String dumpLocks(AffinityLock[] locks) {
+ String value = LockInventory.dumpLocks(locks);
+ return value.replaceAll("#[0-9]+(,)?", "");
+ }
+
@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();
@@ -55,7 +60,7 @@ public void dumpLocksI7() throws IOException {
locks[6].assignedThread = new Thread(new InterrupedThread(), "main");
locks[7].assignedThread = new Thread(new InterrupedThread(), "tcp");
locks[7].assignedThread.start();
- final String actual = LockInventory.dumpLocks(locks);
+ final String actual = dumpLocks(locks);
assertEquals("0: General use CPU\n" +
"1: CPU not available\n" +
"2: Thread[logger,5,main] alive=true\n" +
@@ -76,16 +81,16 @@ 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();
locks[3].assignedThread = new Thread(new InterrupedThread(), "main");
- final String actual = LockInventory.dumpLocks(locks);
+ final String actual = dumpLocks(locks);
assertEquals("0: General use CPU\n" +
"1: Thread[engine,5,main] alive=true\n" +
"2: General use CPU\n" +
@@ -99,13 +104,13 @@ 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();
- final String actual = LockInventory.dumpLocks(locks);
+ final String actual = dumpLocks(locks);
assertEquals("0: General use CPU\n" +
"1: Thread[engine,5,main] alive=true\n", actual);
System.out.println(actual);
@@ -124,6 +129,7 @@ public void assignReleaseThread() throws IOException {
System.out.println("Cannot run affinity test as this system doesn't have a /proc/cpuinfo file");
return;
}
+
AffinityLock.cpuLayout(VanillaCpuLayout.fromCpuInfo());
assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity());
@@ -139,6 +145,22 @@ public void assignReleaseThread() throws IOException {
assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity());
}
+ @Test
+ public void resetAffinity() {
+ assumeTrue(System.getProperty("os.name").contains("nux"));
+ assertTrue(Affinity.getAffinity().cardinality() > 1);
+ try (AffinityLock lock = AffinityLock.acquireLock()) {
+ assertEquals(1, Affinity.getAffinity().cardinality());
+ assertTrue(lock.resetAffinity());
+ lock.resetAffinity(false);
+ }
+ assertEquals(1, Affinity.getAffinity().cardinality());
+ try (AffinityLock lock = AffinityLock.acquireLock()) {
+ assertNotNull(lock);
+ }
+ assertTrue(Affinity.getAffinity().cardinality() > 1);
+ }
+
@Test
public void testIssue21() throws IOException {
if (!new File("/proc/cpuinfo").exists()) {
@@ -151,13 +173,15 @@ public void testIssue21() throws IOException {
if (Runtime.getRuntime().availableProcessors() > 2) {
AffinityLock alForAnotherThread2 = al.acquireLock(AffinityStrategies.ANY);
assertNotSame(alForAnotherThread, alForAnotherThread2);
- assertNotSame(alForAnotherThread.cpuId(), alForAnotherThread2.cpuId());
+ if (alForAnotherThread.cpuId() != -1)
+ assertNotSame(alForAnotherThread.cpuId(), alForAnotherThread2.cpuId());
alForAnotherThread2.release();
} else {
assertNotSame(alForAnotherThread, al);
- assertNotSame(alForAnotherThread.cpuId(), al.cpuId());
+ if (alForAnotherThread.cpuId() != -1)
+ assertNotSame(alForAnotherThread.cpuId(), al.cpuId());
}
alForAnotherThread.release();
al.release();
@@ -165,10 +189,10 @@ public void testIssue21() throws IOException {
@Test
public void testIssue19() {
- System.out.println("AffinityLock.PROCESSORS=" + AffinityLock.PROCESSORS);
+ System.out.println("AffinityLock.PROCESSORS=" + PROCESSORS);
AffinityLock al = AffinityLock.acquireLock();
- List locks = new ArrayList();
+ List locks = new ArrayList<>();
locks.add(al);
for (int i = 0; i < 256; i++)
locks.add(al = al.acquireLock(AffinityStrategies.DIFFERENT_SOCKET,
@@ -187,34 +211,160 @@ public void testGettid() {
@Test
public void testAffinity() throws InterruptedException {
- // System.out.println("Started");
logger.info("Started");
displayStatus();
- final AffinityLock al = AffinityLock.acquireLock();
- try {
+ try (AffinityLock al = AffinityLock.acquireLock()) {
System.out.println("Main locked");
displayStatus();
- Thread t = new Thread(new Runnable() {
- @Override
- public void run() {
- AffinityLock al2 = al.acquireLock(AffinityStrategies.SAME_SOCKET, AffinityStrategies.ANY);
- System.out.println("Thread-0 locked");
- displayStatus();
- al2.release();
- }
+ Thread t = new Thread(() -> {
+ AffinityLock al2 = al.acquireLock(AffinityStrategies.SAME_SOCKET, AffinityStrategies.ANY);
+ System.out.println("Thread-0 locked");
+ displayStatus();
+ al2.release();
});
t.start();
t.join();
System.out.println("Thread-0 unlocked");
displayStatus();
- } finally {
- al.close();
}
System.out.println("All unlocked");
displayStatus();
}
+ @Test
+ public void shouldReturnLockForSpecifiedCpu() {
+ assumeTrue(Runtime.getRuntime().availableProcessors() > 3);
+
+ try (final AffinityLock affinityLock = AffinityLock.acquireLock(3)) {
+ MatcherAssert.assertThat(affinityLock.cpuId(), is(3));
+ }
+ assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity());
+ }
+
+ @Test
+ public void lockFilesShouldBeRemovedOnRelease() {
+ if (!Utilities.ISLINUX) {
+ return;
+ }
+ final AffinityLock lock = AffinityLock.acquireLock();
+
+ Path lockFile = Paths.get(System.getProperty("java.io.tmpdir"), "cpu-" + lock.cpuId() + ".lock");
+ assertTrue(Files.exists(lockFile));
+
+ lock.release();
+
+ 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() {
System.out.println(Thread.currentThread() + " on " + Affinity.getCpu() + "\n" + AffinityLock.dumpLocks());
}
+
+ @Test
+ public void testAffinityLockDescriptions() {
+ if (!Utilities.ISLINUX) {
+ return;
+ }
+ try (AffinityLock lock = AffinityLock.acquireLock("last")) {
+ assertNotNull(lock);
+ assertEquals(PROCESSORS - 1, Affinity.getCpu());
+ }
+ try (AffinityLock lock = AffinityLock.acquireLock("last")) {
+ assertNotNull(lock);
+ assertEquals(PROCESSORS - 1, Affinity.getCpu());
+ }
+ try (AffinityLock lock = AffinityLock.acquireLock("last-1")) {
+ assertNotNull(lock);
+ assertEquals(PROCESSORS - 2, Affinity.getCpu());
+ }
+ try (AffinityLock lock = AffinityLock.acquireLock("1")) {
+ assertNotNull(lock);
+ assertEquals(1, Affinity.getCpu());
+ }
+ try (AffinityLock lock = AffinityLock.acquireLock("any")) {
+ assertTrue(lock.bound);
+ }
+ try (AffinityLock lock = AffinityLock.acquireLock("none")) {
+ assertFalse(lock.bound);
+ }
+ try (AffinityLock lock = AffinityLock.acquireLock((String) null)) {
+ assertFalse(lock.bound);
+ }
+ try (AffinityLock lock = AffinityLock.acquireLock("0")) {
+ assertFalse(lock.bound);
+ }
+ }
+
+ @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
+ public void testTooHighCpuId() {
+ assertFalse(AffinityLock.acquireLock(123456).isBound());
+ }
+
+ @Test
+ public void testNegativeCpuId() {
+ assertFalse(AffinityLock.acquireLock(-1).isBound());
+ }
+
+ @Test
+ public void testTooHighCpuId2() {
+ AffinityLock lock = AffinityLock.acquireLock(new int[]{123456});
+ assertFalse(lock.isBound());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void bindingTwoThreadsToSameCpuThrows() throws InterruptedException {
+ assumeTrue(Runtime.getRuntime().availableProcessors() > 1);
+
+ final AffinityLock lock = AffinityLock.acquireLock(false);
+ Thread t = new Thread(() -> {
+ lock.bind();
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignored) {
+ // ignored
+ }
+ });
+ t.start();
+
+ Waiters.waitForCondition("Waiting for lock to be bound", lock::isBound, 1000);
+
+ try {
+ lock.bind();
+ } finally {
+ t.join();
+ lock.release();
+ }
+ }
}
diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityResetToBaseAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityResetToBaseAffinityTest.java
new file mode 100644
index 000000000..86ad17fed
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/AffinityResetToBaseAffinityTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity;
+
+import net.openhft.affinity.impl.VanillaCpuLayout;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.assertEquals;
+
+public class AffinityResetToBaseAffinityTest extends BaseAffinityTest {
+
+ @Test
+ public void resettingShouldRestoreBaseAffinity() throws Exception {
+ if (!new File("/proc/cpuinfo").exists()) {
+ System.out.println("Cannot run affinity test as this system doesn't have a /proc/cpuinfo file");
+ return;
+ }
+
+ // initialise CPU layout from the running machine so acquireLock works
+ AffinityLock.cpuLayout(VanillaCpuLayout.fromCpuInfo());
+
+ assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity());
+ AffinityLock lock = AffinityLock.acquireLock();
+ try {
+ assertEquals(1, Affinity.getAffinity().cardinality());
+
+ Affinity.resetToBaseAffinity();
+ assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity());
+ } finally {
+ lock.release();
+ }
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/AffinitySupportMain.java b/affinity/src/test/java/net/openhft/affinity/AffinitySupportMain.java
index 9cf4e6710..d7cf5b35d 100644
--- a/affinity/src/test/java/net/openhft/affinity/AffinitySupportMain.java
+++ b/affinity/src/test/java/net/openhft/affinity/AffinitySupportMain.java
@@ -1,25 +1,16 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
/**
* @author peter.lawrey
*/
-public class AffinitySupportMain {
+public final class AffinitySupportMain {
+ private AffinitySupportMain() {
+ throw new InstantiationError("Must not instantiate this class");
+ }
+
public static void main(String... args) {
AffinityLock al = AffinityLock.acquireLock();
try {
diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryMain.java b/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryMain.java
index 8a8a3e87a..3dbd15396 100644
--- a/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryMain.java
+++ b/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryMain.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
import java.util.concurrent.Callable;
@@ -26,22 +13,24 @@
/**
* @author peter.lawrey
*/
-public class AffinityThreadFactoryMain {
+public final class AffinityThreadFactoryMain {
private static final ExecutorService ES = Executors.newFixedThreadPool(4,
new AffinityThreadFactory("bg", SAME_CORE, DIFFERENT_SOCKET, ANY));
+ private AffinityThreadFactoryMain() {
+ throw new InstantiationError("Must not instantiate this class");
+ }
+
public static void main(String... args) throws InterruptedException {
for (int i = 0; i < 12; i++)
- ES.submit(new Callable() {
- @Override
- public Void call() throws InterruptedException {
- Thread.sleep(100);
- return null;
- }
+ ES.submit((Callable) () -> {
+ Thread.sleep(100);
+ return null;
});
Thread.sleep(200);
System.out.println("\nThe assignment of CPUs is\n" + AffinityLock.dumpLocks());
ES.shutdown();
+ //noinspection ResultOfMethodCallIgnored
ES.awaitTermination(1, TimeUnit.SECONDS);
}
}
diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryTest.java
new file mode 100644
index 000000000..4c95b8bd4
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Set;
+import java.util.concurrent.*;
+
+import static org.junit.Assert.*;
+
+public class AffinityThreadFactoryTest extends BaseAffinityTest {
+
+ @Before
+ public void checkLinux() {
+ Assume.assumeTrue(LockCheck.IS_LINUX);
+ }
+
+ @Test
+ public void threadsReceiveDistinctCpus() throws InterruptedException {
+ int available = Math.max(1, AffinityLock.PROCESSORS - 1);
+ int nThreads = Math.min(4, available);
+
+ ExecutorService es = Executors.newFixedThreadPool(nThreads,
+ new AffinityThreadFactory("test"));
+
+ Set cpus = ConcurrentHashMap.newKeySet();
+ CountDownLatch ready = new CountDownLatch(nThreads);
+ CountDownLatch finished = new CountDownLatch(nThreads);
+
+ for (int i = 0; i < nThreads; i++) {
+ es.submit(() -> {
+ cpus.add(Affinity.getCpu());
+ ready.countDown();
+ try {
+ ready.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ finished.countDown();
+ });
+ }
+
+ assertTrue(finished.await(5, TimeUnit.SECONDS));
+ es.shutdown();
+ es.awaitTermination(5, TimeUnit.SECONDS);
+
+ assertFalse(cpus.contains(-1));
+ assertEquals(nThreads, cpus.size());
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/BaseAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/BaseAffinityTest.java
new file mode 100644
index 000000000..f147ce33b
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/BaseAffinityTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.BitSet;
+
+import static org.junit.Assert.assertEquals;
+
+public class BaseAffinityTest {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+ private String originalTmpDir;
+
+ @Before
+ public void setTmpDirectory() {
+ originalTmpDir = System.getProperty("java.io.tmpdir");
+ System.setProperty("java.io.tmpdir", folder.getRoot().getAbsolutePath());
+ }
+
+ @After
+ public void restoreTmpDirectoryAndReleaseAllLocks() {
+ // don't leave any locks locked
+ for (int i = 0; i < AffinityLock.PROCESSORS; i++) {
+ LockCheck.releaseLock(i);
+ }
+ System.setProperty("java.io.tmpdir", originalTmpDir);
+ }
+
+ @After
+ public void baseAffinity() {
+ BitSet affinity = Affinity.getAffinity();
+ Affinity.resetToBaseAffinity();
+ assertEquals(AffinityLock.BASE_AFFINITY, affinity);
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java b/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java
new file mode 100644
index 000000000..17558a01e
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+public class BootClassPathTest extends BaseAffinityTest {
+ @Test
+ public void shouldDetectClassesOnClassPath() {
+ 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
new file mode 100644
index 000000000..f1a88ddc1
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity;
+
+import net.openhft.affinity.testimpl.TestFileLockBasedLockChecker;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import static net.openhft.affinity.LockCheck.IS_LINUX;
+
+/**
+ * @author Tom Shercliff
+ */
+public class FileLockLockCheckTest extends BaseAffinityTest {
+
+ private final TestFileLockBasedLockChecker lockChecker = new TestFileLockBasedLockChecker();
+ private int cpu = 5;
+
+ @Before
+ public void before() {
+ Assume.assumeTrue(IS_LINUX);
+ }
+
+ @Test
+ public void test() throws IOException {
+ Assert.assertTrue(LockCheck.isCpuFree(cpu));
+ LockCheck.updateCpu(cpu, 0);
+ Assert.assertEquals(LockCheck.getPID(), LockCheck.getProcessForCpu(cpu));
+ }
+
+ @Test
+ public void testPidOnLinux() {
+ Assert.assertTrue(LockCheck.isProcessRunning(LockCheck.getPID()));
+ }
+
+ @Test
+ public void testReplace() throws IOException {
+ cpu++;
+ Assert.assertTrue(LockCheck.isCpuFree(cpu + 1));
+ LockCheck.replacePid(cpu, 0, 123L);
+ Assert.assertEquals(123L, LockCheck.getProcessForCpu(cpu));
+ }
+
+ @Test
+ public void shouldNotBlowUpIfPidFileIsEmpty() throws Exception {
+ 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/InterrupedThread.java b/affinity/src/test/java/net/openhft/affinity/InterrupedThread.java
index ab3701f3a..9e85c0790 100644
--- a/affinity/src/test/java/net/openhft/affinity/InterrupedThread.java
+++ b/affinity/src/test/java/net/openhft/affinity/InterrupedThread.java
@@ -1,19 +1,6 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity;
/**
diff --git a/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java b/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java
new file mode 100644
index 000000000..3f19a4a2f
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity;
+
+import net.openhft.affinity.testimpl.TestFileLockBasedLockChecker;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import static net.openhft.affinity.LockCheck.IS_LINUX;
+
+/**
+ * @author Rob Austin.
+ */
+public class LockCheckTest extends BaseAffinityTest {
+
+ private final TestFileLockBasedLockChecker lockChecker = new TestFileLockBasedLockChecker();
+ private int cpu = 11;
+
+ @Before
+ public void before() {
+ Assume.assumeTrue(IS_LINUX);
+ }
+
+ @Test
+ public void test() throws IOException {
+ Assert.assertTrue(LockCheck.isCpuFree(cpu));
+ LockCheck.updateCpu(cpu, 0);
+ Assert.assertEquals(LockCheck.getPID(), LockCheck.getProcessForCpu(cpu));
+ }
+
+ @Test
+ 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, 0, 123L);
+ Assert.assertEquals(123L, LockCheck.getProcessForCpu(cpu));
+ }
+
+ @Test
+ public void shouldNotBlowUpIfPidFileIsEmpty() throws Exception {
+ LockCheck.updateCpu(cpu, 0);
+
+ final File file = lockChecker.doToFile(cpu);
+ new RandomAccessFile(file, "rw").setLength(0);
+
+ LockCheck.isCpuFree(cpu);
+ }
+
+ @Test
+ public void shouldNotBlowUpIfPidFileIsCorrupt() throws Exception {
+ LockCheck.updateCpu(cpu, 0);
+
+ final File file = lockChecker.doToFile(cpu);
+ try (final FileWriter writer = new FileWriter(file, false)) {
+ writer.append("not a number\nnot a date");
+ }
+
+ LockCheck.isCpuFree(cpu);
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/MultiProcessAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/MultiProcessAffinityTest.java
new file mode 100644
index 000000000..63dd9105b
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/MultiProcessAffinityTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity;
+
+import net.openhft.affinity.testimpl.TestFileLockBasedLockChecker;
+import net.openhft.chronicle.testframework.process.JavaProcessBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.StandardOpenOption;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static java.lang.String.format;
+import static net.openhft.affinity.LockCheck.IS_LINUX;
+import static org.junit.Assert.*;
+
+public class MultiProcessAffinityTest extends BaseAffinityTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MultiProcessAffinityTest.class);
+
+ @Before
+ public void setUp() {
+ Assume.assumeTrue(IS_LINUX);
+ }
+
+ @Test
+ public void shouldNotAcquireLockOnCoresLockedByOtherProcesses() throws InterruptedException {
+ // run the separate affinity locker
+ final Process affinityLockerProcess = JavaProcessBuilder.create(AffinityLockerProcess.class)
+ .withJvmArguments("-Djava.io.tmpdir=" + folder.getRoot().getAbsolutePath())
+ .withProgramArguments("last").start();
+ try {
+ int lastCpuId = AffinityLock.PROCESSORS - 1;
+
+ // wait for the CPU to be locked
+ long endTime = System.currentTimeMillis() + 5_000;
+ while (LockCheck.isCpuFree(lastCpuId)) {
+ //noinspection BusyWait
+ Thread.sleep(100);
+ if (System.currentTimeMillis() > endTime) {
+ LOGGER.info("Timed out waiting for the lock to be acquired: isAlive={}, exitCode={}",
+ affinityLockerProcess.isAlive(), affinityLockerProcess.isAlive() ? "N/A" : affinityLockerProcess.exitValue());
+ JavaProcessBuilder.printProcessOutput("AffinityLockerProcess", affinityLockerProcess);
+ fail("Timed out waiting for the sub-process to acquire the lock");
+ }
+ }
+
+ try (AffinityLock lock = AffinityLock.acquireLock("last")) {
+ assertNotEquals(lastCpuId, lock.cpuId());
+ }
+ } finally {
+ affinityLockerProcess.destroy();
+ waitForProcessToEnd(5, "Affinity locker process", affinityLockerProcess, false);
+ }
+ }
+
+ @Test
+ public void shouldAllocateCoresCorrectlyUnderContention() throws InterruptedException {
+ final int numberOfLockers = Math.max(2, Math.min(12, Runtime.getRuntime().availableProcessors())) / 2;
+ List lockers = new ArrayList<>();
+ LOGGER.info("Running test with {} locker processes", numberOfLockers);
+ for (int i = 0; i < numberOfLockers; i++) {
+ lockers.add(JavaProcessBuilder.create(RepeatedAffinityLocker.class)
+ .withJvmArguments("-Djava.io.tmpdir=" + folder.getRoot().getAbsolutePath())
+ .withProgramArguments("last", "30", "2").start());
+ }
+ for (int i = 0; i < numberOfLockers; i++) {
+ final Process process = lockers.get(i);
+ waitForProcessToEnd(20, "Locking process", process);
+ }
+ }
+
+ @Test
+ public void shouldAllocateCoresCorrectlyUnderContentionWithFailures() throws InterruptedException {
+ final int numberOfLockers = Math.max(2, Math.min(12, Runtime.getRuntime().availableProcessors())) / 2;
+ List lockers = new ArrayList<>();
+ LOGGER.info("Running test with {} locker processes", numberOfLockers);
+ Process lockFileDropper = JavaProcessBuilder.create(LockFileDropper.class).start();
+ for (int i = 0; i < numberOfLockers; i++) {
+ lockers.add(JavaProcessBuilder.create(RepeatedAffinityLocker.class)
+ .withJvmArguments("-Djava.io.tmpdir=" + folder.getRoot().getAbsolutePath())
+ .withProgramArguments("last", "30", "2").start());
+ }
+ for (int i = 0; i < numberOfLockers; i++) {
+ final Process process = lockers.get(i);
+ waitForProcessToEnd(20, "Locking process", process);
+ }
+ lockFileDropper.destroy();
+ waitForProcessToEnd(5, "Lock file droppper", lockFileDropper);
+ }
+
+ @Test
+ public void shouldBeAbleToAcquireLockLeftByOtherProcess() throws InterruptedException {
+ final Process process = JavaProcessBuilder.create(AffinityLockerThatDoesNotReleaseProcess.class)
+ .withJvmArguments("-Djava.io.tmpdir=" + folder.getRoot().getAbsolutePath())
+ .withProgramArguments("last").start();
+ waitForProcessToEnd(5, "Locking process", process);
+ // We should be able to acquire the lock despite the other process not explicitly releasing it
+ try (final AffinityLock acquired = AffinityLock.acquireLock("last")) {
+ assertEquals(AffinityLock.PROCESSORS - 1, acquired.cpuId());
+ }
+ }
+
+ private void waitForProcessToEnd(int timeToWaitSeconds, String processDescription, Process process) throws InterruptedException {
+ waitForProcessToEnd(timeToWaitSeconds, processDescription, process, true);
+ }
+
+ private void waitForProcessToEnd(int timeToWaitSeconds, String processDescription, Process process, boolean checkExitCode) throws InterruptedException {
+ if (!process.waitFor(timeToWaitSeconds, TimeUnit.SECONDS)) {
+ JavaProcessBuilder.printProcessOutput(processDescription, process);
+ fail(processDescription + " didn't end in time");
+ }
+ if (checkExitCode && process.exitValue() != 0) {
+ JavaProcessBuilder.printProcessOutput(processDescription, process);
+ fail(processDescription + " failed, see output above (exit value " + process.exitValue() + ")");
+ }
+ }
+
+ /**
+ * Repeatedly acquires and releases a lock on the specified core
+ */
+ static class RepeatedAffinityLocker implements Callable {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RepeatedAffinityLocker.class);
+ private static final long PID = LockCheck.getPID();
+ private final int iterations;
+ private final String cpuIdToLock;
+
+ RepeatedAffinityLocker(String cpuIdToLock, int iterations) {
+ this.iterations = iterations;
+ this.cpuIdToLock = cpuIdToLock;
+ }
+
+ public static void main(String[] args) throws InterruptedException, ExecutionException {
+ String cpuIdToLock = args[0];
+ int iterations = Integer.parseInt(args[1]);
+ int threads = Integer.parseInt(args[2]);
+
+ LOGGER.info("Acquiring lock with {} threads, {} iterations", threads, iterations);
+ ExecutorService executorService = Executors.newFixedThreadPool(threads);
+ final List> futures = executorService.invokeAll(IntStream.range(0, threads)
+ .mapToObj(tid -> new RepeatedAffinityLocker(cpuIdToLock, iterations))
+ .collect(Collectors.toList()));
+ for (Future future : futures) {
+ future.get();
+ }
+ executorService.shutdown();
+ if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Executor service didn't shut down");
+ }
+ }
+
+ @Override
+ public Void call() throws Exception {
+ for (int i = 0; i < iterations; i++) {
+ LOGGER.info("******* Starting iteration {} at {}", i, LocalDateTime.now());
+ try (final AffinityLock affinityLock = AffinityLock.acquireLock(cpuIdToLock)) {
+ if (affinityLock.isAllocated()) {
+ assertLockFileContainsMyPid(affinityLock);
+ Thread.sleep(ThreadLocalRandom.current().nextInt(50));
+ assertLockFileContainsMyPid(affinityLock);
+ } else {
+ LOGGER.info("Couldn't get a lock");
+ }
+ }
+ }
+ return null;
+ }
+
+ private void assertLockFileContainsMyPid(AffinityLock affinityLock) throws IOException {
+ int lockPID = LockCheck.getProcessForCpu(affinityLock.cpuId());
+ if (lockPID != PID) {
+ throw new IllegalStateException(format("PID in lock file is not mine (lockPID=%d, myPID=%d)", lockPID, PID));
+ }
+ }
+ }
+
+ /**
+ * Acquires a lock on the specified CPU, holds it until interrupted
+ */
+ static class AffinityLockerProcess {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AffinityLockerProcess.class);
+
+ public static void main(String[] args) {
+ String cpuIdToLock = args[0];
+
+ try (final AffinityLock affinityLock = AffinityLock.acquireLock(cpuIdToLock)) {
+ LOGGER.info("Got affinity lock {} at {}, CPU={}", affinityLock, LocalDateTime.now(), affinityLock.cpuId());
+ Thread.sleep(Integer.MAX_VALUE);
+ LOGGER.error("Woke from sleep? this should never happen");
+ } catch (InterruptedException e) {
+ // expected, just end
+ LOGGER.info("Interrupted at {} lock is released", LocalDateTime.now());
+ }
+ }
+ }
+
+ /**
+ * Acquires a lock then ends
+ */
+ static class AffinityLockerThatDoesNotReleaseProcess {
+ private static final Logger LOGGER = LoggerFactory.getLogger(AffinityLockerThatDoesNotReleaseProcess.class);
+
+ public static void main(String[] args) {
+ String cpuIdToLock = args[0];
+
+ final AffinityLock affinityLock = AffinityLock.acquireLock(cpuIdToLock);
+ LOGGER.info("Got affinity lock {} at {}, CPU={}", affinityLock, LocalDateTime.now(), affinityLock.cpuId());
+ }
+ }
+
+ /**
+ * Simulate failing processes by repeatedly dropping lock files
+ * with PIDs of non-existent processes
+ */
+ static class LockFileDropper {
+
+ public static void main(String[] args) throws InterruptedException, IOException {
+ while (Thread.currentThread().isInterrupted()) {
+ for (int cpu = 0; cpu < AffinityLock.PROCESSORS; cpu++) {
+ try {
+ File lockFile = toFile(cpu);
+ try (final FileChannel fc = FileChannel.open(lockFile.toPath(), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
+ final long maxValue = Long.MAX_VALUE; // a PID that never exists
+ ByteBuffer buffer = ByteBuffer.wrap((maxValue + "\n").getBytes(StandardCharsets.UTF_8));
+ while (buffer.hasRemaining()) {
+ //noinspection ResultOfMethodCallIgnored
+ fc.write(buffer);
+ }
+ }
+ } catch (FileAlreadyExistsException e) {
+ LOGGER.info("Failed, trying again");
+ }
+ //noinspection BusyWait
+ Thread.sleep(ThreadLocalRandom.current().nextInt(50));
+ }
+ }
+ }
+
+ @NotNull
+ static File toFile(int id) {
+ return new TestFileLockBasedLockChecker().doToFile(id);
+ }
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/impl/AbstractAffinityImplTest.java b/affinity/src/test/java/net/openhft/affinity/impl/AbstractAffinityImplTest.java
index b21a6a14b..6c1cc5fbf 100644
--- a/affinity/src/test/java/net/openhft/affinity/impl/AbstractAffinityImplTest.java
+++ b/affinity/src/test/java/net/openhft/affinity/impl/AbstractAffinityImplTest.java
@@ -1,46 +1,31 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
+import net.openhft.affinity.BaseAffinityTest;
import net.openhft.affinity.IAffinity;
import org.junit.After;
-import org.junit.Ignore;
import org.junit.Test;
import java.util.BitSet;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* @author cheremin
* @since 29.12.11, 20:25
*/
-public abstract class AbstractAffinityImplTest {
+public abstract class AbstractAffinityImplTest extends BaseAffinityTest {
- protected static final int CORES = Runtime.getRuntime().availableProcessors();
- protected static final BitSet CORES_MASK = new BitSet(CORES);
+ private static final int CORES = Runtime.getRuntime().availableProcessors();
+ private static final BitSet CORES_MASK = new BitSet(CORES);
- static
- {
+ static {
CORES_MASK.set(0, CORES, true);
}
- public abstract IAffinity getImpl();
+ protected abstract IAffinity getImpl();
@Test
public void getAffinityCompletesGracefully() {
@@ -50,15 +35,12 @@ public void getAffinityCompletesGracefully() {
@Test
public void getAffinityReturnsValidValue() {
final BitSet affinity = getImpl().getAffinity();
- assertTrue(
- "Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty",
- !affinity.isEmpty()
- );
+ assertFalse("Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty", affinity.isEmpty());
final long allCoresMask = (1L << CORES) - 1;
assertTrue(
"Affinity mask " + Utilities.toBinaryString(affinity) + " must be <=(2^" + CORES + "-1 = " + allCoresMask + ")",
- affinity.length() <= CORES_MASK.length()
- );
+ affinity.length() <= CORES_MASK.length()
+ );
}
@Test
@@ -69,11 +51,9 @@ public void setAffinityCompletesGracefully() {
}
@Test
- @Ignore("TODO FIX")
public void getAffinityReturnsValuePreviouslySet() {
final IAffinity impl = getImpl();
- final int cores = CORES;
- for (int core = 0; core < cores; core++) {
+ for (int core = 0; core < CORES; core++) {
final BitSet mask = new BitSet();
mask.set(core, true);
getAffinityReturnsValuePreviouslySet(impl, mask);
diff --git a/affinity/src/test/java/net/openhft/affinity/impl/CpuInfoLayoutMappingTest.java b/affinity/src/test/java/net/openhft/affinity/impl/CpuInfoLayoutMappingTest.java
new file mode 100644
index 000000000..7687f6967
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/impl/CpuInfoLayoutMappingTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.impl;
+
+import net.openhft.affinity.BaseAffinityTest;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.Assert.assertEquals;
+
+public class CpuInfoLayoutMappingTest extends BaseAffinityTest {
+
+ @Test
+ public void verifyI7CpuInfoMapping() throws IOException {
+ final InputStream i7 = getClass().getClassLoader().getResourceAsStream("i7.cpuinfo");
+ VanillaCpuLayout vcl = VanillaCpuLayout.fromCpuInfo(i7);
+ assertEquals("" +
+ "0: CpuInfo{socketId=0, coreId=0, threadId=0}\n" +
+ "1: CpuInfo{socketId=0, coreId=1, threadId=0}\n" +
+ "2: CpuInfo{socketId=0, coreId=2, threadId=0}\n" +
+ "3: CpuInfo{socketId=0, coreId=3, threadId=0}\n" +
+ "4: CpuInfo{socketId=0, coreId=0, threadId=1}\n" +
+ "5: CpuInfo{socketId=0, coreId=1, threadId=1}\n" +
+ "6: CpuInfo{socketId=0, coreId=2, threadId=1}\n" +
+ "7: CpuInfo{socketId=0, coreId=3, threadId=1}\n",
+ vcl.toString());
+ }
+}
+
diff --git a/affinity/src/test/java/net/openhft/affinity/impl/LinuxJNAAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/impl/LinuxJNAAffinityTest.java
index c58854b9a..cf0db12bd 100644
--- a/affinity/src/test/java/net/openhft/affinity/impl/LinuxJNAAffinityTest.java
+++ b/affinity/src/test/java/net/openhft/affinity/impl/LinuxJNAAffinityTest.java
@@ -1,23 +1,9 @@
/*
- *
- * * Copyright (C) 2016 higherfrequencytrading.com
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU Lesser General Public License as published by
- * * the Free Software Foundation, either version 3 of the License.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU Lesser General Public License for more details.
- * *
- * * You should have received a copy of the GNU Lesser General Public License
- * * along with this program. If not, see .
- *
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
+import net.openhft.affinity.BaseAffinityTest;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -26,10 +12,10 @@
import static org.junit.Assert.assertEquals;
-/**
- * Created by peter on 23/03/16.
+/*
+ * Created by Peter Lawrey on 23/03/16.
*/
-public class LinuxJNAAffinityTest {
+public class LinuxJNAAffinityTest extends BaseAffinityTest {
@BeforeClass
public static void checkJniLibraryPresent() {
Assume.assumeTrue(LinuxJNAAffinity.LOADED);
@@ -53,5 +39,4 @@ public void LinuxJNA() {
affinity.set(0, nbits);
LinuxJNAAffinity.INSTANCE.setAffinity(affinity);
}
-
}
diff --git a/affinity/src/test/java/net/openhft/affinity/impl/NativeAffinityImpTest.java b/affinity/src/test/java/net/openhft/affinity/impl/NativeAffinityImpTest.java
index 09dafc7d0..2b9cbb73a 100644
--- a/affinity/src/test/java/net/openhft/affinity/impl/NativeAffinityImpTest.java
+++ b/affinity/src/test/java/net/openhft/affinity/impl/NativeAffinityImpTest.java
@@ -1,6 +1,9 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
package net.openhft.affinity.impl;
-import net.openhft.affinity.AffinitySupport;
+import net.openhft.affinity.Affinity;
import net.openhft.affinity.IAffinity;
import org.junit.Assume;
import org.junit.BeforeClass;
@@ -9,11 +12,10 @@
import static org.junit.Assert.assertTrue;
-/**
+/*
* Created by andre on 22/06/15.
*/
-public class NativeAffinityImpTest extends AbstractAffinityImplTest
-{
+public class NativeAffinityImpTest extends AbstractAffinityImplTest {
@BeforeClass
public static void checkJniLibraryPresent() {
Assume.assumeTrue(NativeAffinity.LOADED);
@@ -21,8 +23,7 @@ public static void checkJniLibraryPresent() {
}
@Override
- public IAffinity getImpl()
- {
+ public IAffinity getImpl() {
return NativeAffinity.INSTANCE;
}
@@ -30,7 +31,7 @@ public IAffinity getImpl()
public void testGettid() {
System.out.println("pid=" + getImpl().getProcessId());
System.out.println("tid=" + getImpl().getThreadId());
- AffinitySupport.setThreadId();
+ Affinity.setThreadId();
for (int j = 0; j < 3; j++) {
final int runs = 100000;
@@ -38,7 +39,9 @@ public void testGettid() {
long time = 0;
for (int i = 0; i < runs; i++) {
long start = System.nanoTime();
- tid = Thread.currentThread().getId();
+ @SuppressWarnings("deprecation")
+ long tid0 = Thread.currentThread().getId();
+ tid = tid0;
time += System.nanoTime() - start;
assertTrue(tid > 0);
assertTrue(tid < 1 << 16);
diff --git a/affinity/src/test/java/net/openhft/affinity/impl/PosixJNAAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/impl/PosixJNAAffinityTest.java
index d2aa694ec..180506e70 100644
--- a/affinity/src/test/java/net/openhft/affinity/impl/PosixJNAAffinityTest.java
+++ b/affinity/src/test/java/net/openhft/affinity/impl/PosixJNAAffinityTest.java
@@ -1,29 +1,15 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
import net.openhft.affinity.Affinity;
-import net.openhft.affinity.AffinitySupport;
import net.openhft.affinity.IAffinity;
-import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
/**
* @author peter.lawrey
@@ -31,7 +17,8 @@
public class PosixJNAAffinityTest extends AbstractAffinityImplTest {
@BeforeClass
public static void checkJniLibraryPresent() {
- Assume.assumeTrue("linux".equalsIgnoreCase(System.getProperty("os.name")));
+ assumeTrue("TODO FIX JNA library is not used, but the test is flaky", false);
+ assumeTrue("linux".equalsIgnoreCase(System.getProperty("os.name")));
}
@Override
@@ -43,7 +30,7 @@ public IAffinity getImpl() {
public void testGettid() {
System.out.println("pid=" + getImpl().getProcessId());
System.out.println("tid=" + getImpl().getThreadId());
- AffinitySupport.setThreadId();
+ Affinity.setThreadId();
for (int j = 0; j < 3; j++) {
final int runs = 100000;
@@ -51,10 +38,12 @@ public void testGettid() {
long time = 0;
for (int i = 0; i < runs; i++) {
long start = System.nanoTime();
- tid = Thread.currentThread().getId();
+ @SuppressWarnings("deprecation")
+ long tid0 = Thread.currentThread().getId();
+ tid = tid0;
time += System.nanoTime() - start;
assertTrue(tid > 0);
- assertTrue(tid < 1 << 16);
+ assertTrue(tid < 1 << 24);
}
System.out.printf("gettid took an average of %,d ns, tid=%d%n", time / runs, tid);
}
diff --git a/affinity/src/test/java/net/openhft/affinity/impl/UtilitiesTest.java b/affinity/src/test/java/net/openhft/affinity/impl/UtilitiesTest.java
new file mode 100644
index 000000000..80eed46ea
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/impl/UtilitiesTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.impl;
+
+import net.openhft.affinity.BaseAffinityTest;
+import org.junit.Test;
+
+import java.util.BitSet;
+
+import static org.junit.Assert.assertEquals;
+
+public class UtilitiesTest extends BaseAffinityTest {
+
+ private static String hex(BitSet set, int... bits) {
+ set.clear();
+ for (int b : bits) {
+ set.set(b);
+ }
+ return Utilities.toHexString(set);
+ }
+
+ private static String bin(BitSet set, int... bits) {
+ set.clear();
+ for (int b : bits) {
+ set.set(b);
+ }
+ return Utilities.toBinaryString(set);
+ }
+
+ @Test
+ public void testToHexString() {
+ BitSet set = new BitSet();
+ assertEquals("", hex(set));
+ assertEquals("1", hex(set, 0));
+ assertEquals("10", hex(set, 4));
+ assertEquals(Long.toHexString(1L << 63), hex(set, 63));
+ assertEquals("01", hex(set, 64));
+ assertEquals("101", hex(set, 0, 128));
+ }
+
+ @Test
+ public void testToBinaryString() {
+ BitSet set = new BitSet();
+ assertEquals("", bin(set));
+ assertEquals("1", bin(set, 0));
+ assertEquals("10000", bin(set, 4));
+ assertEquals(Long.toBinaryString(1L << 63), bin(set, 63));
+ assertEquals("01", bin(set, 64));
+ assertEquals("101", bin(set, 0, 128));
+ }
+}
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..c356f3c81
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPairTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.impl;
+
+import net.openhft.affinity.BaseAffinityTest;
+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 extends BaseAffinityTest {
+
+ @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));
+ }
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPropertiesParseTest.java b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPropertiesParseTest.java
new file mode 100644
index 000000000..7452c060f
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPropertiesParseTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.impl;
+
+import net.openhft.affinity.BaseAffinityTest;
+import org.junit.Test;
+
+import java.io.InputStream;
+
+import static org.junit.Assert.assertEquals;
+
+public class VanillaCpuLayoutPropertiesParseTest extends BaseAffinityTest {
+
+ @Test
+ public void testCountsI7() throws Exception {
+ InputStream is = getClass().getClassLoader().getResourceAsStream("i7.properties");
+ VanillaCpuLayout vcl = VanillaCpuLayout.fromProperties(is);
+ assertEquals(8, vcl.cpus());
+ assertEquals(1, vcl.sockets());
+ assertEquals(4, vcl.coresPerSocket());
+ assertEquals(2, vcl.threadsPerCore());
+ }
+
+ @Test
+ public void testCountsDualXeon() throws Exception {
+ InputStream is = getClass().getClassLoader().getResourceAsStream("dual.xeon.properties");
+ VanillaCpuLayout vcl = VanillaCpuLayout.fromProperties(is);
+ assertEquals(4, vcl.cpus());
+ assertEquals(2, vcl.sockets());
+ assertEquals(1, vcl.coresPerSocket());
+ assertEquals(2, vcl.threadsPerCore());
+ }
+
+ @Test
+ public void testCountsDualE5405() throws Exception {
+ InputStream is = getClass().getClassLoader().getResourceAsStream("dual.E5405.properties");
+ VanillaCpuLayout vcl = VanillaCpuLayout.fromProperties(is);
+ assertEquals(8, vcl.cpus());
+ assertEquals(2, vcl.sockets());
+ assertEquals(4, vcl.coresPerSocket());
+ assertEquals(1, vcl.threadsPerCore());
+ }
+
+ @Test
+ public void testCountsI3() throws Exception {
+ InputStream is = getClass().getClassLoader().getResourceAsStream("i3.properties");
+ VanillaCpuLayout vcl = VanillaCpuLayout.fromProperties(is);
+ assertEquals(4, vcl.cpus());
+ assertEquals(1, vcl.sockets());
+ assertEquals(2, vcl.coresPerSocket());
+ assertEquals(2, vcl.threadsPerCore());
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutTest.java b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutTest.java
index ae26737ec..0e90d5069 100644
--- a/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutTest.java
+++ b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutTest.java
@@ -1,32 +1,20 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.affinity.impl;
+import net.openhft.affinity.BaseAffinityTest;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
-import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertEquals;
/**
* @author peter.lawrey
*/
-public class VanillaCpuLayoutTest {
+public class VanillaCpuLayoutTest extends BaseAffinityTest {
@Test
public void testFromCpuInfoI7() throws IOException {
diff --git a/affinity/src/test/java/net/openhft/affinity/impl/VersionHelperTest.java b/affinity/src/test/java/net/openhft/affinity/impl/VersionHelperTest.java
new file mode 100644
index 000000000..a7f21afff
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/impl/VersionHelperTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.impl;
+
+import net.openhft.affinity.BaseAffinityTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class VersionHelperTest extends BaseAffinityTest {
+
+ @Test
+ public void isSameOrNewerTest() {
+ final VersionHelper v0 = new VersionHelper(0, 0, 0);
+ final VersionHelper v2_6 = new VersionHelper(2, 6, 0);
+ final VersionHelper v4_1 = new VersionHelper(4, 1, 1);
+ final VersionHelper v4_9 = new VersionHelper(4, 9, 0);
+ final VersionHelper v9_9 = new VersionHelper(9, 9, 9);
+
+ VersionHelper[] versions = new VersionHelper[]{v0, v2_6, v4_1, v4_9, v9_9};
+
+ for (int i = 0; i < versions.length; i++) {
+ for (int j = 0; j < versions.length; j++) {
+ Assert.assertEquals(String.format("expected %s.isSameOrNewer(%s) to be %b", versions[i], versions[j], i >= j),
+ i >= j, versions[i].isSameOrNewer(versions[j]));
+ }
+ }
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileLockBasedLockChecker.java b/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileLockBasedLockChecker.java
new file mode 100644
index 000000000..56b47d9e1
--- /dev/null
+++ b/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileLockBasedLockChecker.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package net.openhft.affinity.testimpl;
+
+import net.openhft.affinity.lockchecker.FileLockBasedLockChecker;
+
+import java.io.File;
+
+public class TestFileLockBasedLockChecker extends FileLockBasedLockChecker {
+
+ public File doToFile(int cpu) {
+ return toFile(cpu);
+ }
+}
diff --git a/affinity/src/test/java/net/openhft/ticker/impl/JNIClockTest.java b/affinity/src/test/java/net/openhft/ticker/impl/JNIClockTest.java
index c8213e149..359c06e02 100644
--- a/affinity/src/test/java/net/openhft/ticker/impl/JNIClockTest.java
+++ b/affinity/src/test/java/net/openhft/ticker/impl/JNIClockTest.java
@@ -1,31 +1,19 @@
/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package net.openhft.ticker.impl;
import net.openhft.affinity.Affinity;
+import net.openhft.affinity.BaseAffinityTest;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
-/**
- * Created by peter on 13/07/15.
+/*
+ * Created by Peter Lawrey on 13/07/15.
*/
-public class JNIClockTest {
+public class JNIClockTest extends BaseAffinityTest {
@Test
@Ignore("TODO Fix")
@@ -77,4 +65,4 @@ public void testJitter() {
System.out.println(((long) (clock.toMicros(time[i]) * 10)) / 10.0 + ", " + ((long) (clock.toMicros(length[i]) * 10) / 10.0));
}
}
-}
\ No newline at end of file
+}
diff --git a/affinity/src/test/java/org/junit/Assert.java b/affinity/src/test/java/org/junit/Assert.java
deleted file mode 100644
index a0325c7bd..000000000
--- a/affinity/src/test/java/org/junit/Assert.java
+++ /dev/null
@@ -1,885 +0,0 @@
-/*
- * Copyright (C) 2015 higherfrequencytrading.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package org.junit;
-
-import org.hamcrest.Matcher;
-import org.hamcrest.MatcherAssert;
-import org.junit.internal.ArrayComparisonFailure;
-import org.junit.internal.ExactComparisonCriteria;
-import org.junit.internal.InexactComparisonCriteria;
-
-/**
- * TODO replace with 4.12 when it is released with a simple bug fix.
- *
- * A set of assertion methods useful for writing tests. Only failed assertions
- * are recorded. These methods can be used directly:
- * Assert.assertEquals(...), however, they read better if they
- * are referenced through static import:
- *
- *
- * import static org.junit.Assert.*;
- * ...
- * assertEquals(...);
- *
- *
- * @see AssertionError
- * @since 4.0
- */
-public class Assert {
- /**
- * Protect constructor since it is a static only class
- */
- protected Assert() {
- }
-
- /**
- * Asserts that a condition is true. If it isn't it throws an
- * {@link AssertionError} with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param condition condition to be checked
- */
- static public void assertTrue(String message, boolean condition) {
- if (!condition) {
- fail(message);
- }
- }
-
- /**
- * Asserts that a condition is true. If it isn't it throws an
- * {@link AssertionError} without a message.
- *
- * @param condition condition to be checked
- */
- static public void assertTrue(boolean condition) {
- assertTrue(null, condition);
- }
-
- /**
- * Asserts that a condition is false. If it isn't it throws an
- * {@link AssertionError} with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param condition condition to be checked
- */
- static public void assertFalse(String message, boolean condition) {
- assertTrue(message, !condition);
- }
-
- /**
- * Asserts that a condition is false. If it isn't it throws an
- * {@link AssertionError} without a message.
- *
- * @param condition condition to be checked
- */
- static public void assertFalse(boolean condition) {
- assertFalse(null, condition);
- }
-
- /**
- * Fails a test with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @see AssertionError
- */
- static public void fail(String message) {
- if (message == null) {
- throw new AssertionError();
- }
- throw new AssertionError(message);
- }
-
- /**
- * Fails a test with no message.
- */
- static public void fail() {
- fail(null);
- }
-
- /**
- * Asserts that two objects are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message. If
- * expected and actual are null,
- * they are considered equal.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expected expected value
- * @param actual actual value
- */
- static public void assertEquals(String message, Object expected,
- Object actual) {
- if (equalsRegardingNull(expected, actual)) {
- return;
-
- } else if (expected instanceof String && actual instanceof String) {
- String cleanMessage = message == null ? "" : message;
- throw new ComparisonFailure(cleanMessage, (String) expected,
- (String) actual);
-
- } else {
- failNotEquals(message, expected, actual);
- }
- }
-
- private static boolean equalsRegardingNull(Object expected, Object actual) {
- if (expected == null) {
- return actual == null;
- }
-
- return isEquals(expected, actual);
- }
-
- private static boolean isEquals(Object expected, Object actual) {
- return expected.equals(actual);
- }
-
- /**
- * Asserts that two objects are equal. If they are not, an
- * {@link AssertionError} without a message is thrown. If
- * expected and actual are null,
- * they are considered equal.
- *
- * @param expected expected value
- * @param actual the value to check against expected
- */
- static public void assertEquals(Object expected, Object actual) {
- assertEquals(null, expected, actual);
- }
-
- /**
- * Asserts that two objects are not equals. If they are, an
- * {@link AssertionError} is thrown with the given message. If
- * first and second are null,
- * they are considered equal.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param first first value to check
- * @param second the value to check against first
- */
- static public void assertNotEquals(String message, Object first,
- Object second) {
- if (equalsRegardingNull(first, second)) {
- failEquals(message, first);
- }
- }
-
- /**
- * Asserts that two objects are not equals. If they are, an
- * {@link AssertionError} without a message is thrown. If
- * first and second are null,
- * they are considered equal.
- *
- * @param first first value to check
- * @param second the value to check against first
- */
- static public void assertNotEquals(Object first, Object second) {
- assertNotEquals(null, first, second);
- }
-
- private static void failEquals(String message, Object actual) {
- String formatted = "Values should be different. ";
- if (message != null) {
- formatted = message + ". ";
- }
-
- formatted += "Actual: " + actual;
- fail(formatted);
- }
-
- /**
- * Asserts that two longs are not equals. If they are, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param first first value to check
- * @param second the value to check against first
- */
- static public void assertNotEquals(String message, long first, long second) {
- assertNotEquals(message, (Long) first, (Long) second);
- }
-
- /**
- * Asserts that two longs are not equals. If they are, an
- * {@link AssertionError} without a message is thrown.
- *
- * @param first first value to check
- * @param second the value to check against first
- */
- static public void assertNotEquals(long first, long second) {
- assertNotEquals(null, first, second);
- }
-
- /**
- * Asserts that two doubles or floats are not equal to within a positive delta.
- * If they are, an {@link AssertionError} is thrown with the given
- * message. If the expected value is infinity then the delta value is
- * ignored. NaNs are considered equal:
- * assertNotEquals(Double.NaN, Double.NaN, *) fails
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param first first value to check
- * @param second the value to check against first
- * @param delta the maximum delta between expected and
- * actual for which both numbers are still
- * considered equal.
- */
- static public void assertNotEquals(String message, double first,
- double second, double delta) {
- if (!doubleIsDifferent(first, second, delta)) {
- failEquals(message, new Double(first));
- }
- }
-
- /**
- * Asserts that two doubles or floats are not equal to within a positive delta.
- * If they are, an {@link AssertionError} is thrown. If the expected
- * value is infinity then the delta value is ignored.NaNs are considered
- * equal: assertNotEquals(Double.NaN, Double.NaN, *) fails
- *
- * @param first first value to check
- * @param second the value to check against first
- * @param delta the maximum delta between expected and
- * actual for which both numbers are still
- * considered equal.
- */
- static public void assertNotEquals(double first, double second, double delta) {
- assertNotEquals(null, first, second, delta);
- }
-
- /**
- * Asserts that two object arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message. If
- * expecteds and actuals are null,
- * they are considered equal.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds Object array or array of arrays (multi-dimensional array) with
- * expected values.
- * @param actuals Object array or array of arrays (multi-dimensional array) with
- * actual values
- */
- public static void assertArrayEquals(String message, Object[] expecteds,
- Object[] actuals) throws ArrayComparisonFailure {
- internalArrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two object arrays are equal. If they are not, an
- * {@link AssertionError} is thrown. If expected and
- * actual are null, they are considered
- * equal.
- *
- * @param expecteds Object array or array of arrays (multi-dimensional array) with
- * expected values
- * @param actuals Object array or array of arrays (multi-dimensional array) with
- * actual values
- */
- public static void assertArrayEquals(Object[] expecteds, Object[] actuals) {
- assertArrayEquals(null, expecteds, actuals);
- }
-
- /**
- * Asserts that two byte arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds byte array with expected values.
- * @param actuals byte array with actual values
- */
- public static void assertArrayEquals(String message, byte[] expecteds,
- byte[] actuals) throws ArrayComparisonFailure {
- internalArrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two byte arrays are equal. If they are not, an
- * {@link AssertionError} is thrown.
- *
- * @param expecteds byte array with expected values.
- * @param actuals byte array with actual values
- */
- public static void assertArrayEquals(byte[] expecteds, byte[] actuals) {
- assertArrayEquals(null, expecteds, actuals);
- }
-
- /**
- * Asserts that two char arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds char array with expected values.
- * @param actuals char array with actual values
- */
- public static void assertArrayEquals(String message, char[] expecteds,
- char[] actuals) throws ArrayComparisonFailure {
- internalArrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two char arrays are equal. If they are not, an
- * {@link AssertionError} is thrown.
- *
- * @param expecteds char array with expected values.
- * @param actuals char array with actual values
- */
- public static void assertArrayEquals(char[] expecteds, char[] actuals) {
- assertArrayEquals(null, expecteds, actuals);
- }
-
- /**
- * Asserts that two short arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds short array with expected values.
- * @param actuals short array with actual values
- */
- public static void assertArrayEquals(String message, short[] expecteds,
- short[] actuals) throws ArrayComparisonFailure {
- internalArrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two short arrays are equal. If they are not, an
- * {@link AssertionError} is thrown.
- *
- * @param expecteds short array with expected values.
- * @param actuals short array with actual values
- */
- public static void assertArrayEquals(short[] expecteds, short[] actuals) {
- assertArrayEquals(null, expecteds, actuals);
- }
-
- /**
- * Asserts that two int arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds int array with expected values.
- * @param actuals int array with actual values
- */
- public static void assertArrayEquals(String message, int[] expecteds,
- int[] actuals) throws ArrayComparisonFailure {
- internalArrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two int arrays are equal. If they are not, an
- * {@link AssertionError} is thrown.
- *
- * @param expecteds int array with expected values.
- * @param actuals int array with actual values
- */
- public static void assertArrayEquals(int[] expecteds, int[] actuals) {
- assertArrayEquals(null, expecteds, actuals);
- }
-
- /**
- * Asserts that two long arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds long array with expected values.
- * @param actuals long array with actual values
- */
- public static void assertArrayEquals(String message, long[] expecteds,
- long[] actuals) throws ArrayComparisonFailure {
- internalArrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two long arrays are equal. If they are not, an
- * {@link AssertionError} is thrown.
- *
- * @param expecteds long array with expected values.
- * @param actuals long array with actual values
- */
- public static void assertArrayEquals(long[] expecteds, long[] actuals) {
- assertArrayEquals(null, expecteds, actuals);
- }
-
- /**
- * Asserts that two double arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds double array with expected values.
- * @param actuals double array with actual values
- */
- public static void assertArrayEquals(String message, double[] expecteds,
- double[] actuals, double delta) throws ArrayComparisonFailure {
- new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two double arrays are equal. If they are not, an
- * {@link AssertionError} is thrown.
- *
- * @param expecteds double array with expected values.
- * @param actuals double array with actual values
- */
- public static void assertArrayEquals(double[] expecteds, double[] actuals, double delta) {
- assertArrayEquals(null, expecteds, actuals, delta);
- }
-
- /**
- * Asserts that two float arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds float array with expected values.
- * @param actuals float array with actual values
- */
- public static void assertArrayEquals(String message, float[] expecteds,
- float[] actuals, float delta) throws ArrayComparisonFailure {
- new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two float arrays are equal. If they are not, an
- * {@link AssertionError} is thrown.
- *
- * @param expecteds float array with expected values.
- * @param actuals float array with actual values
- */
- public static void assertArrayEquals(float[] expecteds, float[] actuals, float delta) {
- assertArrayEquals(null, expecteds, actuals, delta);
- }
-
- /**
- * Asserts that two object arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message. If
- * expecteds and actuals are null,
- * they are considered equal.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds Object array or array of arrays (multi-dimensional array) with
- * expected values.
- * @param actuals Object array or array of arrays (multi-dimensional array) with
- * actual values
- */
- private static void internalArrayEquals(String message, Object expecteds,
- Object actuals) throws ArrayComparisonFailure {
- new ExactComparisonCriteria().arrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two doubles are equal to within a positive delta.
- * If they are not, an {@link AssertionError} is thrown with the given
- * message. If the expected value is infinity then the delta value is
- * ignored. NaNs are considered equal:
- * assertEquals(Double.NaN, Double.NaN, *) passes
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expected expected value
- * @param actual the value to check against expected
- * @param delta the maximum delta between expected and
- * actual for which both numbers are still
- * considered equal.
- */
- static public void assertEquals(String message, double expected,
- double actual, double delta) {
- if (doubleIsDifferent(expected, actual, delta)) {
- failNotEquals(message, new Double(expected), new Double(actual));
- }
- }
-
- /**
- * Asserts that two floats are equal to within a positive delta.
- * If they are not, an {@link AssertionError} is thrown with the given
- * message. If the expected value is infinity then the delta value is
- * ignored. NaNs are considered equal:
- * assertEquals(Float.NaN, Float.NaN, *) passes
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expected expected value
- * @param actual the value to check against expected
- * @param delta the maximum delta between expected and
- * actual for which both numbers are still
- * considered equal.
- */
- static public void assertEquals(String message, float expected,
- float actual, float delta) {
- if (Float.compare(expected, actual) == 0) {
- return;
- }
- if (!(Math.abs(expected - actual) <= delta)) {
- failNotEquals(message, new Float(expected), new Float(actual));
- }
- }
-
- static private boolean doubleIsDifferent(double d1, double d2, double delta) {
- if (Double.compare(d1, d2) == 0) {
- return false;
- }
- return (Math.abs(d1 - d2) > delta);
-
- }
-
- /**
- * Asserts that two longs are equal. If they are not, an
- * {@link AssertionError} is thrown.
- *
- * @param expected expected long value.
- * @param actual actual long value
- */
- static public void assertEquals(long expected, long actual) {
- if (expected != actual)
- assertEquals(null, expected, actual);
- }
-
- /**
- * Asserts that two longs are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expected long expected value.
- * @param actual long actual value
- */
- static public void assertEquals(String message, long expected, long actual) {
- assertEquals(message, (Long) expected, (Long) actual);
- }
-
- /**
- * @deprecated Use
- * assertEquals(double expected, double actual, double delta)
- * instead
- */
- @Deprecated
- static public void assertEquals(double expected, double actual) {
- assertEquals(null, expected, actual);
- }
-
- /**
- * @deprecated Use
- * assertEquals(String message, double expected, double actual, double delta)
- * instead
- */
- @Deprecated
- static public void assertEquals(String message, double expected,
- double actual) {
- fail("Use assertEquals(expected, actual, delta) to compare floating-point numbers");
- }
-
- /**
- * Asserts that two doubles are equal to within a positive delta.
- * If they are not, an {@link AssertionError} is thrown. If the expected
- * value is infinity then the delta value is ignored.NaNs are considered
- * equal: assertEquals(Double.NaN, Double.NaN, *) passes
- *
- * @param expected expected value
- * @param actual the value to check against expected
- * @param delta the maximum delta between expected and
- * actual for which both numbers are still
- * considered equal.
- */
- static public void assertEquals(double expected, double actual, double delta) {
- assertEquals(null, expected, actual, delta);
- }
-
- /**
- * Asserts that two floats are equal to within a positive delta.
- * If they are not, an {@link AssertionError} is thrown. If the expected
- * value is infinity then the delta value is ignored. NaNs are considered
- * equal: assertEquals(Float.NaN, Float.NaN, *) passes
- *
- * @param expected expected value
- * @param actual the value to check against expected
- * @param delta the maximum delta between expected and
- * actual for which both numbers are still
- * considered equal.
- */
- static public void assertEquals(float expected, float actual, float delta) {
- assertEquals(null, expected, actual, delta);
- }
-
- /**
- * Asserts that an object isn't null. If it is an {@link AssertionError} is
- * thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param object Object to check or null
- */
- static public void assertNotNull(String message, Object object) {
- assertTrue(message, object != null);
- }
-
- /**
- * Asserts that an object isn't null. If it is an {@link AssertionError} is
- * thrown.
- *
- * @param object Object to check or null
- */
- static public void assertNotNull(Object object) {
- assertNotNull(null, object);
- }
-
- /**
- * Asserts that an object is null. If it is not, an {@link AssertionError}
- * is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param object Object to check or null
- */
- static public void assertNull(String message, Object object) {
- if (object == null) {
- return;
- }
- failNotNull(message, object);
- }
-
- /**
- * Asserts that an object is null. If it isn't an {@link AssertionError} is
- * thrown.
- *
- * @param object Object to check or null
- */
- static public void assertNull(Object object) {
- assertNull(null, object);
- }
-
- static private void failNotNull(String message, Object actual) {
- String formatted = "";
- if (message != null) {
- formatted = message + " ";
- }
- fail(formatted + "expected null, but was:<" + actual + ">");
- }
-
- /**
- * Asserts that two objects refer to the same object. If they are not, an
- * {@link AssertionError} is thrown with the given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expected the expected object
- * @param actual the object to compare to expected
- */
- static public void assertSame(String message, Object expected, Object actual) {
- if (expected == actual) {
- return;
- }
- failNotSame(message, expected, actual);
- }
-
- /**
- * Asserts that two objects refer to the same object. If they are not the
- * same, an {@link AssertionError} without a message is thrown.
- *
- * @param expected the expected object
- * @param actual the object to compare to expected
- */
- static public void assertSame(Object expected, Object actual) {
- assertSame(null, expected, actual);
- }
-
- /**
- * Asserts that two objects do not refer to the same object. If they do
- * refer to the same object, an {@link AssertionError} is thrown with the
- * given message.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param unexpected the object you don't expect
- * @param actual the object to compare to unexpected
- */
- static public void assertNotSame(String message, Object unexpected,
- Object actual) {
- if (unexpected == actual) {
- failSame(message);
- }
- }
-
- /**
- * Asserts that two objects do not refer to the same object. If they do
- * refer to the same object, an {@link AssertionError} without a message is
- * thrown.
- *
- * @param unexpected the object you don't expect
- * @param actual the object to compare to unexpected
- */
- static public void assertNotSame(Object unexpected, Object actual) {
- assertNotSame(null, unexpected, actual);
- }
-
- static private void failSame(String message) {
- String formatted = "";
- if (message != null) {
- formatted = message + " ";
- }
- fail(formatted + "expected not same");
- }
-
- static private void failNotSame(String message, Object expected,
- Object actual) {
- String formatted = "";
- if (message != null) {
- formatted = message + " ";
- }
- fail(formatted + "expected same:<" + expected + "> was not:<" + actual
- + ">");
- }
-
- static private void failNotEquals(String message, Object expected,
- Object actual) {
- fail(format(message, expected, actual));
- }
-
- static String format(String message, Object expected, Object actual) {
- String formatted = "";
- if (message != null && !message.equals("")) {
- formatted = message + " ";
- }
- String expectedString = String.valueOf(expected);
- String actualString = String.valueOf(actual);
- if (expectedString.equals(actualString)) {
- return formatted + "expected: "
- + formatClassAndValue(expected, expectedString)
- + " but was: " + formatClassAndValue(actual, actualString);
-
- } else {
- return formatted + "expected:<" + expectedString + "> but was:<"
- + actualString + ">";
- }
- }
-
- private static String formatClassAndValue(Object value, String valueString) {
- String className = value == null ? "null" : value.getClass().getName();
- return className + "<" + valueString + ">";
- }
-
- /**
- * Asserts that two object arrays are equal. If they are not, an
- * {@link AssertionError} is thrown with the given message. If
- * expecteds and actuals are null,
- * they are considered equal.
- *
- * @param message the identifying message for the {@link AssertionError} (null
- * okay)
- * @param expecteds Object array or array of arrays (multi-dimensional array) with
- * expected values.
- * @param actuals Object array or array of arrays (multi-dimensional array) with
- * actual values
- * @deprecated use assertArrayEquals
- */
- @Deprecated
- public static void assertEquals(String message, Object[] expecteds,
- Object[] actuals) {
- assertArrayEquals(message, expecteds, actuals);
- }
-
- /**
- * Asserts that two object arrays are equal. If they are not, an
- * {@link AssertionError} is thrown. If expected and
- * actual are null, they are considered
- * equal.
- *
- * @param expecteds Object array or array of arrays (multi-dimensional array) with
- * expected values
- * @param actuals Object array or array of arrays (multi-dimensional array) with
- * actual values
- * @deprecated use assertArrayEquals
- */
- @Deprecated
- public static void assertEquals(Object[] expecteds, Object[] actuals) {
- assertArrayEquals(expecteds, actuals);
- }
-
- /**
- * Asserts that actual satisfies the condition specified by
- * matcher. If not, an {@link AssertionError} is thrown with
- * information about the matcher and failing value. Example:
- *
- *
- * assertThat(0, is(1)); // fails:
- * // failure message:
- * // expected: is <1>
- * // got value: <0>
- * assertThat(0, is(not(1))) // passes
- *
- *
- * org.hamcrest.Matcher does not currently document the meaning
- * of its type parameter T. This method assumes that a matcher
- * typed as Matcher<T> can be meaningfully applied only
- * to values that could be assigned to a variable of type T.
- *
- * @param the static type accepted by the matcher (this can flag obvious
- * compile-time problems such as {@code assertThat(1, is("a"))}
- * @param actual the computed value being compared
- * @param matcher an expression, built of {@link Matcher}s, specifying allowed
- * values
- * @see org.hamcrest.CoreMatchers
- * @see org.hamcrest.MatcherAssert
- */
- public static void assertThat(T actual, Matcher super T> matcher) {
- assertThat("", actual, matcher);
- }
-
- /**
- * Asserts that actual satisfies the condition specified by
- * matcher. If not, an {@link AssertionError} is thrown with
- * the reason and information about the matcher and failing value. Example:
- *
- *
- * assertThat("Help! Integers don't work", 0, is(1)); // fails:
- * // failure message:
- * // Help! Integers don't work
- * // expected: is <1>
- * // got value: <0>
- * assertThat("Zero is one", 0, is(not(1))) // passes
- *
- *
- * org.hamcrest.Matcher does not currently document the meaning
- * of its type parameter T. This method assumes that a matcher
- * typed as Matcher<T> can be meaningfully applied only
- * to values that could be assigned to a variable of type T.
- *
- * @param reason additional information about the error
- * @param the static type accepted by the matcher (this can flag obvious
- * compile-time problems such as {@code assertThat(1, is("a"))}
- * @param actual the computed value being compared
- * @param matcher an expression, built of {@link Matcher}s, specifying allowed
- * values
- * @see org.hamcrest.CoreMatchers
- * @see org.hamcrest.MatcherAssert
- */
- public static void assertThat(String reason, T actual,
- Matcher super T> matcher) {
- MatcherAssert.assertThat(reason, actual, matcher);
- }
-}
diff --git a/affinity/src/test/java/software/chronicle/enterprise/internals/JnaAffinityTest.java b/affinity/src/test/java/software/chronicle/enterprise/internals/JnaAffinityTest.java
new file mode 100644
index 000000000..a65863c1d
--- /dev/null
+++ b/affinity/src/test/java/software/chronicle/enterprise/internals/JnaAffinityTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+ */
+package software.chronicle.enterprise.internals;
+
+import net.openhft.affinity.BaseAffinityTest;
+import net.openhft.affinity.IAffinity;
+import net.openhft.affinity.impl.LinuxJNAAffinity;
+import net.openhft.affinity.impl.Utilities;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.BitSet;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author peter.lawrey
+ */
+public class JnaAffinityTest extends BaseAffinityTest {
+ private static final int CORES = Runtime.getRuntime().availableProcessors();
+ private static final BitSet CORES_MASK = new BitSet(CORES);
+
+ static {
+ CORES_MASK.set(0, CORES, true);
+ }
+
+ @BeforeClass
+ public static void checkJniLibraryPresent() {
+ Assume.assumeTrue(LinuxJNAAffinity.LOADED);
+ }
+
+ @Test
+ public void getAffinityCompletesGracefully() {
+ System.out.println("affinity: " + Utilities.toBinaryString(getImpl().getAffinity()));
+ }
+
+ @Test
+ public void getAffinityReturnsValidValue() {
+ final BitSet affinity = getImpl().getAffinity();
+ assertFalse("Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty", affinity.isEmpty());
+ final int allCoresMask = (1 << CORES) - 1;
+ assertTrue(
+ "Affinity mask " + Utilities.toBinaryString(affinity) + " must be <=(2^" + CORES + "-1 = " + allCoresMask + ")",
+ affinity.length() <= CORES_MASK.length()
+ );
+ }
+
+ @Test
+ public void setAffinityCompletesGracefully() {
+ BitSet affinity = new BitSet(1);
+ affinity.set(0, true);
+ getImpl().setAffinity(affinity);
+ }
+
+ @Test
+ public void getAffinityReturnsValuePreviouslySet() {
+ String osName = System.getProperty("os.name");
+ if (!osName.startsWith("Linux")) {
+ System.out.println("Skipping Linux tests");
+ return;
+ }
+ final IAffinity impl = LinuxJNAAffinity.INSTANCE;
+ for (int core = 0; core < CORES; core++) {
+ final BitSet mask = new BitSet();
+ mask.set(core, true);
+ getAffinityReturnsValuePreviouslySet(impl, mask);
+ }
+ }
+
+ @Test
+ public void showOtherIds() {
+ System.out.println("processId: " + LinuxJNAAffinity.INSTANCE.getProcessId());
+ System.out.println("threadId: " + LinuxJNAAffinity.INSTANCE.getThreadId());
+ System.out.println("cpu: " + LinuxJNAAffinity.INSTANCE.getCpu());
+ }
+
+ private void getAffinityReturnsValuePreviouslySet(final IAffinity impl,
+ final BitSet mask) {
+
+ impl.setAffinity(mask);
+ final BitSet _mask = impl.getAffinity();
+ assertEquals(mask, _mask);
+ }
+
+ @After
+ public void tearDown() {
+ getImpl().setAffinity(CORES_MASK);
+ }
+
+ private IAffinity getImpl() {
+ return LinuxJNAAffinity.INSTANCE;
+ }
+}
diff --git a/affinity/src/test/java/software/chronicle/enterprise/internals/NativeAffinityTest.java b/affinity/src/test/java/software/chronicle/enterprise/internals/NativeAffinityTest.java
index 4d75a5e4f..fa2bc7fba 100644
--- a/affinity/src/test/java/software/chronicle/enterprise/internals/NativeAffinityTest.java
+++ b/affinity/src/test/java/software/chronicle/enterprise/internals/NativeAffinityTest.java
@@ -1,23 +1,9 @@
/*
- *
- * * Copyright (C) 2016 higherfrequencytrading.com
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU Lesser General Public License as published by
- * * the Free Software Foundation, either version 3 of the License.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU Lesser General Public License for more details.
- * *
- * * You should have received a copy of the GNU Lesser General Public License
- * * along with this program. If not, see .
- *
+ * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
-
package software.chronicle.enterprise.internals;
+import net.openhft.affinity.BaseAffinityTest;
import net.openhft.affinity.IAffinity;
import net.openhft.affinity.impl.LinuxJNAAffinity;
import net.openhft.affinity.impl.Utilities;
@@ -26,15 +12,14 @@
import java.util.BitSet;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* @author peter.lawrey
*/
-public class NativeAffinityTest {
- protected static final int CORES = Runtime.getRuntime().availableProcessors();
- protected static final BitSet CORES_MASK = new BitSet(CORES);
+public class NativeAffinityTest extends BaseAffinityTest {
+ private static final int CORES = Runtime.getRuntime().availableProcessors();
+ private static final BitSet CORES_MASK = new BitSet(CORES);
static {
CORES_MASK.set(0, CORES, true);
@@ -53,10 +38,7 @@ public void getAffinityCompletesGracefully() {
@Test
public void getAffinityReturnsValidValue() {
final BitSet affinity = getImpl().getAffinity();
- assertTrue(
- "Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty",
- !affinity.isEmpty()
- );
+ assertFalse("Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty", affinity.isEmpty());
final int allCoresMask = (1 << CORES) - 1;
assertTrue(
"Affinity mask " + Utilities.toBinaryString(affinity) + " must be <=(2^" + CORES + "-1 = " + allCoresMask + ")",
@@ -80,8 +62,7 @@ public void getAffinityReturnsValuePreviouslySet() {
return;
}
final IAffinity impl = NativeAffinity.INSTANCE;
- final int cores = CORES;
- for (int core = 0; core < cores; core++) {
+ for (int core = 0; core < CORES; core++) {
final BitSet mask = new BitSet();
mask.set(core, true);
getAffinityReturnsValuePreviouslySet(impl, mask);
@@ -135,7 +116,7 @@ public void tearDown() {
getImpl().setAffinity(CORES_MASK);
}
- public IAffinity getImpl() {
+ private IAffinity getImpl() {
return NativeAffinity.INSTANCE;
}
}
diff --git a/affinity/src/test/resources/dual.E5405.properties b/affinity/src/test/resources/dual.E5405.properties
new file mode 100644
index 000000000..d656b7376
--- /dev/null
+++ b/affinity/src/test/resources/dual.E5405.properties
@@ -0,0 +1,24 @@
+#
+# 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.
+#
+#
+0=0,0,0
+1=0,1,0
+2=0,2,0
+3=0,3,0
+4=1,4,0
+5=1,5,0
+6=1,6,0
+7=1,7,0
diff --git a/affinity/src/test/resources/dual.xeon.properties b/affinity/src/test/resources/dual.xeon.properties
new file mode 100644
index 000000000..36fc4ef90
--- /dev/null
+++ b/affinity/src/test/resources/dual.xeon.properties
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+#
+0=0,0,0
+1=0,0,1
+2=3,3,0
+3=3,3,1
diff --git a/affinity/src/test/resources/i3.properties b/affinity/src/test/resources/i3.properties
new file mode 100644
index 000000000..ab4e83876
--- /dev/null
+++ b/affinity/src/test/resources/i3.properties
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+#
+0=0,0,0
+1=0,2,0
+2=0,0,1
+3=0,2,1
diff --git a/affinity/src/test/resources/i7.properties b/affinity/src/test/resources/i7.properties
index 39117a318..9e617cf9c 100644
--- a/affinity/src/test/resources/i7.properties
+++ b/affinity/src/test/resources/i7.properties
@@ -1,19 +1,19 @@
#
-# Copyright (C) 2015 higherfrequencytrading.com
+# Copyright 2016-2025 chronicle.software
#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License.
+# 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
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
+# 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.
#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see .
#
-
0=0,0,0
1=0,1,0
2=0,2,0
diff --git a/docs/images/Thread-Affinity_line.png b/docs/images/Thread-Affinity_line.png
new file mode 100644
index 000000000..2f80359d2
Binary files /dev/null and b/docs/images/Thread-Affinity_line.png differ
diff --git a/pom.xml b/pom.xml
index 6222a93a9..f88f45845 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,34 +1,22 @@
-
-
+
+ Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
+
+-->
+
+
+ 4.0.0
net.openhft
- root-parent-pom
- 1.1.1
-
+ java-parent-pom
+ 2026.0
+
- 4.0.0
Java-Thread-Affinity
- 3.0.6-SNAPSHOT
+ 2026.3-SNAPSHOT
pom
OpenHFT/Java-Thread-Affinity Parent
@@ -39,4 +27,12 @@
affinity-test
+
+ scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git
+ scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git
+ scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git
+
+ ea
+
+