diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 75599414..54ffd111 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,7 @@ jobs: java-version: | 17 21 + 24-ea - 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}}" diff --git a/pom.xml b/pom.xml index 796f4239..f06568f7 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ false false - 11 + 24 3.0.3.Final diff --git a/src/main/java/org/jboss/threads/EnhancedQueueExecutor.java b/src/main/java/org/jboss/threads/EnhancedQueueExecutor.java index 3ce0b001..6da1bb65 100644 --- a/src/main/java/org/jboss/threads/EnhancedQueueExecutor.java +++ b/src/main/java/org/jboss/threads/EnhancedQueueExecutor.java @@ -3,12 +3,15 @@ import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Thread.currentThread; +import static java.lang.invoke.MethodHandles.lookup; import static java.security.AccessController.doPrivileged; import static java.security.AccessController.getContext; import static java.util.concurrent.locks.LockSupport.parkNanos; import static java.util.concurrent.locks.LockSupport.unpark; -import static org.jboss.threads.JBossExecutors.unsafe; +import java.lang.invoke.ConstantBootstraps; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.lang.management.ManagementFactory; import java.security.AccessControlContext; import java.security.PrivilegedAction; @@ -72,6 +75,15 @@ public final class EnhancedQueueExecutor extends AbstractExecutorService implements ManageableThreadPoolExecutorService, ScheduledExecutorService { private static final Thread[] NO_THREADS = new Thread[0]; + private static final VarHandle objArrayHandle = MethodHandles.arrayElementVarHandle(TaskNode[].class); + private static final VarHandle longArrayHandle = MethodHandles.arrayElementVarHandle(long[].class); + + private static final VarHandle terminationWaitersHandle = ConstantBootstraps.fieldVarHandle(lookup(), "terminationWaiters", VarHandle.class, EnhancedQueueExecutor.class, Waiter.class); + + private static final VarHandle peakThreadCountHandle = ConstantBootstraps.fieldVarHandle(lookup(), "peakThreadCount", VarHandle.class, EnhancedQueueExecutor.class, int.class); + private static final VarHandle activeCountHandle = ConstantBootstraps.fieldVarHandle(lookup(), "activeCount", VarHandle.class, EnhancedQueueExecutor.class, int.class); + private static final VarHandle peakQueueSizeHandle = ConstantBootstraps.fieldVarHandle(lookup(), "peakQueueSize", VarHandle.class, EnhancedQueueExecutor.class, int.class); + static { Version.getVersionString(); MBeanUnregisterAction.forceInit(); @@ -316,12 +328,6 @@ public final class EnhancedQueueExecutor extends AbstractExecutorService impleme private static final int numUnsharedLongs = 2; private static final int numUnsharedObjects = 2; - private static final long terminationWaitersOffset; - - private static final long peakThreadCountOffset; - private static final long activeCountOffset; - private static final long peakQueueSizeOffset; - // GraalVM should initialize this class at run time, which we instruct it to do in // src/main/resources/META-INF/native-image/org.jboss.threads/jboss-threads/native-image.properties // Please make sure to update that file if you remove or rename this class, or if runtime @@ -330,11 +336,11 @@ private static final class RuntimeFields { private static final int unsharedTaskNodesSize; private static final int unsharedLongsSize; - private static final long headOffset; - private static final long tailOffset; + private static final int headIndex; + private static final int tailIndex; - private static final long threadStatusOffset; - private static final long queueSizeOffset; + private static final int threadStatusIndex; + private static final int queueSizeIndex; static { int cacheLine = CacheInfo.getSmallestDataCacheLineSize(); @@ -344,28 +350,16 @@ private static final class RuntimeFields { } // cpu spatial prefetcher can drag 2 cache-lines at once into L2 int pad = cacheLine > 128 ? cacheLine : 128; - int longScale = unsafe.arrayIndexScale(long[].class); - int taskNodeScale = unsafe.arrayIndexScale(TaskNode[].class); + // make some assumptions about array scale + int longScale = 8; + int taskNodeScale = 8; // these fields are in units of array scale unsharedTaskNodesSize = pad / taskNodeScale * (numUnsharedObjects + 1); unsharedLongsSize = pad / longScale * (numUnsharedLongs + 1); - // these fields are in bytes - headOffset = unsafe.arrayBaseOffset(TaskNode[].class) + pad; - tailOffset = unsafe.arrayBaseOffset(TaskNode[].class) + pad * 2; - threadStatusOffset = unsafe.arrayBaseOffset(long[].class) + pad; - queueSizeOffset = unsafe.arrayBaseOffset(long[].class) + pad * 2; - } - } - - static { - try { - terminationWaitersOffset = unsafe.objectFieldOffset(EnhancedQueueExecutor.class.getDeclaredField("terminationWaiters")); - - peakThreadCountOffset = unsafe.objectFieldOffset(EnhancedQueueExecutor.class.getDeclaredField("peakThreadCount")); - activeCountOffset = unsafe.objectFieldOffset(EnhancedQueueExecutor.class.getDeclaredField("activeCount")); - peakQueueSizeOffset = unsafe.objectFieldOffset(EnhancedQueueExecutor.class.getDeclaredField("peakQueueSize")); - } catch (NoSuchFieldException e) { - throw new NoSuchFieldError(e.getMessage()); + headIndex = pad / taskNodeScale; + tailIndex = pad / taskNodeScale * 2; + threadStatusIndex = pad / longScale; + queueSizeIndex = pad / longScale * 2; } } @@ -383,7 +377,6 @@ private static final class RuntimeFields { private static final long TS_ALLOW_CORE_TIMEOUT = 1L << 60; private static final long TS_SHUTDOWN_REQUESTED = 1L << 61; private static final long TS_SHUTDOWN_INTERRUPT = 1L << 62; - @SuppressWarnings("NumericOverflow") private static final long TS_SHUTDOWN_COMPLETE = 1L << 63; private static final int EXE_OK = 0; @@ -929,8 +922,7 @@ public List shutdownNow() { head = getHead(unsharedTaskNodes); continue; } - if (headNext instanceof TaskNode) { - TaskNode taskNode = (TaskNode) headNext; + if (headNext instanceof TaskNode taskNode) { if (compareAndSetHead(unsharedTaskNodes, head, taskNode)) { // save from GC nepotism head.setNextOrdered(head); @@ -1941,8 +1933,7 @@ private int tryExecute(final Task runnable) { } // otherwise the consumer gave up or was exited already, so fall out and... } - } else if (tailNext instanceof TaskNode) { - TaskNode tailNextTaskNode = (TaskNode) tailNext; + } else if (tailNext instanceof TaskNode tailNextTaskNode) { // Opportunistically update tail to the next node. If this operation has been handled by // another thread we fall back to the loop and try again instead of duplicating effort. if (compareAndSetTail(tail, tailNextTaskNode)) { @@ -2013,79 +2004,79 @@ void completeTermination() { // ======================================================= TaskNode getTail() { - return (TaskNode) unsafe.getObjectVolatile(unsharedTaskNodes, RuntimeFields.tailOffset); + return (TaskNode) objArrayHandle.getVolatile(unsharedTaskNodes, RuntimeFields.tailIndex); } TaskNode setTailPlain(TaskNode tail) { - unsafe.putObject(unsharedTaskNodes, RuntimeFields.tailOffset, tail); + unsharedTaskNodes[RuntimeFields.tailIndex] = tail; return tail; } boolean compareAndSetTail(final EnhancedQueueExecutor.TaskNode expect, final EnhancedQueueExecutor.TaskNode update) { - return getTail() == expect && unsafe.compareAndSwapObject(unsharedTaskNodes, RuntimeFields.tailOffset, expect, update); + return getTail() == expect && objArrayHandle.compareAndSet(unsharedTaskNodes, RuntimeFields.tailIndex, expect, update); } - TaskNode getHead(final TaskNode[] unsharedTaskNodes) { - return (TaskNode) unsafe.getObjectVolatile(unsharedTaskNodes, RuntimeFields.headOffset); + static TaskNode getHead(final TaskNode[] unsharedTaskNodes) { + return (TaskNode) objArrayHandle.getVolatile(unsharedTaskNodes, RuntimeFields.headIndex); } TaskNode setHeadPlain(TaskNode head) { - unsafe.putObject(unsharedTaskNodes, RuntimeFields.headOffset, head); + unsharedTaskNodes[RuntimeFields.headIndex] = head; return head; } - boolean compareAndSetHead(final TaskNode[] unsharedTaskNodes, final TaskNode expect, final TaskNode update) { - return unsafe.compareAndSwapObject(unsharedTaskNodes, RuntimeFields.headOffset, expect, update); + static boolean compareAndSetHead(final TaskNode[] unsharedTaskNodes, final TaskNode expect, final TaskNode update) { + return objArrayHandle.compareAndSet(unsharedTaskNodes, RuntimeFields.headIndex, expect, update); } long getThreadStatus() { - return unsafe.getLongVolatile(unsharedLongs, RuntimeFields.threadStatusOffset); + return (long) longArrayHandle.getVolatile(unsharedLongs, RuntimeFields.threadStatusIndex); } long setThreadStatusPlain(long status) { - unsafe.putLong(unsharedLongs, RuntimeFields.threadStatusOffset, status); + unsharedLongs[RuntimeFields.threadStatusIndex] = status; return status; } boolean compareAndSetThreadStatus(final long expect, final long update) { - return unsafe.compareAndSwapLong(unsharedLongs, RuntimeFields.threadStatusOffset, expect, update); + return longArrayHandle.compareAndSet(unsharedLongs, RuntimeFields.threadStatusIndex, expect, update); } void incrementActiveCount() { - unsafe.getAndAddInt(this, activeCountOffset, 1); + activeCountHandle.getAndAdd(this, 1); } void decrementActiveCount() { - unsafe.getAndAddInt(this, activeCountOffset, -1); + activeCountHandle.getAndAdd(this, -1); } boolean compareAndSetPeakThreadCount(final int expect, final int update) { - return unsafe.compareAndSwapInt(this, peakThreadCountOffset, expect, update); + return peakThreadCountHandle.compareAndSet(this, expect, update); } boolean compareAndSetPeakQueueSize(final int expect, final int update) { - return unsafe.compareAndSwapInt(this, peakQueueSizeOffset, expect, update); + return peakQueueSizeHandle.compareAndSet(this, expect, update); } long getQueueSizeVolatile() { - return unsafe.getLongVolatile(unsharedLongs, RuntimeFields.queueSizeOffset); + return (long) longArrayHandle.getVolatile(unsharedLongs, RuntimeFields.queueSizeIndex); } long setQueueSizePlain(long queueSize) { - unsafe.putLong(unsharedLongs, RuntimeFields.queueSizeOffset, queueSize); + unsharedLongs[RuntimeFields.queueSizeIndex] = queueSize; return queueSize; } boolean compareAndSetQueueSize(final long expect, final long update) { - return unsafe.compareAndSwapLong(unsharedLongs, RuntimeFields.queueSizeOffset, expect, update); + return longArrayHandle.compareAndSet(unsharedLongs, RuntimeFields.queueSizeIndex, expect, update); } boolean compareAndSetTerminationWaiters(final Waiter expect, final Waiter update) { - return unsafe.compareAndSwapObject(this, terminationWaitersOffset, expect, update); + return terminationWaitersHandle.compareAndSet(this, expect, update); } Waiter getAndSetTerminationWaiters(final Waiter update) { - return (Waiter) unsafe.getAndSetObject(this, terminationWaitersOffset, update); + return (Waiter) terminationWaitersHandle.getAndSet(this, update); } // ======================================================= @@ -2294,21 +2285,13 @@ void rejectShutdown(final Task task) { // ======================================================= abstract static class QNode { - private static final long nextOffset; - - static { - try { - nextOffset = unsafe.objectFieldOffset(QNode.class.getDeclaredField("next")); - } catch (NoSuchFieldException e) { - throw new NoSuchFieldError(e.getMessage()); - } - } + private static final VarHandle nextHandle = ConstantBootstraps.fieldVarHandle(lookup(), "next", VarHandle.class, QNode.class, QNode.class); @SuppressWarnings("unused") private volatile QNode next; boolean compareAndSetNext(QNode expect, QNode update) { - return unsafe.compareAndSwapObject(this, nextOffset, expect, update); + return nextHandle.compareAndSet(this, expect, update); } QNode getNext() { @@ -2320,11 +2303,11 @@ void setNext(final QNode node) { } void setNextRelaxed(final QNode node) { - unsafe.putObject(this, nextOffset, node); + nextHandle.set(this, node); } void setNextOrdered(final QNode node) { - unsafe.putOrderedObject(this, nextOffset, node); + nextHandle.setOpaque(this, node); } } @@ -2360,18 +2343,8 @@ static final class PoolThreadNode extends PoolThreadNodeBase { */ private static final int STATE_UNPARKED = 2; - - private static final long taskOffset; - private static final long parkedOffset; - - static { - try { - taskOffset = unsafe.objectFieldOffset(PoolThreadNode.class.getDeclaredField("task")); - parkedOffset = unsafe.objectFieldOffset(PoolThreadNode.class.getDeclaredField("parked")); - } catch (NoSuchFieldException e) { - throw new NoSuchFieldError(e.getMessage()); - } - } + private static final VarHandle taskHandle = ConstantBootstraps.fieldVarHandle(lookup(), "task", VarHandle.class, PoolThreadNode.class, Runnable.class); + private static final VarHandle parkedHandle = ConstantBootstraps.fieldVarHandle(lookup(), "parked", VarHandle.class, PoolThreadNode.class, int.class); private final Thread thread; @@ -2390,7 +2363,7 @@ static final class PoolThreadNode extends PoolThreadNodeBase { } boolean compareAndSetTask(final Runnable expect, final Runnable update) { - return task == expect && unsafe.compareAndSwapObject(this, taskOffset, expect, update); + return task == expect && taskHandle.compareAndSet(this, expect, update); } Runnable getTask() { @@ -2403,7 +2376,7 @@ PoolThreadNode getNext() { void park(EnhancedQueueExecutor enhancedQueueExecutor) { int spins = PARK_SPINS; - if (parked == STATE_UNPARKED && unsafe.compareAndSwapInt(this, parkedOffset, STATE_UNPARKED, STATE_NORMAL)) { + if (parked == STATE_UNPARKED && parkedHandle.compareAndSet(this, STATE_UNPARKED, STATE_NORMAL)) { return; } while (spins > 0) { @@ -2413,11 +2386,11 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor) { Thread.onSpinWait(); } spins--; - if (parked == STATE_UNPARKED && unsafe.compareAndSwapInt(this, parkedOffset, STATE_UNPARKED, STATE_NORMAL)) { + if (parked == STATE_UNPARKED && parkedHandle.compareAndSet(this, STATE_UNPARKED, STATE_NORMAL)) { return; } } - if (parked == STATE_NORMAL && unsafe.compareAndSwapInt(this, parkedOffset, STATE_NORMAL, STATE_PARKED)) try { + if (parked == STATE_NORMAL && parkedHandle.compareAndSet(this, STATE_NORMAL, STATE_PARKED)) try { LockSupport.park(enhancedQueueExecutor); } finally { // parked can be STATE_PARKED or STATE_UNPARKED, cannot possibly be STATE_NORMAL. @@ -2436,7 +2409,7 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor, long nanos) { //as spin time is short and for our use cases it does not matter if the time //overruns a bit (as the nano time is for thread timeout) we just spin then check //to keep performance consistent between the two versions. - if (parked == STATE_UNPARKED && unsafe.compareAndSwapInt(this, parkedOffset, STATE_UNPARKED, STATE_NORMAL)) { + if (parked == STATE_UNPARKED && parkedHandle.compareAndSet(this, STATE_UNPARKED, STATE_NORMAL)) { return; } while (spins > 0) { @@ -2445,7 +2418,7 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor, long nanos) { } else { Thread.onSpinWait(); } - if (parked == STATE_UNPARKED && unsafe.compareAndSwapInt(this, parkedOffset, STATE_UNPARKED, STATE_NORMAL)) { + if (parked == STATE_UNPARKED && parkedHandle.compareAndSet(this, STATE_UNPARKED, STATE_NORMAL)) { return; } spins--; @@ -2457,7 +2430,7 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor, long nanos) { } else { remaining = nanos; } - if (parked == STATE_NORMAL && unsafe.compareAndSwapInt(this, parkedOffset, STATE_NORMAL, STATE_PARKED)) try { + if (parked == STATE_NORMAL && parkedHandle.compareAndSet(this, STATE_NORMAL, STATE_PARKED)) try { LockSupport.parkNanos(enhancedQueueExecutor, remaining); } finally { // parked can be STATE_PARKED or STATE_UNPARKED, cannot possibly be STATE_NORMAL. @@ -2468,7 +2441,7 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor, long nanos) { } void unpark() { - if (parked == STATE_NORMAL && unsafe.compareAndSwapInt(this, parkedOffset, STATE_NORMAL, STATE_UNPARKED)) { + if (parked == STATE_NORMAL && parkedHandle.compareAndSet(this, STATE_NORMAL, STATE_UNPARKED)) { return; } LockSupport.unpark(thread); diff --git a/src/main/java/org/jboss/threads/EnhancedViewExecutor.java b/src/main/java/org/jboss/threads/EnhancedViewExecutor.java index 5118d8bf..908d07fc 100644 --- a/src/main/java/org/jboss/threads/EnhancedViewExecutor.java +++ b/src/main/java/org/jboss/threads/EnhancedViewExecutor.java @@ -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; @@ -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 @@ -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); @@ -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() { diff --git a/src/main/java/org/jboss/threads/JBossExecutors.java b/src/main/java/org/jboss/threads/JBossExecutors.java index 8b8f00cb..b4aca73a 100644 --- a/src/main/java/org/jboss/threads/JBossExecutors.java +++ b/src/main/java/org/jboss/threads/JBossExecutors.java @@ -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"); @@ -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() { @@ -294,31 +289,6 @@ 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() { - 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. * @@ -326,7 +296,7 @@ public Unsafe run() { * @return the context class loader */ static ClassLoader getContextClassLoader(final Thread thread) { - return (ClassLoader) unsafe.getObject(thread, contextClassLoaderOffs); + return JDKSpecific.getThreadContextClassLoader(thread); } /** @@ -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; } /** @@ -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); } /** @@ -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); } // ================================================== diff --git a/src/main/java/org/jboss/threads/JDKSpecific.java b/src/main/java/org/jboss/threads/JDKSpecific.java new file mode 100644 index 00000000..d4eaa1f4 --- /dev/null +++ b/src/main/java/org/jboss/threads/JDKSpecific.java @@ -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() { + 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); + } + } +} diff --git a/src/main/java/org/jboss/threads/ThreadLocalResettingRunnable.java b/src/main/java/org/jboss/threads/ThreadLocalResettingRunnable.java index 5536ef32..123fefe7 100644 --- a/src/main/java/org/jboss/threads/ThreadLocalResettingRunnable.java +++ b/src/main/java/org/jboss/threads/ThreadLocalResettingRunnable.java @@ -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); - } - } } diff --git a/src/main/java24/org/jboss/threads/JDKSpecific.java b/src/main/java24/org/jboss/threads/JDKSpecific.java new file mode 100644 index 00000000..6467e5c0 --- /dev/null +++ b/src/main/java24/org/jboss/threads/JDKSpecific.java @@ -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); + } + } + } +}