Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions affinity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-jpms</artifactId>
<version>5.17.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,38 @@
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.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
* Signature: ()[B
*/
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,21 +53,23 @@ 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
}

/*
* Class: software_chronicle_enterprise_internals_NativeAffinity
* Class: software_chronicle_enterprise_internals_impl_NativeAffinity
* Method: setAffinity0
* Signature: (J)V
* Signature: ([B)V
*/
JNIEXPORT void JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_setAffinity0
(JNIEnv *env, jclass c, jbyteArray affinity)
Expand All @@ -61,12 +79,26 @@ 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);
if (env->ExceptionCheck()) {
return;
}
}

sched_setaffinity(0, size, &mask);
int res = sched_setaffinity(0, size, &mask);
if (res != 0) {
const int error = errno;
char message[256];
snprintf(message, sizeof(message),
"sched_setaffinity(thread=0, maskBytes=%d) failed: errno=%d (%s)",
(int) length, error, strerror(error));
throwRuntimeException(env, message);
}
#else
throw std::runtime_error("Not supported");
throwUnsupportedOperation(env, "NativeAffinity.setAffinity0 is only supported on Linux");
#endif
}

Expand All @@ -77,11 +109,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 +124,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 +139,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
}

120 changes: 73 additions & 47 deletions affinity/src/main/java/net/openhft/affinity/Affinity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
*/
package net.openhft.affinity;

import com.sun.jna.Native;
import net.openhft.affinity.impl.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.util.BitSet;

Expand All @@ -25,43 +25,47 @@ 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 (LinkageError | RuntimeException t) {
// Optional native initialisation may fail; VM errors and assertion failures must propagate.
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 All @@ -72,7 +76,7 @@ private static boolean isWindowsJNAAffinityUsable() {
if (isJNAAvailable()) {
try {
return WindowsJNAAffinity.LOADED;
} catch (Throwable t) {
} catch (LinkageError | RuntimeException t) {
logThrowable(t, "Windows JNA-based affinity not usable because it failed to load!");
return false;
}
Expand All @@ -86,7 +90,7 @@ private static boolean isPosixJNAAffinityUsable() {
if (isJNAAvailable()) {
try {
return PosixJNAAffinity.LOADED;
} catch (Throwable t) {
} catch (LinkageError | RuntimeException t) {
logThrowable(t, "Posix JNA-based affinity not usable because it failed to load!");
return false;
}
Expand All @@ -100,7 +104,7 @@ private static boolean isLinuxJNAAffinityUsable() {
if (isJNAAvailable()) {
try {
return LinuxJNAAffinity.LOADED;
} catch (Throwable t) {
} catch (LinkageError | RuntimeException t) {
logThrowable(t, "Linux JNA-based affinity not usable because it failed to load!");
return false;
}
Expand Down Expand Up @@ -173,22 +177,44 @@ public static void setThreadId() {
}
}

@SuppressWarnings("removal") // ThreadDeath must propagate on supported older JDKs.
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");
// Access the inherited public field through Native without opening JNA's module.
String version = (String) MethodHandles.publicLookup()
.findStaticGetter(nativeClass, "VERSION", String.class).invokeExact();
int majorVersion = version == null ? 0 : 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 (VirtualMachineError | ThreadDeath | AssertionError fatal) {
throw fatal;
} catch (Throwable t) {
// JNA also reports an incompatible jnidispatch with a plain Error.
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
11 changes: 11 additions & 0 deletions affinity/src/main/java/net/openhft/affinity/impl/LinuxHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public class LinuxHelper {
version = ver;
}

/**
* Returns the CPU affinity mask of the calling thread.
*
* @return the processors on which the calling thread is permitted to run
*/
public static
@NotNull
cpu_set_t sched_getaffinity() {
Expand All @@ -52,10 +57,16 @@ cpu_set_t sched_getaffinity() {
return cpuset;
}

/**
* Sets the CPU affinity mask of the calling thread.
*/
public static void sched_setaffinity(final BitSet affinity) {
sched_setaffinity(0, affinity);
}

/**
* Sets affinity for the thread identified by {@code pid}; zero selects the calling thread.
*/
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public enum LinuxJNAAffinity implements IAffinity {

private final ThreadLocal<Integer> THREAD_ID = new ThreadLocal<>();

/**
* Returns the CPU affinity mask of the calling thread.
*/
@Override
public BitSet getAffinity() {
final LinuxHelper.cpu_set_t cpuset = LinuxHelper.sched_getaffinity();
Expand All @@ -58,6 +61,9 @@ public BitSet getAffinity() {
return ret;
}

/**
* Sets the CPU affinity mask of the calling thread.
*/
@Override
public void setAffinity(final BitSet affinity) {
LinuxHelper.sched_setaffinity(affinity);
Expand Down
Loading