Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
208a18d
Enhance requirements document formatting and structure
peter-lawrey Nov 21, 2025
822021b
Refactor ThreadLocal variable names for consistency and clarity
peter-lawrey Nov 21, 2025
d7b7686
Refactor test methods for consistency and improve exception handling
peter-lawrey Nov 21, 2025
e3d05f2
Update third-party-bom version to 3.27ea7
peter-lawrey Nov 21, 2025
93bedc7
Add documentation files for AI agent guidelines and project overview
peter-lawrey Nov 21, 2025
4542f1b
Add decision log and rename requirements document for Java Thread Aff…
peter-lawrey Nov 21, 2025
7124abb
Apply checkstyle adjustments
peter-lawrey Nov 12, 2025
0f6c7dc
Checkstyle issues addressed
peter-lawrey Nov 13, 2025
b267ab0
Update documentation
peter-lawrey Nov 16, 2025
8571e45
Checkpoint
peter-lawrey Nov 18, 2025
f1e6e09
Document and test native integration
peter-lawrey Nov 18, 2025
7889a95
Add security hardening and version tracking to native affinity library
peter-lawrey Nov 18, 2025
0be8351
Add shared architectural, operational, and security standards documen…
peter-lawrey Nov 20, 2025
e1dcd23
Code Analysis fixes
peter-lawrey Nov 21, 2025
7869296
Code Analysis fixes
peter-lawrey Nov 21, 2025
7a3ef41
Remove unnecessary blank lines in various classes for improved code r…
peter-lawrey Nov 24, 2025
728b839
Remove unnecessary blank lines in various classes for improved code r…
peter-lawrey Nov 24, 2025
dc84304
Remove unnecessary blank lines in various classes for improved code r…
peter-lawrey Nov 24, 2025
e4808e0
Remove unnecessary blank lines and improve documentation across multi…
peter-lawrey Nov 24, 2025
1c863fb
Remove JDK activation from quality profile in pom.xml
peter-lawrey Nov 24, 2025
3989267
Update copyright license in package-info.java to Apache-2.0
peter-lawrey Nov 24, 2025
1baf231
Refactor Affinity class to improve OS detection logic and error handl…
peter-lawrey Nov 26, 2025
dd7f512
Add TODO comment to OSGiBundleTest to indicate pending fixes
peter-lawrey Nov 26, 2025
784289a
@Ignore old test
peter-lawrey Nov 28, 2025
fca31d3
Merge branch 'develop' into adv/develop
peter-lawrey Apr 29, 2026
c3f00a4
Consolidate affinity project documentation
peter-lawrey Apr 29, 2026
bfb58c4
Remove local Maven quality profile wiring
peter-lawrey Apr 29, 2026
02d597b
Stop tracking native object artefacts
peter-lawrey Apr 29, 2026
85fb924
Simplify JNI native build and version handling
peter-lawrey Apr 29, 2026
3820b6b
Tidy affinity Java source cleanup
peter-lawrey Apr 29, 2026
0984916
Remove empty OSGi package marker
peter-lawrey Apr 29, 2026
40d7e09
Prune speculative native affinity tests
peter-lawrey Apr 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
### Compiled class file
*.class

### Native build artefacts
*.o
*.so

### Package Files
*.jar
*.war
Expand Down
3 changes: 3 additions & 0 deletions LICENSE.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
== Copyright 2016-2025 chronicle.software
:toc:
:lang: en-GB
:source-highlighter: rouge

Licensed under the *Apache License, Version 2.0* (the "License");
you may not use this file except in compliance with the License.
Expand Down
9 changes: 6 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
= Thread Affinity
:toc:
:lang: en-GB
:source-highlighter: rouge

image::docs/images/Thread-Affinity_line.png[width=20%]

Expand All @@ -10,9 +13,9 @@ image::https://maven-badges.herokuapp.com/maven-central/net.openhft/affinity/bad
image:https://javadoc.io/badge2/net.openhft/affinity/javadoc.svg[link="https://www.javadoc.io/doc/net.openhft/affinity/latest/index.html"]

== Overview
Lets you bind a thread to a given core, this can improve performance (this library works best on linux).
Lets you bind a thread to a given core; this can improve performance (this library works best on Linux).

