Skip to content

Commit

Permalink
Remove usages of Unsafe
Browse files Browse the repository at this point in the history
  • Loading branch information
dmlloyd committed Jan 23, 2025
1 parent a4443cc commit 32504ed
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 161 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
java-version: |
17
21
24
- name: Build with Maven
run: mvn verify -ntp -B "-Djava17.home=${{env.JAVA_HOME_17_X64}}${{env.JAVA_HOME_17_ARM64}}"
run: mvn verify -ntp -B "-Djava17.home=${{env.JAVA_HOME_17_X64}}${{env.JAVA_HOME_17_ARM64}} -Djava21.home=${{env.JAVA_HOME_21_X64}}${{env.JAVA_HOME_21_ARM64}}"
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
<jboss.threads.eqe.unlimited-queue>false</jboss.threads.eqe.unlimited-queue>
<jboss.threads.eqe.register-mbean>false</jboss.threads.eqe.register-mbean>

<jdk.min.version>11</jdk.min.version>
<jdk.min.version>24</jdk.min.version>

<version.jboss.logging.tools>3.0.3.Final</version.jboss.logging.tools>
</properties>
Expand Down
147 changes: 60 additions & 87 deletions src/main/java/org/jboss/threads/EnhancedQueueExecutor.java

Large diffs are not rendered by default.

15 changes: 5 additions & 10 deletions src/main/java/org/jboss/threads/EnhancedViewExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import io.smallrye.common.constraint.Nullable;
import io.smallrye.common.cpu.ProcessorInfo;

import java.lang.invoke.ConstantBootstraps;
import java.lang.invoke.VarHandle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand All @@ -18,7 +20,7 @@
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static org.jboss.threads.JBossExecutors.unsafe;
import static java.lang.invoke.MethodHandles.lookup;