OpenHFT Java Thread Affinity library
OpenHFT Java Thread Affinity library.

See https://github.com/OpenHFT/Java-Thread-Affinity/tree/master/affinity/src/test/java[affinity/src/test/java]
for working examples of how to use this library.
Expand Down Expand Up @@ -352,7 +355,7 @@ I have the cpuId in a configuration file, how can I set it using a string?

=== Answer: use one of the following

[source,java]
[source,java,opts=novalidate]
----
try (AffinityLock lock = AffinityLock.acquireLock("last")) {
assertEquals(PROCESSORS - 1, Affinity.getCpu());
Expand Down
2 changes: 1 addition & 1 deletion affinity-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
</file>
</activation>
</profile>

</profiles>

<scm>
Expand All @@ -201,4 +202,3 @@
</scm>

</project>

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import static org.junit.Assert.*;
import static org.ops4j.pax.exam.CoreOptions.*;

@Ignore("TODO FIX")
@Ignore("Fails with current Felix resolver (NoSuchMethodError: ResolveContext.onCancel); skip until updated")
@RunWith(PaxExam.class)
public class OSGiBundleTest extends net.openhft.affinity.osgi.OSGiTestBase {
@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,35 @@
#include <unistd.h>
#include <string.h>
#endif
#include <stdexcept>
#include "software_chronicle_enterprise_internals_impl_NativeAffinity.h"

#ifndef __linux__
static void throwUnsupportedOperation(JNIEnv *env, const char *message) {
jclass exClass = env->FindClass("java/lang/UnsupportedOperationException");
if (exClass != NULL) {
env->ThrowNew(exClass, message);
}
}
#endif

#ifdef __linux__
static void throwRuntimeException(JNIEnv *env, const char *message) {
jclass exClass = env->FindClass("java/lang/RuntimeException");
if (exClass != NULL) {
env->ThrowNew(exClass, message);
}
}
#endif

/*
* Class: software_chronicle_enterprise_internals_impl_NativeAffinity
* Method: getAffinity0
* Signature: ()J
*/
JNIEXPORT jbyteArray JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getAffinity0
(JNIEnv *env, jclass c)
(JNIEnv *env, jclass c)
{
#ifdef __linux__
// The default size of the structure supports 1024 CPUs, should be enough
// for now In the future we can use dynamic sets, which can support more
// CPUs, given OS can handle them as well
cpu_set_t mask;
const size_t size = sizeof(mask);

Expand All @@ -37,14 +51,16 @@ JNIEXPORT jbyteArray JNICALL Java_software_chronicle_enterprise_internals_impl_N
return NULL;
}

jbyteArray ret = env->NewByteArray(size);
jbyte* bytes = env->GetByteArrayElements(ret, 0);
memcpy(bytes, &mask, size);
env->SetByteArrayRegion(ret, 0, size, bytes);
jbyteArray ret = env->NewByteArray((jsize) size);
if (ret == NULL) {
return NULL;
}
env->SetByteArrayRegion(ret, 0, (jsize) size, (const jbyte *) &mask);

return ret;
#else
throw std::runtime_error("Not supported");
throwUnsupportedOperation(env, "NativeAffinity.getAffinity0 is only supported on Linux");
return NULL;
#endif
}

Expand All @@ -61,12 +77,18 @@ JNIEXPORT void JNICALL Java_software_chronicle_enterprise_internals_impl_NativeA
const size_t size = sizeof(mask);
CPU_ZERO(&mask);

jbyte* bytes = env->GetByteArrayElements(affinity, 0);
memcpy(&mask, bytes, size);
jsize length = env->GetArrayLength(affinity);
if (length > 0) {
jsize copyLength = length < (jsize) size ? length : (jsize) size;
env->GetByteArrayRegion(affinity, 0, copyLength, (jbyte *) &mask);
}

sched_setaffinity(0, size, &mask);
int res = sched_setaffinity(0, size, &mask);
if (res != 0) {
throwRuntimeException(env, "sched_setaffinity failed");
}
#else
throw std::runtime_error("Not supported");
throwUnsupportedOperation(env, "NativeAffinity.setAffinity0 is only supported on Linux");
#endif
}

Expand All @@ -77,11 +99,11 @@ JNIEXPORT void JNICALL Java_software_chronicle_enterprise_internals_impl_NativeA
*/
JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getProcessId0
(JNIEnv *env, jclass c) {
#ifndef __linux__
throw std::runtime_error("Not supported");
#ifdef __linux__
return (jint) getpid();
#else
return (jint) getpid();
throwUnsupportedOperation(env, "NativeAffinity.getProcessId0 is only supported on Linux");
return (jint) -1;
#endif
}

Expand All @@ -92,11 +114,11 @@ JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeA
*/
JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getThreadId0
(JNIEnv *env, jclass c) {
#ifndef __linux__
throw std::runtime_error("Not supported");
#else

#ifdef __linux__
return (jint) (pid_t) syscall (SYS_gettid);
#else
throwUnsupportedOperation(env, "NativeAffinity.getThreadId0 is only supported on Linux");
return (jint) -1;
#endif
}

Expand All @@ -107,11 +129,10 @@ JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeA
*/
JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getCpu0
(JNIEnv *env, jclass c) {
#ifndef __linux__
throw std::runtime_error("Not supported");
#ifdef __linux__
return (jint) sched_getcpu();
#else
return (jint) sched_getcpu();
throwUnsupportedOperation(env, "NativeAffinity.getCpu0 is only supported on Linux");
return (jint) -1;
#endif
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <jni.h>
#include <mach/thread_policy.h>
#include <pthread.h>
#include <stdio.h>
#include "software_chronicle_enterprise_internals_impl_NativeAffinity.h"

/*
Expand All @@ -20,13 +21,13 @@ JNIEXPORT jlong JNICALL Java_software_chronicle_enterprise_internals_impl_Native
policy.affinity_tag = 0;
mach_msg_type_number_t count = THREAD_AFFINITY_POLICY_COUNT;
boolean_t get_default = FALSE;

if ((thread_policy_get(threadport,
THREAD_AFFINITY_POLICY, (thread_policy_t)&policy,
&count, &get_default)) != KERN_SUCCESS) {
return ~0LL;
}

return (jlong) policy.affinity_tag;
}

Expand All @@ -37,19 +38,21 @@ JNIEXPORT jlong JNICALL Java_software_chronicle_enterprise_internals_impl_Native
*/
JNIEXPORT void JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_setAffinity0
(JNIEnv *env, jclass c, jlong affinity) {

thread_port_t threadport = pthread_mach_thread_np(pthread_self());

struct thread_enterprise_internals_policy policy;
policy.affinity_tag = affinity;

int rc = thread_policy_set(threadport,
THREAD_AFFINITY_POLICY, (thread_policy_t)&policy,
THREAD_AFFINITY_POLICY_COUNT);
THREAD_AFFINITY_POLICY_COUNT);
if (rc != KERN_SUCCESS) {
jclass ex = (*env)->FindClass(env, "java/lang/RuntimeException");
char msg[100];
sprintf(msg, "Bad return value from thread_policy_set: %d", rc);
(*env)->ThrowNew(env, ex, msg);
if (ex != NULL) {
char msg[100];
snprintf(msg, sizeof(msg), "Bad return value from thread_policy_set: %d", rc);
(*env)->ThrowNew(env, ex, msg);
}
}
}
109 changes: 65 additions & 44 deletions affinity/src/main/java/net/openhft/affinity/Affinity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/
package net.openhft.affinity;