/**
* A View Executor implementation which avoids lock contention in the common path. This allows us to
Expand All @@ -29,14 +31,7 @@
*/
final class EnhancedViewExecutor extends ViewExecutor {
private static final Logger log = Logger.getLogger("org.jboss.threads.view-executor");
private static final long stateOffset;
static {
try {
stateOffset = unsafe.objectFieldOffset(EnhancedViewExecutor.class.getDeclaredField("state"));
} catch (NoSuchFieldException e) {
throw new NoSuchFieldError(e.getMessage());
}
}
private static final VarHandle stateHandle = ConstantBootstraps.fieldVarHandle(lookup(), "state", VarHandle.class, EnhancedViewExecutor.class, long.class);

private static final int QUEUE_FAILURE_LOG_INTERVAL =
readIntPropertyPrefixed("queue.failure.log.interval", 1_000_000);
Expand Down Expand Up @@ -429,7 +424,7 @@ private static int getQueueSize(long state) {
}

private boolean compareAndSwapState(long expected, long update) {
return unsafe.compareAndSwapLong(this, stateOffset, expected, update);
return stateHandle.compareAndSet(this, expected, update);
}

private Thread.UncaughtExceptionHandler uncaughtExceptionHandler() {
Expand Down
49 changes: 8 additions & 41 deletions src/main/java/org/jboss/threads/JBossExecutors.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
package org.jboss.threads;

import java.lang.reflect.Field;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ScheduledExecutorService;
import java.security.PrivilegedAction;
import java.security.AccessController;

import org.jboss.logging.Logger;
import io.smallrye.common.constraint.Assert;
import sun.misc.Unsafe;

/**
* JBoss thread- and executor-related utility and factory methods.
*/
@SuppressWarnings("deprecation")
public final class JBossExecutors {

private static final Logger THREAD_ERROR_LOGGER = Logger.getLogger("org.jboss.threads.errors");
Expand Down Expand Up @@ -225,7 +220,7 @@ public Thread newThread(final Runnable r) {

private static final Runnable TCCL_RESETTER = new Runnable() {
public void run() {
Thread.currentThread().setContextClassLoader(null);
JDKSpecific.setThreadContextClassLoader(Thread.currentThread(), null);
}

public String toString() {
Expand Down Expand Up @@ -294,39 +289,14 @@ static Runnable classLoaderPreservingTaskUnchecked(final Runnable delegate) {
return new ContextClassLoaderSavingRunnable(getContextClassLoader(Thread.currentThread()), delegate);
}

static final Unsafe unsafe;

static final long contextClassLoaderOffs;

static {
unsafe = AccessController.doPrivileged(new PrivilegedAction<Unsafe>() {
public Unsafe run() {
try {
final Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (IllegalAccessException e) {
throw new IllegalAccessError(e.getMessage());
} catch (NoSuchFieldException e) {
throw new NoSuchFieldError(e.getMessage());
}
}
});
try {
contextClassLoaderOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("contextClassLoader"));
} catch (NoSuchFieldException e) {
throw new NoSuchFieldError(e.getMessage());
}
}

/**
* Privileged method to get the context class loader of the given thread.
*
* @param thread the thread to introspect
* @return the context class loader
*/
static ClassLoader getContextClassLoader(final Thread thread) {
return (ClassLoader) unsafe.getObject(thread, contextClassLoaderOffs);
return JDKSpecific.getThreadContextClassLoader(thread);
}

/**
Expand All @@ -337,12 +307,11 @@ static ClassLoader getContextClassLoader(final Thread thread) {
* @return the old context class loader
*/
static ClassLoader getAndSetContextClassLoader(final Thread thread, final ClassLoader newClassLoader) {
final ClassLoader currentClassLoader = (ClassLoader) unsafe.getObject(thread, contextClassLoaderOffs);
if (currentClassLoader != newClassLoader) {
// not using setContextClassLoader to save loading the current one again
unsafe.putObject(thread, contextClassLoaderOffs, newClassLoader);
ClassLoader old = JDKSpecific.getThreadContextClassLoader(thread);
if (old != newClassLoader) {
JDKSpecific.setThreadContextClassLoader(thread, newClassLoader);
}
return currentClassLoader;
return old;
}

/**
Expand All @@ -352,9 +321,7 @@ static ClassLoader getAndSetContextClassLoader(final Thread thread, final ClassL
* @param classLoader the new context class loader
*/
static void setContextClassLoader(final Thread thread, final ClassLoader classLoader) {
if (unsafe.getObject(thread, contextClassLoaderOffs) != classLoader) {
unsafe.putObject(thread, contextClassLoaderOffs, classLoader);
}
JDKSpecific.setThreadContextClassLoader(thread, classLoader);
}

/**
Expand All @@ -363,7 +330,7 @@ static void setContextClassLoader(final Thread thread, final ClassLoader classLo
* @param thread the thread to introspect
*/
static void clearContextClassLoader(final Thread thread) {
unsafe.putObject(thread, contextClassLoaderOffs, SAFE_CL);
JDKSpecific.setThreadContextClassLoader(thread, SAFE_CL);
}

// ==================================================
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/org/jboss/threads/JDKSpecific.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.jboss.threads;

import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;

import sun.misc.Unsafe;

final class JDKSpecific {
private JDKSpecific() {}

private static final Unsafe unsafe;
private static final long contextClassLoaderOffs;

static {
unsafe = AccessController.doPrivileged(new PrivilegedAction<Unsafe>() {
public Unsafe run() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (IllegalAccessException e) {
IllegalAccessError error = new IllegalAccessError(e.getMessage());
error.setStackTrace(e.getStackTrace());
throw error;
} catch (NoSuchFieldException e) {
NoSuchFieldError error = new NoSuchFieldError(e.getMessage());
error.setStackTrace(e.getStackTrace());
throw error;
}
}
});
try {
contextClassLoaderOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("contextClassLoader"));
} catch (NoSuchFieldException e) {
NoSuchFieldError error = new NoSuchFieldError(e.getMessage());
error.setStackTrace(e.getStackTrace());
throw error;
}
}

static void setThreadContextClassLoader(Thread thread, ClassLoader classLoader) {
unsafe.putObject(thread, contextClassLoaderOffs, classLoader);
}

static ClassLoader getThreadContextClassLoader(Thread thread) {
return (ClassLoader) unsafe.getObject(thread, contextClassLoaderOffs);
}

static final class ThreadAccess {
private static final long threadLocalMapOffs;
private static final long inheritableThreadLocalMapOffs;

static {
try {
threadLocalMapOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("threadLocals"));
inheritableThreadLocalMapOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("inheritableThreadLocals"));
} catch (NoSuchFieldException e) {
NoSuchFieldError error = new NoSuchFieldError(e.getMessage());
error.setStackTrace(e.getStackTrace());
throw error;
}
}

static void clearThreadLocals() {
Thread thread = Thread.currentThread();
unsafe.putObject(thread, threadLocalMapOffs, null);
unsafe.putObject(thread, inheritableThreadLocalMapOffs, null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,11 @@ public void run() {
try {
super.run();
} finally {
Resetter.run();
JDKSpecific.ThreadAccess.clearThreadLocals();
}
}

public String toString() {
return "Thread-local resetting Runnable";
}

static final class Resetter {
private static final long threadLocalMapOffs;
private static final long inheritableThreadLocalMapOffs;

static {
try {
threadLocalMapOffs = JBossExecutors.unsafe.objectFieldOffset(Thread.class.getDeclaredField("threadLocals"));
inheritableThreadLocalMapOffs = JBossExecutors.unsafe.objectFieldOffset(Thread.class.getDeclaredField("inheritableThreadLocals"));
} catch (NoSuchFieldException e) {
throw new NoSuchFieldError(e.getMessage());
}
}

static void run() {
final Thread thread = Thread.currentThread();
JBossExecutors.unsafe.putObject(thread, threadLocalMapOffs, null);
JBossExecutors.unsafe.putObject(thread, inheritableThreadLocalMapOffs, null);
}
}
}
51 changes: 51 additions & 0 deletions src/main/java24/org/jboss/threads/JDKSpecific.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.jboss.threads;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.lang.reflect.UndeclaredThrowableException;

final class JDKSpecific {
private JDKSpecific() {}

static void setThreadContextClassLoader(Thread thread, ClassLoader classLoader) {
thread.setContextClassLoader(classLoader);
}

static ClassLoader getThreadContextClassLoader(Thread thread) {
return thread.getContextClassLoader();
}

static final class ThreadAccess {
private static final MethodHandle setThreadLocalsHandle;
private static final MethodHandle setInheritableThreadLocalsHandle;

static {
try {
MethodHandles.Lookup threadLookup = MethodHandles.privateLookupIn(Thread.class, MethodHandles.lookup());
setThreadLocalsHandle = threadLookup.unreflectVarHandle(Thread.class.getDeclaredField("threadLocals")).toMethodHandle(VarHandle.AccessMode.SET).asType(MethodType.methodType(void.class, Object.class));
setInheritableThreadLocalsHandle = threadLookup.unreflectVarHandle(Thread.class.getDeclaredField("inheritableThreadLocals")).toMethodHandle(VarHandle.AccessMode.SET).asType(MethodType.methodType(void.class, Object.class));
} catch (IllegalAccessException e) {
Module myModule = ThreadAccess.class.getModule();
String myName = myModule.isNamed() ? myModule.getName() : "ALL-UNNAMED";
throw new IllegalAccessError(e.getMessage() +
"; to use the thread-local-reset capability on Java 24 or later, use this JVM option: --add-opens java.base/java.lang=" + myName);
} catch (NoSuchFieldException e) {
throw new NoSuchFieldError(e.getMessage());
}
}

static void clearThreadLocals() {
final Thread thread = Thread.currentThread();
try {
setThreadLocalsHandle.invokeExact(thread, null);
setInheritableThreadLocalsHandle.invokeExact(thread, null);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
}
}

0 comments on commit 32504ed

Please sign in to comment.