import com.sun.jna.Native;
import net.openhft.affinity.impl.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
Expand All @@ -25,43 +24,46 @@ public enum Affinity {
static final Logger LOGGER = LoggerFactory.getLogger(Affinity.class);
@NotNull
private static final IAffinity AFFINITY_IMPL;
private static Boolean JNAAvailable;
private static volatile Boolean jnaAvailable;

static {
String osName = System.getProperty("os.name");
if (osName.contains("Win") && isWindowsJNAAffinityUsable()) {
LOGGER.trace("Using Windows JNA-based affinity control implementation");
AFFINITY_IMPL = WindowsJNAAffinity.INSTANCE;

} else if (osName.contains("x")) {
/*if (osName.startsWith("Linux") && NativeAffinity.LOADED) {
LOGGER.trace("Using Linux JNI-based affinity control implementation");
AFFINITY_IMPL = NativeAffinity.INSTANCE;
} else*/
if (osName.startsWith("Linux") && isLinuxJNAAffinityUsable()) {
LOGGER.trace("Using Linux JNA-based affinity control implementation");
AFFINITY_IMPL = LinuxJNAAffinity.INSTANCE;

} else if (isPosixJNAAffinityUsable()) {
LOGGER.trace("Using Posix JNA-based affinity control implementation");
AFFINITY_IMPL = PosixJNAAffinity.INSTANCE;
IAffinity impl;
try {
String osName = System.getProperty("os.name");
if (osName.contains("Win") && isWindowsJNAAffinityUsable()) {
LOGGER.trace("Using Windows JNA-based affinity control implementation");
impl = WindowsJNAAffinity.INSTANCE;

} else if (osName.contains("x")) {
if (osName.startsWith("Linux") && isLinuxJNAAffinityUsable()) {
LOGGER.trace("Using Linux JNA-based affinity control implementation");
impl = LinuxJNAAffinity.INSTANCE;

} else if (isPosixJNAAffinityUsable()) {
LOGGER.trace("Using Posix JNA-based affinity control implementation");
impl = PosixJNAAffinity.INSTANCE;

} else {
LOGGER.info("Unsupported POSIX OS: {} with an 'x'. Using dummy affinity control implementation", osName);
impl = NullAffinity.INSTANCE;
}
} else if (osName.contains("Mac") && isMacJNAAffinityUsable()) {
LOGGER.trace("Using MAC OSX JNA-based thread id implementation");
impl = OSXJNAAffinity.INSTANCE;

} else if (osName.contains("SunOS") && isSolarisJNAAffinityUsable()) {
LOGGER.trace("Using Solaris JNA-based thread id implementation");
impl = SolarisJNAAffinity.INSTANCE;

} else {
LOGGER.info("Using dummy affinity control implementation");
AFFINITY_IMPL = NullAffinity.INSTANCE;
LOGGER.info("Unsupported OS: {}. Using dummy affinity control implementation", osName);
impl = NullAffinity.INSTANCE;
}
} else if (osName.contains("Mac") && isMacJNAAffinityUsable()) {
LOGGER.trace("Using MAC OSX JNA-based thread id implementation");
AFFINITY_IMPL = OSXJNAAffinity.INSTANCE;

} else if (osName.contains("SunOS") && isSolarisJNAAffinityUsable()) {
LOGGER.trace("Using Solaris JNA-based thread id implementation");
AFFINITY_IMPL = SolarisJNAAffinity.INSTANCE;

} else {
LOGGER.info("Using dummy affinity control implementation");
AFFINITY_IMPL = NullAffinity.INSTANCE;
} catch (Throwable t) {
LOGGER.warn("Falling back to dummy affinity control implementation because native init failed", t);
impl = NullAffinity.INSTANCE;
}
AFFINITY_IMPL = impl;
}

public static IAffinity getAffinityImpl() {
Expand Down Expand Up @@ -174,21 +176,40 @@ public static void setThreadId() {
}

public static boolean isJNAAvailable() {
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;
Boolean available = jnaAvailable;
if (available == null) {
synchronized (Affinity.class) {
available = jnaAvailable;
if (available == null) {
boolean result;
try {
Class<?> nativeClass = Class.forName("com.sun.jna.Native");
Field versionField = nativeClass.getField("VERSION");
versionField.setAccessible(true);
Object versionObj = versionField.get(null);
String version = versionObj == null ? "0" : versionObj.toString();
int majorVersion = Integer.parseInt(version.split("\\.")[0]);
if (majorVersion < 5) {
LOGGER.warn("Affinity library requires JNA version >= 5");
result = false;
} else {
try {
Class.forName("com.sun.jna.Platform");
result = true;
} catch (ClassNotFoundException ignored) {
result = false;
}
}
} catch (Throwable t) { // NoClassDefFoundError, UnsatisfiedLinkError, IllegalAccessException etc.
LOGGER.warn("JNA not available, falling back to NullAffinity", t);
result = false;
}
available = result;
jnaAvailable = available;
}
}
}
return JNAAvailable;
return available;
}

public static AffinityLock acquireLock() {
Expand Down
Loading