From 41fa48ec77bb626d4b8e010586428761a6582c38 Mon Sep 17 00:00:00 2001 From: Elijah Date: Fri, 4 Sep 2020 16:58:46 +0800 Subject: [PATCH 1/2] =?UTF-8?q?DEC-14578=20=E5=BC=95=E5=85=A5com.alibaba:t?= =?UTF-8?q?ransmittable-thread-local?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fine-transmittable-thread-local/pom.xml | 41 + .../alibaba/ttl/TransmittableThreadLocal.java | 754 ++++++++++++++++++ .../com/fr/third/alibaba/ttl/TtlCallable.java | 262 ++++++ .../com/fr/third/alibaba/ttl/TtlCopier.java | 29 + .../com/fr/third/alibaba/ttl/TtlEnhanced.java | 15 + .../third/alibaba/ttl/TtlRecursiveAction.java | 63 ++ .../third/alibaba/ttl/TtlRecursiveTask.java | 72 ++ .../com/fr/third/alibaba/ttl/TtlRunnable.java | 262 ++++++ .../fr/third/alibaba/ttl/TtlTimerTask.java | 193 +++++ .../com/fr/third/alibaba/ttl/TtlUnwrap.java | 70 ++ .../com/fr/third/alibaba/ttl/TtlWrappers.java | 342 ++++++++ .../fr/third/alibaba/ttl/package-info.java | 9 + .../third/alibaba/ttl/spi/TtlAttachments.java | 39 + .../ttl/spi/TtlAttachmentsDelegate.java | 31 + .../fr/third/alibaba/ttl/spi/TtlEnhanced.java | 20 + .../fr/third/alibaba/ttl/spi/TtlWrapper.java | 26 + .../third/alibaba/ttl/spi/package-info.java | 9 + ...nheritableForkJoinWorkerThreadFactory.java | 21 + ...bleForkJoinWorkerThreadFactoryWrapper.java | 58 ++ .../DisableInheritableThreadFactory.java | 22 + ...isableInheritableThreadFactoryWrapper.java | 56 ++ .../threadpool/ExecutorServiceTtlWrapper.java | 101 +++ .../ttl/threadpool/ExecutorTtlWrapper.java | 56 ++ .../ScheduledExecutorServiceTtlWrapper.java | 61 ++ .../alibaba/ttl/threadpool/TtlExecutors.java | 171 ++++ .../ttl/threadpool/TtlForkJoinPoolHelper.java | 73 ++ .../ttl/threadpool/agent/TtlAgent.java | 245 ++++++ .../ttl/threadpool/agent/TtlTransformer.java | 73 ++ .../agent/internal/logging/Logger.java | 80 ++ .../internal/transformlet/ClassInfo.java | 66 ++ .../transformlet/JavassistTransformlet.java | 17 + .../impl/TtlExecutorTransformlet.java | 164 ++++ .../impl/TtlForkJoinTransformlet.java | 85 ++ .../impl/TtlTimerTaskTransformlet.java | 71 ++ .../internal/transformlet/impl/Utils.java | 119 +++ .../internal/transformlet/package-info.java | 7 + .../ttl/threadpool/agent/package-info.java | 8 + .../alibaba/ttl/threadpool/package-info.java | 6 + 38 files changed, 3797 insertions(+) create mode 100644 fine-transmittable-thread-local/pom.xml create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TransmittableThreadLocal.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlCallable.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlCopier.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlEnhanced.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRecursiveAction.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRecursiveTask.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRunnable.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlTimerTask.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlUnwrap.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlWrappers.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/package-info.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlAttachments.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlAttachmentsDelegate.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlEnhanced.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlWrapper.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/package-info.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactory.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactoryWrapper.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableThreadFactory.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableThreadFactoryWrapper.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ExecutorServiceTtlWrapper.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ExecutorTtlWrapper.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ScheduledExecutorServiceTtlWrapper.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/TtlExecutors.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/TtlForkJoinPoolHelper.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/TtlAgent.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/TtlTransformer.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/logging/Logger.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/ClassInfo.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/JavassistTransformlet.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlExecutorTransformlet.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlForkJoinTransformlet.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlTimerTaskTransformlet.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/Utils.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/package-info.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/package-info.java create mode 100644 fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/package-info.java diff --git a/fine-transmittable-thread-local/pom.xml b/fine-transmittable-thread-local/pom.xml new file mode 100644 index 000000000..4d324253f --- /dev/null +++ b/fine-transmittable-thread-local/pom.xml @@ -0,0 +1,41 @@ + + 4.0.0 + + + com.fr.third + step8 + 10.0-FEATURE-SNAPSHOT + ../base-third-project/base-third-step8 + + + fine-transmittable-thread-local + ${revision} + + + + + + com.fr.third + fine-javassist + ${revision} + + + + com.fr.third + fine-jetbrains + ${revision} + + + + + diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TransmittableThreadLocal.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TransmittableThreadLocal.java new file mode 100644 index 000000000..f4d423503 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TransmittableThreadLocal.java @@ -0,0 +1,754 @@ +package com.fr.third.alibaba.ttl; + +import com.fr.third.alibaba.ttl.threadpool.TtlExecutors; +import com.fr.third.alibaba.ttl.threadpool.TtlForkJoinPoolHelper; +import com.fr.third.alibaba.ttl.threadpool.agent.TtlAgent; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.Callable; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * {@link TransmittableThreadLocal} can transmit value from the thread of submitting task to the thread of executing task. + *

+ * Note:
+ * {@link TransmittableThreadLocal} extends {@link InheritableThreadLocal}, + * so {@link TransmittableThreadLocal} first is a {@link InheritableThreadLocal}.
+ * If the inheritable ability from {@link InheritableThreadLocal} has potential leaking problem, + * you can disable the inheritable ability: + *

+ * ❶ by wrapping thread factory using method + * {@link TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory)} / + * {@link TtlForkJoinPoolHelper#getDefaultDisableInheritableForkJoinWorkerThreadFactory()} + * for thread pooling components({@link java.util.concurrent.ThreadPoolExecutor}, {@link java.util.concurrent.ForkJoinPool}). + * Inheritable feature in thread pooling components should never happen, + * because threads in thread pooling components is pre-created and pooled, these threads is neutral for biz logic/data. + *
+ * You can turn on "disable inheritable for thread pool" by {@link TtlAgent} + * so as to wrap thread factory for thread pooling components ({@link java.util.concurrent.ThreadPoolExecutor}, + * {@link java.util.concurrent.ForkJoinPool}) automatically and transparently. + *

+ * ❷ or by overriding method {@link #childValue(Object)}. + * Whether the value should be inheritable or not can be controlled by the data owner, + * disable it carefully when data owner have a clear idea. + *

 {@code TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal() {
+ *     protected String childValue(String parentValue) {
+ *         return initialValue();
+ *     }
+ * }}
+ *

+ * More discussion about "disable the inheritable ability" + * see + * issue #100: disable Inheritable when it's not necessary and buggy. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @author Yang Fang (snoop dot fy at gmail dot com) + * @see TtlRunnable + * @see TtlCallable + * @see TtlExecutors + * @see TtlExecutors#getTtlExecutor(java.util.concurrent.Executor) + * @see TtlExecutors#getTtlExecutorService(java.util.concurrent.ExecutorService) + * @see TtlExecutors#getTtlScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) + * @see TtlExecutors#getDefaultDisableInheritableThreadFactory() + * @see TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) + * @see TtlForkJoinPoolHelper + * @see TtlForkJoinPoolHelper#getDefaultDisableInheritableForkJoinWorkerThreadFactory() + * @see TtlForkJoinPoolHelper#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) + * @see TtlAgent + * @since 0.10.0 + */ +public class TransmittableThreadLocal extends InheritableThreadLocal implements TtlCopier { + private static final Logger logger = Logger.getLogger(TransmittableThreadLocal.class.getName()); + + private final boolean disableIgnoreNullValueSemantics; + + /** + * Default constructor. + *

+ * Create a {@link TransmittableThreadLocal} instance with "Ignore-Null-Value Semantics". + * About "Ignore-Null-Value Semantics": + *

+ *

    + *
  1. If value is {@code null}(check by {@link #get()} method), do NOT transmit this {@code ThreadLocal}.
  2. + *
  3. If set {@code null} value, also remove value(invoke {@link #remove()} method).
  4. + *
+ *

+ * This is a pragmatic design decision: + *

    + *
  1. use explicit value type rather than {@code null} to biz intent.
  2. + *
  3. more safe(avoid {@code NPE}) and gc friendly.
  4. + *
+ *

+ * So it's not recommended to use {@code null} value. + *

+ * But the behavior of "Ignore-Null-Value Semantics" is NOT compatible with + * {@link ThreadLocal} and {@link InheritableThreadLocal}, + * you can disable this behavior/semantics via using constructor {@link #TransmittableThreadLocal(boolean)} + * and set parameter {@code disableIgnoreNullValueSemantics} instead. + *

+ * More info see Issue #157. + * + * @see #TransmittableThreadLocal(boolean) + */ + public TransmittableThreadLocal() { + this(false); + } + + /** + * Constructor, create a {@link TransmittableThreadLocal} with parameter {@code disableIgnoreNullValueSemantics} + * to control "Ignore-Null-Value Semantics". + * + * @param disableIgnoreNullValueSemantics disable "Ignore-Null-Value Semantics" + * @see #TransmittableThreadLocal() + * @since 2.11.3 + */ + public TransmittableThreadLocal(boolean disableIgnoreNullValueSemantics) { + this.disableIgnoreNullValueSemantics = disableIgnoreNullValueSemantics; + } + + /** + * Computes the value for this transmittable thread-local variable + * as a function of the source thread's value at the time the task + * Object is created. + *

+ * This method is called from {@link TtlRunnable} or + * {@link TtlCallable} when it create, before the task is started. + *

+ * This method merely returns reference of its source thread value(the shadow copy), + * and should be overridden if a different behavior is desired. + * + * @since 1.0.0 + */ + public T copy(T parentValue) { + return parentValue; + } + + /** + * Callback method before task object({@link TtlRunnable}/{@link TtlCallable}) execute. + *

+ * Default behavior is to do nothing, and should be overridden + * if a different behavior is desired. + *

+ * Do not throw any exception, just ignored. + * + * @since 1.2.0 + */ + protected void beforeExecute() { + } + + /** + * Callback method after task object({@link TtlRunnable}/{@link TtlCallable}) execute. + *

+ * Default behavior is to do nothing, and should be overridden + * if a different behavior is desired. + *

+ * Do not throw any exception, just ignored. + * + * @since 1.2.0 + */ + protected void afterExecute() { + } + + /** + * see {@link InheritableThreadLocal#get()} + */ + @Override + public final T get() { + T value = super.get(); + if (disableIgnoreNullValueSemantics || null != value) addThisToHolder(); + return value; + } + + /** + * see {@link InheritableThreadLocal#set} + */ + @Override + public final void set(T value) { + if (!disableIgnoreNullValueSemantics && null == value) { + // may set null to remove value + remove(); + } else { + super.set(value); + addThisToHolder(); + } + } + + /** + * see {@link InheritableThreadLocal#remove()} + */ + @Override + public final void remove() { + removeThisFromHolder(); + super.remove(); + } + + private void superRemove() { + super.remove(); + } + + private T copyValue() { + return copy(get()); + } + + // Note about the holder: + // 1. holder self is a InheritableThreadLocal(a *ThreadLocal*). + // 2. The type of value in the holder is WeakHashMap, ?>. + // 2.1 but the WeakHashMap is used as a *Set*: + // - the value of WeakHashMap is *always null, + // - and be never used. + // 2.2 WeakHashMap support *null* value. + private static InheritableThreadLocal, ?>> holder = + new InheritableThreadLocal, ?>>() { + @Override + protected WeakHashMap, ?> initialValue() { + return new WeakHashMap, Object>(); + } + + @Override + protected WeakHashMap, ?> childValue(WeakHashMap, ?> parentValue) { + return new WeakHashMap, Object>(parentValue); + } + }; + + @SuppressWarnings("unchecked") + private void addThisToHolder() { + if (!holder.get().containsKey(this)) { + holder.get().put((TransmittableThreadLocal) this, null); // WeakHashMap supports null value. + } + } + + private void removeThisFromHolder() { + holder.get().remove(this); + } + + private static void doExecuteCallback(boolean isBefore) { + for (TransmittableThreadLocal threadLocal : holder.get().keySet()) { + try { + if (isBefore) threadLocal.beforeExecute(); + else threadLocal.afterExecute(); + } catch (Throwable t) { + if (logger.isLoggable(Level.WARNING)) { + logger.log(Level.WARNING, "TTL exception when " + (isBefore ? "beforeExecute" : "afterExecute") + ", cause: " + t.toString(), t); + } + } + } + } + + /** + * Debug only method! + */ + static void dump(@Nullable String title) { + if (title != null && title.length() > 0) { + System.out.printf("Start TransmittableThreadLocal[%s] Dump...%n", title); + } else { + System.out.println("Start TransmittableThreadLocal Dump..."); + } + + for (TransmittableThreadLocal threadLocal : holder.get().keySet()) { + System.out.println(threadLocal.get()); + } + System.out.println("TransmittableThreadLocal Dump end!"); + } + + /** + * Debug only method! + */ + static void dump() { + dump(null); + } + + /** + * {@link Transmitter} transmit all {@link TransmittableThreadLocal} + * and registered {@link ThreadLocal}(registered by {@link Transmitter#registerThreadLocal}) + * values of the current thread to other thread by static methods + * {@link #capture()} => {@link #replay(Object)} => {@link #restore(Object)} (aka {@code CRR} operation). + *

+ * {@link Transmitter} is internal manipulation api for framework/middleware integration; + * In general, you will never use it in the biz/application code! + * + *

Framework/Middleware integration to TTL transmittance

+ * Below is the example code: + * + *

+     * ///////////////////////////////////////////////////////////////////////////
+     * // in thread A, capture all TransmittableThreadLocal values of thread A
+     * ///////////////////////////////////////////////////////////////////////////
+     *
+     * Object captured = Transmitter.capture(); // (1)
+     *
+     * ///////////////////////////////////////////////////////////////////////////
+     * // in thread B
+     * ///////////////////////////////////////////////////////////////////////////
+     *
+     * // replay all TransmittableThreadLocal values from thread A
+     * Object backup = Transmitter.replay(captured); // (2)
+     * try {
+     *     // your biz logic, run with the TransmittableThreadLocal values of thread B
+     *     System.out.println("Hello");
+     *     // ...
+     *     return "World";
+     * } finally {
+     *     // restore the TransmittableThreadLocal of thread B when replay
+     *     Transmitter.restore(backup); (3)
+     * }
+     * 
+ *

+ * see the implementation code of {@link TtlRunnable} and {@link TtlCallable} for more actual code sample. + *

+ * Of course, {@link #replay(Object)} and {@link #restore(Object)} operation can be simplified by util methods + * {@link #runCallableWithCaptured(Object, Callable)} or {@link #runSupplierWithCaptured(Object, Supplier)} + * and the adorable {@code Java 8 lambda syntax}. + *

+ * Below is the example code: + * + *


+     * ///////////////////////////////////////////////////////////////////////////
+     * // in thread A, capture all TransmittableThreadLocal values of thread A
+     * ///////////////////////////////////////////////////////////////////////////
+     *
+     * Object captured = Transmitter.capture(); // (1)
+     *
+     * ///////////////////////////////////////////////////////////////////////////
+     * // in thread B
+     * ///////////////////////////////////////////////////////////////////////////
+     *
+     * String result = runSupplierWithCaptured(captured, () -> {
+     *      // your biz logic, run with the TransmittableThreadLocal values of thread A
+     *      System.out.println("Hello");
+     *      ...
+     *      return "World";
+     * }); // (2) + (3)
+     * 
+ *

+ * The reason of providing 2 util methods is the different {@code throws Exception} type + * so as to satisfy your biz logic({@code lambda}): + *

    + *
  1. {@link #runCallableWithCaptured(Object, Callable)}: {@code throws Exception}
  2. + *
  3. {@link #runSupplierWithCaptured(Object, Supplier)}: No {@code throws}
  4. + *
+ *

+ * If you need the different {@code throws Exception} type, + * you can define your own util method(function interface({@code lambda})) with your own {@code throws Exception} type. + * + *

ThreadLocal Integration

+ * If you can not rewrite the existed code which use {@link ThreadLocal} to {@link TransmittableThreadLocal}, + * register the {@link ThreadLocal} instances via the methods + * {@link #registerThreadLocal(ThreadLocal, TtlCopier)}/{@link #registerThreadLocalWithShadowCopier(ThreadLocal)} + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + *

+ * Below is the example code: + * + *


+     * // the value of this ThreadLocal instance will be transmitted after registered
+     * Transmitter.registerThreadLocal(aThreadLocal, copyLambda);
+     *
+     * // Then the value of this ThreadLocal instance will not be transmitted after unregistered
+     * Transmitter.unregisterThreadLocal(aThreadLocal);
+     * 
+ * + * Caution:
+ * If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, + * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! + * + * @author Yang Fang (snoop dot fy at gmail dot com) + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlRunnable + * @see TtlCallable + * @since 2.3.0 + */ + public static class Transmitter { + /** + * Capture all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread. + * + * @return the captured {@link TransmittableThreadLocal} values + * @since 2.3.0 + */ + @NotNull + public static Object capture() { + return new Snapshot(captureTtlValues(), captureThreadLocalValues()); + } + + private static HashMap, Object> captureTtlValues() { + HashMap, Object> ttl2Value = new HashMap, Object>(); + for (TransmittableThreadLocal threadLocal : holder.get().keySet()) { + ttl2Value.put(threadLocal, threadLocal.copyValue()); + } + return ttl2Value; + } + + private static HashMap, Object> captureThreadLocalValues() { + final HashMap, Object> threadLocal2Value = new HashMap, Object>(); + for (Map.Entry, TtlCopier> entry : threadLocalHolder.entrySet()) { + final ThreadLocal threadLocal = entry.getKey(); + final TtlCopier copier = entry.getValue(); + + threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get())); + } + return threadLocal2Value; + } + + /** + * Replay the captured {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values from {@link #capture()}, + * and return the backup {@link TransmittableThreadLocal} values in the current thread before replay. + * + * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} + * @return the backup {@link TransmittableThreadLocal} values before replay + * @see #capture() + * @since 2.3.0 + */ + @NotNull + public static Object replay(@NotNull Object captured) { + final Snapshot capturedSnapshot = (Snapshot) captured; + return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value)); + } + + @NotNull + private static HashMap, Object> replayTtlValues(@NotNull HashMap, Object> captured) { + HashMap, Object> backup = new HashMap, Object>(); + + for (final Iterator> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { + TransmittableThreadLocal threadLocal = iterator.next(); + + // backup + backup.put(threadLocal, threadLocal.get()); + + // clear the TTL values that is not in captured + // avoid the extra TTL values after replay when run task + if (!captured.containsKey(threadLocal)) { + iterator.remove(); + threadLocal.superRemove(); + } + } + + // set TTL values to captured + setTtlValuesTo(captured); + + // call beforeExecute callback + doExecuteCallback(true); + + return backup; + } + + private static HashMap, Object> replayThreadLocalValues(@NotNull HashMap, Object> captured) { + final HashMap, Object> backup = new HashMap, Object>(); + + for (Map.Entry, Object> entry : captured.entrySet()) { + final ThreadLocal threadLocal = entry.getKey(); + backup.put(threadLocal, threadLocal.get()); + + final Object value = entry.getValue(); + if (value == threadLocalClearMark) threadLocal.remove(); + else threadLocal.set(value); + } + + return backup; + } + + /** + * Clear all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread, + * and return the backup {@link TransmittableThreadLocal} values in the current thread before clear. + * + * @return the backup {@link TransmittableThreadLocal} values before clear + * @since 2.9.0 + */ + @NotNull + public static Object clear() { + final HashMap, Object> ttl2Value = new HashMap, Object>(); + + final HashMap, Object> threadLocal2Value = new HashMap, Object>(); + for (Map.Entry, TtlCopier> entry : threadLocalHolder.entrySet()) { + final ThreadLocal threadLocal = entry.getKey(); + threadLocal2Value.put(threadLocal, threadLocalClearMark); + } + + return replay(new Snapshot(ttl2Value, threadLocal2Value)); + } + + /** + * Restore the backup {@link TransmittableThreadLocal} and + * registered {@link ThreadLocal} values from {@link #replay(Object)}/{@link #clear()}. + * + * @param backup the backup {@link TransmittableThreadLocal} values from {@link #replay(Object)}/{@link #clear()} + * @see #replay(Object) + * @see #clear() + * @since 2.3.0 + */ + public static void restore(@NotNull Object backup) { + final Snapshot backupSnapshot = (Snapshot) backup; + restoreTtlValues(backupSnapshot.ttl2Value); + restoreThreadLocalValues(backupSnapshot.threadLocal2Value); + } + + private static void restoreTtlValues(@NotNull HashMap, Object> backup) { + // call afterExecute callback + doExecuteCallback(false); + + for (final Iterator> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { + TransmittableThreadLocal threadLocal = iterator.next(); + + // clear the TTL values that is not in backup + // avoid the extra TTL values after restore + if (!backup.containsKey(threadLocal)) { + iterator.remove(); + threadLocal.superRemove(); + } + } + + // restore TTL values + setTtlValuesTo(backup); + } + + private static void setTtlValuesTo(@NotNull HashMap, Object> ttlValues) { + for (Map.Entry, Object> entry : ttlValues.entrySet()) { + TransmittableThreadLocal threadLocal = entry.getKey(); + threadLocal.set(entry.getValue()); + } + } + + private static void restoreThreadLocalValues(@NotNull HashMap, Object> backup) { + for (Map.Entry, Object> entry : backup.entrySet()) { + final ThreadLocal threadLocal = entry.getKey(); + threadLocal.set(entry.getValue()); + } + } + + private static class Snapshot { + final HashMap, Object> ttl2Value; + final HashMap, Object> threadLocal2Value; + + private Snapshot(HashMap, Object> ttl2Value, HashMap, Object> threadLocal2Value) { + this.ttl2Value = ttl2Value; + this.threadLocal2Value = threadLocal2Value; + } + } + + /** + * Util method for simplifying {@link #replay(Object)} and {@link #restore(Object)} operation. + * + * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} + * @param bizLogic biz logic + * @param the return type of biz logic + * @return the return value of biz logic + * @see #capture() + * @see #replay(Object) + * @see #restore(Object) + * @since 2.3.1 + */ + public static R runSupplierWithCaptured(@NotNull Object captured, @NotNull Supplier bizLogic) { + Object backup = replay(captured); + try { + return bizLogic.get(); + } finally { + restore(backup); + } + } + + /** + * Util method for simplifying {@link #clear()} and {@link #restore(Object)} operation. + * + * @param bizLogic biz logic + * @param the return type of biz logic + * @return the return value of biz logic + * @see #clear() + * @see #restore(Object) + * @since 2.9.0 + */ + public static R runSupplierWithClear(@NotNull Supplier bizLogic) { + Object backup = clear(); + try { + return bizLogic.get(); + } finally { + restore(backup); + } + } + + /** + * Util method for simplifying {@link #replay(Object)} and {@link #restore(Object)} operation. + * + * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} + * @param bizLogic biz logic + * @param the return type of biz logic + * @return the return value of biz logic + * @throws Exception exception threw by biz logic + * @see #capture() + * @see #replay(Object) + * @see #restore(Object) + * @since 2.3.1 + */ + public static R runCallableWithCaptured(@NotNull Object captured, @NotNull Callable bizLogic) throws Exception { + Object backup = replay(captured); + try { + return bizLogic.call(); + } finally { + restore(backup); + } + } + + /** + * Util method for simplifying {@link #clear()} and {@link #restore(Object)} operation. + * + * @param bizLogic biz logic + * @param the return type of biz logic + * @return the return value of biz logic + * @throws Exception exception threw by biz logic + * @see #clear() + * @see #restore(Object) + * @since 2.9.0 + */ + public static R runCallableWithClear(@NotNull Callable bizLogic) throws Exception { + Object backup = clear(); + try { + return bizLogic.call(); + } finally { + restore(backup); + } + } + + private static volatile WeakHashMap, TtlCopier> threadLocalHolder = new WeakHashMap, TtlCopier>(); + private static final Object threadLocalHolderUpdateLock = new Object(); + private static final Object threadLocalClearMark = new Object(); + + /** + * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + *

+ * If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, + * it is unnecessary to register a {@link TransmittableThreadLocal} instance. + * + * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability + * @param copier the {@link TtlCopier} + * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} + * @see #registerThreadLocal(ThreadLocal, TtlCopier, boolean) + * @since 2.11.0 + */ + public static boolean registerThreadLocal(@NotNull ThreadLocal threadLocal, @NotNull TtlCopier copier) { + return registerThreadLocal(threadLocal, copier, false); + } + + /** + * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + *

+ * Use the shadow copier(transmit the reference directly), + * and should use {@link #registerThreadLocal(ThreadLocal, TtlCopier)} to pass a {@link TtlCopier} explicitly + * if a different behavior is desired. + *

+ * If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, + * it is unnecessary to register a {@link TransmittableThreadLocal} instance. + * + * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability + * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} + * @see #registerThreadLocal(ThreadLocal, TtlCopier) + * @see #registerThreadLocal(ThreadLocal, TtlCopier, boolean) + * @since 2.11.0 + */ + @SuppressWarnings("unchecked") + public static boolean registerThreadLocalWithShadowCopier(@NotNull ThreadLocal threadLocal) { + return registerThreadLocal(threadLocal, (TtlCopier) shadowCopier, false); + } + + /** + * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + *

+ * If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, + * it is unnecessary to register a {@link TransmittableThreadLocal} instance. + * + * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability + * @param copier the {@link TtlCopier} + * @param force if {@code true}, update {@code copier} to {@link ThreadLocal} instance + * when a {@link ThreadLocal} instance is already registered; otherwise, ignore. + * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} + * @see #registerThreadLocal(ThreadLocal, TtlCopier) + * @since 2.11.0 + */ + @SuppressWarnings("unchecked") + public static boolean registerThreadLocal(@NotNull ThreadLocal threadLocal, @NotNull TtlCopier copier, boolean force) { + if (threadLocal instanceof TransmittableThreadLocal) { + logger.warning("register a TransmittableThreadLocal instance, this is unnecessary!"); + return true; + } + + synchronized (threadLocalHolderUpdateLock) { + if (!force && threadLocalHolder.containsKey(threadLocal)) return false; + + WeakHashMap, TtlCopier> newHolder = new WeakHashMap, TtlCopier>(threadLocalHolder); + newHolder.put((ThreadLocal) threadLocal, (TtlCopier) copier); + threadLocalHolder = newHolder; + return true; + } + } + + /** + * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + *

+ * Use the shadow copier(transmit the reference directly), + * and should use {@link #registerThreadLocal(ThreadLocal, TtlCopier, boolean)} to pass a {@link TtlCopier} explicitly + * if a different behavior is desired. + *

+ * If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, + * it is unnecessary to register a {@link TransmittableThreadLocal} instance. + * + * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability + * @param force if {@code true}, update {@code copier} to {@link ThreadLocal} instance + * when a {@link ThreadLocal} instance is already registered; otherwise, ignore. + * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} + * @see #registerThreadLocal(ThreadLocal, TtlCopier) + * @see #registerThreadLocal(ThreadLocal, TtlCopier, boolean) + * @since 2.11.0 + */ + @SuppressWarnings("unchecked") + public static boolean registerThreadLocalWithShadowCopier(@NotNull ThreadLocal threadLocal, boolean force) { + return registerThreadLocal(threadLocal, (TtlCopier) shadowCopier, force); + } + + /** + * Unregister the {@link ThreadLocal} instances + * to remove the Transmittable ability for the {@link ThreadLocal} instances. + *

+ * If the {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * + * @see #registerThreadLocal(ThreadLocal, TtlCopier) + * @see #registerThreadLocalWithShadowCopier(ThreadLocal) + * @since 2.11.0 + */ + public static boolean unregisterThreadLocal(@NotNull ThreadLocal threadLocal) { + if (threadLocal instanceof TransmittableThreadLocal) { + logger.warning("unregister a TransmittableThreadLocal instance, this is unnecessary!"); + return true; + } + + synchronized (threadLocalHolderUpdateLock) { + if (!threadLocalHolder.containsKey(threadLocal)) return false; + + WeakHashMap, TtlCopier> newHolder = new WeakHashMap, TtlCopier>(threadLocalHolder); + newHolder.remove(threadLocal); + threadLocalHolder = newHolder; + return true; + } + } + + private static final TtlCopier shadowCopier = new TtlCopier() { + @Override + public Object copy(Object parentValue) { + return parentValue; + } + }; + + private Transmitter() { + throw new InstantiationError("Must not instantiate this class"); + } + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlCallable.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlCallable.java new file mode 100644 index 000000000..c4c937216 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlCallable.java @@ -0,0 +1,262 @@ +package com.fr.third.alibaba.ttl; + +import com.fr.third.alibaba.ttl.spi.TtlAttachments; +import com.fr.third.alibaba.ttl.spi.TtlAttachmentsDelegate; +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.spi.TtlWrapper; +import com.fr.third.alibaba.ttl.threadpool.TtlExecutors; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; + +/** + * {@link TtlCallable} decorate {@link Callable}, so as to get {@link TransmittableThreadLocal} + * and transmit it to the time of {@link Callable} execution, needed when use {@link Callable} to thread pool. + *

+ * Use factory method {@link #get(Callable)} to get decorated instance. + *

+ * Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlExecutors + * @see TtlWrappers + * @see java.util.concurrent.Executor + * @see java.util.concurrent.ExecutorService + * @see java.util.concurrent.ThreadPoolExecutor + * @see java.util.concurrent.ScheduledThreadPoolExecutor + * @see java.util.concurrent.Executors + * @see java.util.concurrent.CompletionService + * @see java.util.concurrent.ExecutorCompletionService + * @since 0.9.0 + */ +public final class TtlCallable implements Callable, TtlWrapper>, TtlEnhanced, TtlAttachments { + private final AtomicReference capturedRef; + private final Callable callable; + private final boolean releaseTtlValueReferenceAfterCall; + + private TtlCallable(@NotNull Callable callable, boolean releaseTtlValueReferenceAfterCall) { + this.capturedRef = new AtomicReference(capture()); + this.callable = callable; + this.releaseTtlValueReferenceAfterCall = releaseTtlValueReferenceAfterCall; + } + + /** + * wrap method {@link Callable#call()}. + */ + @Override + public V call() throws Exception { + Object captured = capturedRef.get(); + if (captured == null || releaseTtlValueReferenceAfterCall && !capturedRef.compareAndSet(captured, null)) { + throw new IllegalStateException("TTL value reference is released after call!"); + } + + Object backup = replay(captured); + try { + return callable.call(); + } finally { + restore(backup); + } + } + + /** + * return the original/underneath {@link Callable}. + */ + @NotNull + public Callable getCallable() { + return unwrap(); + } + + /** + * unwrap to the original/underneath {@link Callable}. + * + * @see TtlUnwrap#unwrap(Object) + * @since 2.11.4 + */ + @NotNull + @Override + public Callable unwrap() { + return callable; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlCallable that = (TtlCallable) o; + + return callable.equals(that.callable); + } + + @Override + public int hashCode() { + return callable.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + callable.toString(); + } + + /** + * Factory method, wrap input {@link Callable} to {@link TtlCallable}. + *

+ * This method is idempotent. + * + * @param callable input {@link Callable} + * @return Wrapped {@link Callable} + */ + @Nullable + public static TtlCallable get(@Nullable Callable callable) { + return get(callable, false); + } + + + /** + * Factory method, wrap input {@link Callable} to {@link TtlCallable}. + *

+ * This method is idempotent. + * + * @param callable input {@link Callable} + * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @return Wrapped {@link Callable} + */ + @Nullable + public static TtlCallable get(@Nullable Callable callable, boolean releaseTtlValueReferenceAfterCall) { + return get(callable, releaseTtlValueReferenceAfterCall, false); + } + + /** + * Factory method, wrap input {@link Callable} to {@link TtlCallable}. + *

+ * This method is idempotent. + * + * @param callable input {@link Callable} + * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @param idempotent is idempotent or not. {@code true} will cover up bugs! DO NOT set, only when you know why. + * @return Wrapped {@link Callable} + */ + @Nullable + public static TtlCallable get(@Nullable Callable callable, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) { + if (null == callable) return null; + + if (callable instanceof TtlEnhanced) { + // avoid redundant decoration, and ensure idempotency + if (idempotent) return (TtlCallable) callable; + else throw new IllegalStateException("Already TtlCallable!"); + } + return new TtlCallable(callable, releaseTtlValueReferenceAfterCall); + } + + /** + * wrap input {@link Callable} Collection to {@link TtlCallable} Collection. + * + * @param tasks task to be wrapped + * @return Wrapped {@link Callable} + */ + @NotNull + public static List> gets(@Nullable Collection> tasks) { + return gets(tasks, false, false); + } + + /** + * wrap input {@link Callable} Collection to {@link TtlCallable} Collection. + * + * @param tasks task to be wrapped + * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @return Wrapped {@link Callable} + */ + @NotNull + public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterCall) { + return gets(tasks, releaseTtlValueReferenceAfterCall, false); + } + + /** + * wrap input {@link Callable} Collection to {@link TtlCallable} Collection. + * + * @param tasks task to be wrapped + * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @param idempotent is idempotent or not. {@code true} will cover up bugs! DO NOT set, only when you know why. + * @return Wrapped {@link Callable} + */ + @NotNull + public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) { + if (null == tasks) return Collections.emptyList(); + + List> copy = new ArrayList>(); + for (Callable task : tasks) { + copy.add(TtlCallable.get(task, releaseTtlValueReferenceAfterCall, idempotent)); + } + return copy; + } + + /** + * Unwrap {@link TtlCallable} to the original/underneath one. + *

+ * this method is {@code null}-safe, when input {@code Callable} parameter is {@code null}, return {@code null}; + * if input {@code Callable} parameter is not a {@link TtlCallable} just return input {@code Callable}. + *

+ * so {@code TtlCallable.unwrap(TtlCallable.get(callable))} will always return the same input {@code callable} object. + * + * @see #get(Callable) + * @since 2.10.2 + */ + @Nullable + public static Callable unwrap(@Nullable Callable callable) { + if (!(callable instanceof TtlCallable)) return callable; + else return ((TtlCallable) callable).getCallable(); + } + + /** + * Unwrap {@link TtlCallable} to the original/underneath one. + *

+ * Invoke {@link #unwrap(Callable)} for each element in input collection. + *

+ * This method is {@code null}-safe, when input {@code Callable} parameter is {@code null}, return a empty list. + * + * @see #gets(Collection) + * @see #unwrap(Callable) + * @since 2.10.2 + */ + @NotNull + public static List> unwraps(@Nullable Collection> tasks) { + if (null == tasks) return Collections.emptyList(); + + List> copy = new ArrayList>(); + for (Callable task : tasks) { + if (!(task instanceof TtlCallable)) copy.add(task); + else copy.add(((TtlCallable) task).getCallable()); + } + return copy; + } + + private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); + + /** + * see {@link TtlAttachments#setTtlAttachment(String, Object)} + * + * @since 2.11.0 + */ + @Override + public void setTtlAttachment(@NotNull String key, Object value) { + ttlAttachment.setTtlAttachment(key, value); + } + + /** + * see {@link TtlAttachments#getTtlAttachment(String)} + * + * @since 2.11.0 + */ + @Override + public T getTtlAttachment(@NotNull String key) { + return ttlAttachment.getTtlAttachment(key); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlCopier.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlCopier.java new file mode 100644 index 000000000..6135a2531 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlCopier.java @@ -0,0 +1,29 @@ +package com.fr.third.alibaba.ttl; + +/** + * TtlCopier creates the value when {@link TransmittableThreadLocal.Transmitter#capture()}, + * use the created value when {@link TransmittableThreadLocal.Transmitter#replay(Object)} + * + * @see TransmittableThreadLocal.Transmitter + * @see TransmittableThreadLocal.Transmitter#capture() + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 2.11.0 + */ +@FunctionalInterface +public interface TtlCopier { + /** + * Computes the value for {@link TransmittableThreadLocal} + * or registered {@link ThreadLocal}(registered by {@link TransmittableThreadLocal.Transmitter#registerThreadLocal}) + * as a function of the source thread's value at the time the task + * Object is created. + *

+ * This method is called from {@link TtlRunnable} or + * {@link TtlCallable} when it create, before the task is started + * (aka. called when {@link TransmittableThreadLocal.Transmitter#capture()}). + * + * @see TransmittableThreadLocal.Transmitter#registerThreadLocal(ThreadLocal, TtlCopier) + * @see TransmittableThreadLocal.Transmitter#registerThreadLocalWithShadowCopier(ThreadLocal) + * @see TransmittableThreadLocal.Transmitter#unregisterThreadLocal + */ + T copy(T parentValue); +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlEnhanced.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlEnhanced.java new file mode 100644 index 000000000..9849dadcf --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlEnhanced.java @@ -0,0 +1,15 @@ +package com.fr.third.alibaba.ttl; + +import com.fr.third.alibaba.ttl.spi.TtlAttachments; + + +/** + * @see TtlAttachments + * @deprecated Use {@link com.fr.third.alibaba.ttl.spi.TtlEnhanced} instead. + */ +@Deprecated + +// [ERROR] The class name com.alibaba.ttl.TtlEnhanced shadows +// the simple name of implemented interface com.alibaba.ttl.spi.TtlEnhanced [com.alibaba.ttl.TtlEnhanced] +public interface TtlEnhanced extends com.fr.third.alibaba.ttl.spi.TtlEnhanced { +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRecursiveAction.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRecursiveAction.java new file mode 100644 index 000000000..4b0b392a1 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRecursiveAction.java @@ -0,0 +1,63 @@ +package com.fr.third.alibaba.ttl; + +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.threadpool.TtlForkJoinPoolHelper; +import com.fr.third.alibaba.ttl.threadpool.agent.TtlAgent; + +import java.util.concurrent.ForkJoinTask; + +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; + +/** + * A recursive resultless {@link ForkJoinTask} enhanced by {@link TransmittableThreadLocal}. + *

+ * Recommend to use {@link TtlAgent}; + * Specially for {@code Java 8} {@link java.util.stream.Stream} and {@link java.util.concurrent.CompletableFuture}, + * these async task are executed by {@link java.util.concurrent.ForkJoinPool} via {@link ForkJoinTask} at the bottom. + * + * @author LNAmp + * @see java.util.concurrent.RecursiveAction + * @see TtlForkJoinPoolHelper + * @see TtlAgent + * @since 2.4.0 + */ +public abstract class TtlRecursiveAction extends ForkJoinTask implements TtlEnhanced { + + private static final long serialVersionUID = -5753568484583412377L; + + private final Object captured = capture(); + + protected TtlRecursiveAction() { + } + + /** + * The main computation performed by this task. + */ + protected abstract void compute(); + + /** + * see {@link ForkJoinTask#getRawResult()} + */ + public final Void getRawResult() { + return null; + } + + /** + * see {@link ForkJoinTask#setRawResult(Object)} + */ + protected final void setRawResult(Void mustBeNull) { + } + + /** + * Implements execution conventions for RecursiveActions. + */ + protected final boolean exec() { + Object backup = replay(captured); + try { + compute(); + return true; + } finally { + restore(backup); + } + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRecursiveTask.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRecursiveTask.java new file mode 100644 index 000000000..6869c9aba --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRecursiveTask.java @@ -0,0 +1,72 @@ +package com.fr.third.alibaba.ttl; + +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.threadpool.TtlForkJoinPoolHelper; +import com.fr.third.alibaba.ttl.threadpool.agent.TtlAgent; + +import java.util.concurrent.ForkJoinTask; + +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; + +/** + * A recursive result-bearing {@link ForkJoinTask} enhanced by {@link TransmittableThreadLocal}. + *

+ * Recommend to use {@link TtlAgent}; + * Specially for {@code Java 8} {@link java.util.stream.Stream} and {@link java.util.concurrent.CompletableFuture}, + * these async task are executed by {@link java.util.concurrent.ForkJoinPool} via {@link ForkJoinTask} at the bottom. + * + * @author LNAmp + * @see java.util.concurrent.RecursiveTask + * @see TtlForkJoinPoolHelper + * @see TtlAgent + * @since 2.4.0 + */ +public abstract class TtlRecursiveTask extends ForkJoinTask implements TtlEnhanced { + + private static final long serialVersionUID = 1814679366926362436L; + + private final Object captured = capture(); + + protected TtlRecursiveTask() { + } + + /** + * The result of the computation. + */ + V result; + + /** + * The main computation performed by this task. + * + * @return the result of the computation + */ + protected abstract V compute(); + + /** + * see {@link ForkJoinTask#getRawResult()} + */ + public final V getRawResult() { + return result; + } + + /** + * see {@link ForkJoinTask#setRawResult(Object)} + */ + protected final void setRawResult(V value) { + result = value; + } + + /** + * Implements execution conventions for RecursiveTask. + */ + protected final boolean exec() { + Object backup = replay(captured); + try { + result = compute(); + return true; + } finally { + restore(backup); + } + } + +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRunnable.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRunnable.java new file mode 100644 index 000000000..e859150dd --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlRunnable.java @@ -0,0 +1,262 @@ +package com.fr.third.alibaba.ttl; + +import com.fr.third.alibaba.ttl.spi.TtlAttachments; +import com.fr.third.alibaba.ttl.spi.TtlAttachmentsDelegate; +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.spi.TtlWrapper; +import com.fr.third.alibaba.ttl.threadpool.TtlExecutors; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; + +/** + * {@link TtlRunnable} decorate {@link Runnable}, so as to get {@link TransmittableThreadLocal} + * and transmit it to the time of {@link Runnable} execution, needed when use {@link Runnable} to thread pool. + *

+ * Use factory methods {@link #get} / {@link #gets} to create instance. + *

+ * Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlExecutors + * @see TtlWrappers + * @see java.util.concurrent.Executor + * @see java.util.concurrent.ExecutorService + * @see java.util.concurrent.ThreadPoolExecutor + * @see java.util.concurrent.ScheduledThreadPoolExecutor + * @see java.util.concurrent.Executors + * @since 0.9.0 + */ +public final class TtlRunnable implements Runnable, TtlWrapper, TtlEnhanced, TtlAttachments { + private final AtomicReference capturedRef; + private final Runnable runnable; + private final boolean releaseTtlValueReferenceAfterRun; + + private TtlRunnable(@NotNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { + this.capturedRef = new AtomicReference(capture()); + this.runnable = runnable; + this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; + } + + /** + * wrap method {@link Runnable#run()}. + */ + @Override + public void run() { + Object captured = capturedRef.get(); + if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { + throw new IllegalStateException("TTL value reference is released after run!"); + } + + Object backup = replay(captured); + try { + runnable.run(); + } finally { + restore(backup); + } + } + + /** + * return original/unwrapped {@link Runnable}. + */ + @NotNull + public Runnable getRunnable() { + return unwrap(); + } + + /** + * unwrap to original/unwrapped {@link Runnable}. + * + * @see TtlUnwrap#unwrap(Object) + * @since 2.11.4 + */ + @NotNull + @Override + public Runnable unwrap() { + return runnable; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlRunnable that = (TtlRunnable) o; + + return runnable.equals(that.runnable); + } + + @Override + public int hashCode() { + return runnable.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + runnable.toString(); + } + + /** + * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. + * + * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. + * @return Wrapped {@link Runnable} + * @throws IllegalStateException when input is {@link TtlRunnable} already. + */ + @Nullable + public static TtlRunnable get(@Nullable Runnable runnable) { + return get(runnable, false, false); + } + + /** + * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. + * + * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. + * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @return Wrapped {@link Runnable} + * @throws IllegalStateException when input is {@link TtlRunnable} already. + */ + @Nullable + public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { + return get(runnable, releaseTtlValueReferenceAfterRun, false); + } + + /** + * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. + * + * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. + * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Runnable} when it's {@link TtlRunnable}, + * otherwise throw {@link IllegalStateException}. + * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. + * @return Wrapped {@link Runnable} + * @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. + */ + @Nullable + public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { + if (null == runnable) return null; + + if (runnable instanceof TtlEnhanced) { + // avoid redundant decoration, and ensure idempotency + if (idempotent) return (TtlRunnable) runnable; + else throw new IllegalStateException("Already TtlRunnable!"); + } + return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun); + } + + /** + * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. + * + * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. + * @return wrapped tasks + * @throws IllegalStateException when input is {@link TtlRunnable} already. + */ + @NotNull + public static List gets(@Nullable Collection tasks) { + return gets(tasks, false, false); + } + + /** + * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. + * + * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. + * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @return wrapped tasks + * @throws IllegalStateException when input is {@link TtlRunnable} already. + */ + @NotNull + public static List gets(@Nullable Collection tasks, boolean releaseTtlValueReferenceAfterRun) { + return gets(tasks, releaseTtlValueReferenceAfterRun, false); + } + + /** + * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. + * + * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. + * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Runnable} when it's {@link TtlRunnable}, + * otherwise throw {@link IllegalStateException}. + * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. + * @return wrapped tasks + * @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. + */ + @NotNull + public static List gets(@Nullable Collection tasks, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { + if (null == tasks) return Collections.emptyList(); + + List copy = new ArrayList(); + for (Runnable task : tasks) { + copy.add(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent)); + } + return copy; + } + + /** + * Unwrap {@link TtlRunnable} to the original/underneath one. + *

+ * this method is {@code null}-safe, when input {@code Runnable} parameter is {@code null}, return {@code null}; + * if input {@code Runnable} parameter is not a {@link TtlRunnable} just return input {@code Runnable}. + *

+ * so {@code TtlRunnable.unwrap(TtlRunnable.get(runnable))} will always return the same input {@code runnable} object. + * + * @see #get(Runnable) + * @since 2.10.2 + */ + @Nullable + public static Runnable unwrap(@Nullable Runnable runnable) { + if (!(runnable instanceof TtlRunnable)) return runnable; + else return ((TtlRunnable) runnable).getRunnable(); + } + + /** + * Unwrap {@link TtlRunnable} to the original/underneath one for collection. + *

+ * Invoke {@link #unwrap(Runnable)} for each element in input collection. + *

+ * This method is {@code null}-safe, when input {@code Runnable} parameter is {@code null}, return a empty list. + * + * @see #gets(Collection) + * @see #unwrap(Runnable) + * @since 2.10.2 + */ + @NotNull + public static List unwraps(@Nullable Collection tasks) { + if (null == tasks) return Collections.emptyList(); + + List copy = new ArrayList(); + for (Runnable task : tasks) { + if (!(task instanceof TtlRunnable)) copy.add(task); + else copy.add(((TtlRunnable) task).getRunnable()); + } + return copy; + } + + private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); + + /** + * see {@link TtlAttachments#setTtlAttachment(String, Object)} + * + * @since 2.11.0 + */ + @Override + public void setTtlAttachment(@NotNull String key, Object value) { + ttlAttachment.setTtlAttachment(key, value); + } + + /** + * see {@link TtlAttachments#getTtlAttachment(String)} + * + * @since 2.11.0 + */ + @Override + public T getTtlAttachment(@NotNull String key) { + return ttlAttachment.getTtlAttachment(key); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlTimerTask.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlTimerTask.java new file mode 100644 index 000000000..448c8ff12 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlTimerTask.java @@ -0,0 +1,193 @@ +package com.fr.third.alibaba.ttl; + +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.spi.TtlWrapper; +import com.fr.third.alibaba.ttl.threadpool.agent.TtlAgent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; + +/** + * {@link TtlTimerTask} decorate {@link TimerTask}, so as to get {@link TransmittableThreadLocal} + * and transmit it to the time of {@link TtlTimerTask} execution, needed when use {@link TtlTimerTask} to {@link java.util.TimerTask}. + *

+ * Use factory method {@link #get(TimerTask)} to create instance. + *

+ * NOTE: + * The {@link TtlTimerTask} make the method {@link TimerTask#scheduledExecutionTime()} in + * the origin {@link TimerTask} lose effectiveness! Use {@link TtlAgent} instead. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see java.util.Timer + * @see TimerTask + * @see Alibaba Java Coding Guidelines - Concurrency - Item 10: [Mandatory] Run multiple TimeTask by using ScheduledExecutorService rather than Timer because Timer will kill all running threads in case of failing to catch exceptions. + * @see TtlAgent + * @since 0.9.1 + * @deprecated Use {@link TtlRunnable}, {@link java.util.concurrent.ScheduledExecutorService} instead of {@link java.util.Timer}, {@link java.util.TimerTask}. + */ +@Deprecated +public final class TtlTimerTask extends TimerTask implements TtlWrapper, com.fr.third.alibaba.ttl.spi.TtlEnhanced { + private final AtomicReference capturedRef; + private final TimerTask timerTask; + private final boolean releaseTtlValueReferenceAfterRun; + + private TtlTimerTask(@NotNull TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun) { + this.capturedRef = new AtomicReference(capture()); + this.timerTask = timerTask; + this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; + } + + /** + * wrap method {@link TimerTask#run()}. + */ + @Override + public void run() { + Object captured = capturedRef.get(); + if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { + throw new IllegalStateException("TTL value reference is released after run!"); + } + + Object backup = replay(captured); + try { + timerTask.run(); + } finally { + restore(backup); + } + } + + @Override + public boolean cancel() { + timerTask.cancel(); + return super.cancel(); + } + + /** + * return original/unwrapped {@link TimerTask}. + */ + @NotNull + public TimerTask getTimerTask() { + return unwrap(); + } + + /** + * unwrap to original/unwrapped {@link TimerTask}. + * + * @see TtlUnwrap#unwrap(Object) + * @since 2.11.4 + */ + @NotNull + @Override + public TimerTask unwrap() { + return timerTask; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlTimerTask that = (TtlTimerTask) o; + + return timerTask.equals(that.timerTask); + } + + @Override + public int hashCode() { + return timerTask != null ? timerTask.hashCode() : 0; + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + timerTask.toString(); + } + + /** + * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. + *

+ * This method is idempotent. + * + * @param timerTask input {@link TimerTask} + * @return Wrapped {@link TimerTask} + */ + @Nullable + public static TtlTimerTask get(@Nullable TimerTask timerTask) { + return get(timerTask, false, false); + } + + /** + * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. + *

+ * This method is idempotent. + * + * @param timerTask input {@link TimerTask} + * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlTimerTask} is referred. + * @return Wrapped {@link TimerTask} + */ + @Nullable + public static TtlTimerTask get(@Nullable TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun) { + return get(timerTask, releaseTtlValueReferenceAfterRun, false); + } + + /** + * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. + *

+ * This method is idempotent. + * + * @param timerTask input {@link TimerTask} + * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlTimerTask} is referred. + * @param idempotent is idempotent or not. {@code true} will cover up bugs! DO NOT set, only when you know why. + * @return Wrapped {@link TimerTask} + */ + @Nullable + public static TtlTimerTask get(@Nullable TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { + if (null == timerTask) return null; + + if (timerTask instanceof TtlEnhanced) { + // avoid redundant decoration, and ensure idempotency + if (idempotent) return (TtlTimerTask) timerTask; + else throw new IllegalStateException("Already TtlTimerTask!"); + } + return new TtlTimerTask(timerTask, releaseTtlValueReferenceAfterRun); + } + + /** + * Unwrap {@link TtlTimerTask} to the original/underneath one. + *

+ * this method is {@code null}-safe, when input {@code TimerTask} parameter is {@code null}, return {@code null}; + * if input {@code TimerTask} parameter is not a {@link TtlTimerTask} just return input {@code TimerTask}. + * + * @see #get(TimerTask) + * @since 2.10.2 + */ + @Nullable + public static TimerTask unwrap(@Nullable TimerTask timerTask) { + if (!(timerTask instanceof TtlTimerTask)) return timerTask; + else return ((TtlTimerTask) timerTask).getTimerTask(); + } + + /** + * Unwrap {@link TtlTimerTask} to the original/underneath one. + *

+ * Invoke {@link #unwrap(TimerTask)} for each element in input collection. + *

+ * This method is {@code null}-safe, when input {@code TimerTask} parameter is {@code null}, return a empty list. + * + * @see #unwrap(TimerTask) + * @since 2.10.2 + */ + @NotNull + public static List unwraps(@Nullable Collection tasks) { + if (null == tasks) return Collections.emptyList(); + + List copy = new ArrayList(); + for (TimerTask task : tasks) { + if (!(task instanceof TtlTimerTask)) copy.add(task); + else copy.add(((TtlTimerTask) task).getTimerTask()); + } + return copy; + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlUnwrap.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlUnwrap.java new file mode 100644 index 000000000..82a87b63c --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlUnwrap.java @@ -0,0 +1,70 @@ +package com.fr.third.alibaba.ttl; + +import com.fr.third.alibaba.ttl.spi.TtlWrapper; +import com.fr.third.alibaba.ttl.threadpool.TtlExecutors; +import com.fr.third.alibaba.ttl.threadpool.TtlForkJoinPoolHelper; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.Callable; + +/** + * Util methods for TTL Wrapper: unwrap TTL Wrapper and check TTL Wrapper. + *

+ * Note: + * all methods is {@code null}-safe, when input parameter is {@code null}, return {@code null}. + *

+ * Implementation Note: + * The util methods in this class should have been inside {@link TtlWrappers}.
+ * But for {@code Java 6} support, it's required splitting the util methods + * which involved {@code Java 8} from {@link TtlWrappers}. + * In order to avoid loading {@code Java 8} class (eg: {@link java.util.function.Consumer}, {@link java.util.function.Supplier}), + * when invoking any methods of {@link TtlWrappers}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlWrappers + * @see TtlWrapper + * @see TtlRunnable + * @see TtlCallable + * @since 2.11.4 + */ +public class TtlUnwrap { + /** + * Generic unwrap method, unwrap {@code TtlWrapper} to the original/underneath one. + *

+ * this method is {@code null}-safe, when input {@code BiFunction} parameter is {@code null}, return {@code null}; + * if input parameter is not a {@code TtlWrapper} just return input. + *

+ * so {@code unwrap} will always return the same input object. + * + * @see TtlWrappers#wrap(java.util.function.Supplier) + * @see TtlWrappers#wrap(java.util.function.Consumer) + * @see TtlWrappers#wrap(java.util.function.BiConsumer) + * @see TtlWrappers#wrap(java.util.function.Function) + * @see TtlWrappers#wrap(java.util.function.BiFunction) + * @see TtlRunnable#unwrap(Runnable) + * @see TtlCallable#unwrap(Callable) + * @see TtlExecutors#unwrap(java.util.concurrent.Executor) + * @see TtlExecutors#unwrap(java.util.concurrent.ThreadFactory) + * @see TtlForkJoinPoolHelper#unwrap(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) + * @since 2.11.4 + */ + @Nullable + @SuppressWarnings("unchecked") + public static T unwrap(@Nullable T obj) { + if (!isWrapper(obj)) return obj; + else return ((TtlWrapper) obj).unwrap(); + } + + /** + * check the input object is a {@code TtlWrapper} or not. + * + * @since 2.11.4 + */ + public static boolean isWrapper(@Nullable T obj) { + return obj instanceof TtlWrapper; + } + + private TtlUnwrap() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlWrappers.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlWrappers.java new file mode 100644 index 000000000..d932c3568 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/TtlWrappers.java @@ -0,0 +1,342 @@ +package com.fr.third.alibaba.ttl; + +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.spi.TtlWrapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.*; + +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; + +/** + * Util methods for TTL Wrapper: wrap common {@code Functional Interface}. + *

+ * Note: + *

    + *
  • all methods is {@code null}-safe, when input parameter is {@code null}, return {@code null}.
  • + *
  • all wrap method skip wrap (aka. just return input parameter), when input parameter is already wrapped.
  • + *
+ * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlRunnable + * @see TtlCallable + * @see TtlUnwrap + * @see TtlWrapper + * @since 2.11.4 + */ +public class TtlWrappers { + /** + * wrap input {@link Supplier} to TTL wrapper. + * + * @param supplier input {@link Supplier} + * @return Wrapped {@link Supplier} + * @see TtlUnwrap#unwrap(Object) + * @since 2.11.4 + */ + @Nullable + public static Supplier wrap(@Nullable Supplier supplier) { + if (supplier == null) return null; + else if (supplier instanceof TtlEnhanced) return supplier; + else return new TtlSupplier(supplier); + } + + private static class TtlSupplier implements Supplier, TtlWrapper>, TtlEnhanced { + final Supplier supplier; + final Object captured; + + TtlSupplier(@NotNull Supplier supplier) { + this.supplier = supplier; + this.captured = capture(); + } + + @Override + public T get() { + final Object backup = replay(captured); + try { + return supplier.get(); + } finally { + restore(backup); + } + } + + @NotNull + @Override + public Supplier unwrap() { + return supplier; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlSupplier that = (TtlSupplier) o; + + return supplier.equals(that.supplier); + } + + @Override + public int hashCode() { + return supplier.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + supplier.toString(); + } + } + + + /** + * wrap input {@link Consumer} to TTL wrapper. + * + * @param consumer input {@link Consumer} + * @return Wrapped {@link Consumer} + * @see TtlUnwrap#unwrap(Object) + * @since 2.11.4 + */ + @Nullable + public static Consumer wrap(@Nullable Consumer consumer) { + if (consumer == null) return null; + else if (consumer instanceof TtlEnhanced) return consumer; + else return new TtlConsumer(consumer); + } + + private static class TtlConsumer implements Consumer, TtlWrapper>, TtlEnhanced { + final Consumer consumer; + final Object captured; + + TtlConsumer(@NotNull Consumer consumer) { + this.consumer = consumer; + this.captured = capture(); + } + + @Override + public void accept(T t) { + final Object backup = replay(captured); + try { + consumer.accept(t); + } finally { + restore(backup); + } + } + + @NotNull + @Override + public Consumer unwrap() { + return consumer; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlConsumer that = (TtlConsumer) o; + + return consumer.equals(that.consumer); + } + + @Override + public int hashCode() { + return consumer.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + consumer.toString(); + } + } + + + /** + * wrap input {@link BiConsumer} to TTL wrapper. + * + * @param consumer input {@link BiConsumer} + * @return Wrapped {@link BiConsumer} + * @see TtlUnwrap#unwrap(Object) + * @since 2.11.4 + */ + @Nullable + public static BiConsumer wrap(@Nullable BiConsumer consumer) { + if (consumer == null) return null; + else if (consumer instanceof TtlEnhanced) return consumer; + else return new TtlBiConsumer(consumer); + } + + private static class TtlBiConsumer implements BiConsumer, TtlWrapper>, TtlEnhanced { + final BiConsumer consumer; + final Object captured; + + TtlBiConsumer(@NotNull BiConsumer consumer) { + this.consumer = consumer; + this.captured = capture(); + } + + @Override + public void accept(T t, U u) { + final Object backup = replay(captured); + try { + consumer.accept(t, u); + } finally { + restore(backup); + } + } + + @NotNull + @Override + public BiConsumer unwrap() { + return consumer; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlBiConsumer that = (TtlBiConsumer) o; + + return consumer.equals(that.consumer); + } + + @Override + public int hashCode() { + return consumer.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + consumer.toString(); + } + } + + + /** + * wrap input {@link Function} to TTL wrapper. + * + * @param fn input {@link Function} + * @return Wrapped {@link Function} + * @see TtlUnwrap#unwrap(Object) + * @since 2.11.4 + */ + @Nullable + public static Function wrap(@Nullable Function fn) { + if (fn == null) return null; + else if (fn instanceof TtlEnhanced) return fn; + else return new TtlFunction(fn); + } + + private static class TtlFunction implements Function, TtlWrapper>, TtlEnhanced { + final Function fn; + final Object captured; + + TtlFunction(@NotNull Function fn) { + this.fn = fn; + this.captured = capture(); + } + + @Override + public R apply(T t) { + final Object backup = replay(captured); + try { + return fn.apply(t); + } finally { + restore(backup); + } + } + + @NotNull + @Override + public Function unwrap() { + return fn; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlFunction that = (TtlFunction) o; + + return fn.equals(that.fn); + } + + @Override + public int hashCode() { + return fn.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + fn.toString(); + } + } + + + /** + * wrap input {@link BiFunction} to TTL wrapper. + * + * @param fn input {@link BiFunction} + * @return Wrapped {@link BiFunction} + * @see TtlUnwrap#unwrap(Object) + * @since 2.11.4 + */ + @Nullable + public static BiFunction wrap(@Nullable BiFunction fn) { + if (fn == null) return null; + else if (fn instanceof TtlEnhanced) return fn; + else return new TtlBiFunction(fn); + } + + private static class TtlBiFunction implements BiFunction, TtlWrapper>, TtlEnhanced { + final BiFunction fn; + final Object captured; + + TtlBiFunction(@NotNull BiFunction fn) { + this.fn = fn; + this.captured = capture(); + } + + @Override + public R apply(T t, U u) { + final Object backup = replay(captured); + try { + return fn.apply(t, u); + } finally { + restore(backup); + } + } + + @NotNull + @Override + public BiFunction unwrap() { + return fn; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlBiFunction that = (TtlBiFunction) o; + + return fn.equals(that.fn); + } + + @Override + public int hashCode() { + return fn.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + fn.toString(); + } + } + + + private TtlWrappers() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/package-info.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/package-info.java new file mode 100644 index 000000000..d842d0a01 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/package-info.java @@ -0,0 +1,9 @@ +/** + * TTL API. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.fr.third.alibaba.ttl.TransmittableThreadLocal + * @see com.fr.third.alibaba.ttl.TtlRunnable + * @see com.fr.third.alibaba.ttl.TtlCallable + */ +package com.fr.third.alibaba.ttl; diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlAttachments.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlAttachments.java new file mode 100644 index 000000000..d3f83bff2 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlAttachments.java @@ -0,0 +1,39 @@ +package com.fr.third.alibaba.ttl.spi; + +import com.fr.third.alibaba.ttl.TtlCallable; +import com.fr.third.alibaba.ttl.TtlRunnable; +import org.jetbrains.annotations.NotNull; + +/** + * The TTL attachments for TTL tasks, eg: {@link TtlRunnable}, {@link TtlCallable}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 2.11.0 + */ +public interface TtlAttachments extends TtlEnhanced { + /** + * set the TTL attachments for TTL tasks + * + * @param key attachment key + * @param value attachment value + * @since 2.11.0 + */ + void setTtlAttachment(@NotNull String key, Object value); + + /** + * get the TTL attachment for TTL tasks + * + * @param key attachment key + * @since 2.11.0 + */ + T getTtlAttachment(@NotNull String key); + + /** + * The attachment key of TTL task, weather this task is a auto wrapper task. + *

+ * so the value of this attachment is a {@code boolean}. + * + * @since 2.11.0 + */ + String KEY_IS_AUTO_WRAPPER = "ttl.is.auto.wrapper"; +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlAttachmentsDelegate.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlAttachmentsDelegate.java new file mode 100644 index 000000000..fb50ea218 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlAttachmentsDelegate.java @@ -0,0 +1,31 @@ +package com.fr.third.alibaba.ttl.spi; + +import com.fr.third.alibaba.ttl.TtlCallable; +import com.fr.third.alibaba.ttl.TtlRunnable; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * {@link TtlAttachments} delegate/implementation. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlRunnable + * @see TtlCallable + * @since 2.11.0 + */ +public class TtlAttachmentsDelegate implements TtlAttachments { + private final ConcurrentMap attachments = new ConcurrentHashMap(); + + @Override + public void setTtlAttachment(@NotNull String key, Object value) { + attachments.put(key, value); + } + + @Override + @SuppressWarnings("unchecked") + public T getTtlAttachment(@NotNull String key) { + return (T) attachments.get(key); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlEnhanced.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlEnhanced.java new file mode 100644 index 000000000..2fe90b789 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlEnhanced.java @@ -0,0 +1,20 @@ +package com.fr.third.alibaba.ttl.spi; + +import com.fr.third.alibaba.ttl.TtlCallable; +import com.fr.third.alibaba.ttl.TtlRecursiveAction; +import com.fr.third.alibaba.ttl.TtlRecursiveTask; +import com.fr.third.alibaba.ttl.TtlRunnable; + +/** + * a Ttl marker/tag interface, for ttl enhanced class, for example {@code TTL wrapper} like {@link TtlRunnable}, {@link TtlCallable}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlRunnable + * @see TtlCallable + * @see TtlRecursiveAction + * @see TtlRecursiveTask + * @see TtlAttachments + * @since 2.11.0 + */ +public interface TtlEnhanced { +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlWrapper.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlWrapper.java new file mode 100644 index 000000000..5fd32b1ed --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/TtlWrapper.java @@ -0,0 +1,26 @@ +package com.fr.third.alibaba.ttl.spi; + +import com.fr.third.alibaba.ttl.TtlUnwrap; +import org.jetbrains.annotations.NotNull; + +/** + * Ttl Wrapper interface. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlUnwrap#unwrap + * @since 2.11.4 + */ +public interface TtlWrapper extends TtlEnhanced { + /** + * unwrap {@link TtlWrapper} to the original/underneath one. + *

+ * this method is {@code null}-safe, when input {@code BiFunction} parameter is {@code null}, return {@code null}; + * if input parameter is not a {@code TtlWrapper} just return input. + *

+ * so {@code unwrap} will always return the same input object. + * + * @see TtlUnwrap#unwrap(Object) + */ + @NotNull + T unwrap(); +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/package-info.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/package-info.java new file mode 100644 index 000000000..50919ee22 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/spi/package-info.java @@ -0,0 +1,9 @@ +/** + * TTL SPI + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.fr.third.alibaba.ttl.spi.TtlEnhanced + * @see com.fr.third.alibaba.ttl.spi.TtlAttachments + * @since 2.11.0 + */ +package com.fr.third.alibaba.ttl.spi; diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactory.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactory.java new file mode 100644 index 000000000..44a508b99 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactory.java @@ -0,0 +1,21 @@ +package com.fr.third.alibaba.ttl.threadpool; + +import com.fr.third.alibaba.ttl.spi.TtlWrapper; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; + +/** + * Disable inheritable {@link ForkJoinWorkerThreadFactory}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 2.10.1 + */ +public interface DisableInheritableForkJoinWorkerThreadFactory extends ForkJoinWorkerThreadFactory, TtlWrapper { + /** + * Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. + */ + @Override + @NotNull + ForkJoinWorkerThreadFactory unwrap(); +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactoryWrapper.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactoryWrapper.java new file mode 100644 index 000000000..d96b8282a --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactoryWrapper.java @@ -0,0 +1,58 @@ +package com.fr.third.alibaba.ttl.threadpool; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; +import java.util.concurrent.ForkJoinWorkerThread; + +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.clear; +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.restore; + +/** + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 2.10.1 + */ +class DisableInheritableForkJoinWorkerThreadFactoryWrapper implements DisableInheritableForkJoinWorkerThreadFactory { + private final ForkJoinWorkerThreadFactory threadFactory; + + DisableInheritableForkJoinWorkerThreadFactoryWrapper(@NotNull ForkJoinWorkerThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + final Object backup = clear(); + try { + return threadFactory.newThread(pool); + } finally { + restore(backup); + } + } + + @Override + @NotNull + public ForkJoinWorkerThreadFactory unwrap() { + return threadFactory; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DisableInheritableForkJoinWorkerThreadFactoryWrapper that = (DisableInheritableForkJoinWorkerThreadFactoryWrapper) o; + + return threadFactory.equals(that.threadFactory); + } + + @Override + public int hashCode() { + return threadFactory.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + threadFactory.toString(); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableThreadFactory.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableThreadFactory.java new file mode 100644 index 000000000..90049cc3e --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableThreadFactory.java @@ -0,0 +1,22 @@ +package com.fr.third.alibaba.ttl.threadpool; + +import com.fr.third.alibaba.ttl.spi.TtlWrapper; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ThreadFactory; + +/** + * Disable inheritable {@link ThreadFactory}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see ThreadFactory + * @since 2.10.0 + */ +public interface DisableInheritableThreadFactory extends ThreadFactory, TtlWrapper { + /** + * Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. + */ + @Override + @NotNull + ThreadFactory unwrap(); +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableThreadFactoryWrapper.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableThreadFactoryWrapper.java new file mode 100644 index 000000000..5ecf93cb0 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/DisableInheritableThreadFactoryWrapper.java @@ -0,0 +1,56 @@ +package com.fr.third.alibaba.ttl.threadpool; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ThreadFactory; + +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.clear; +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.restore; + +/** + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 2.10.0 + */ +class DisableInheritableThreadFactoryWrapper implements DisableInheritableThreadFactory { + private final ThreadFactory threadFactory; + + DisableInheritableThreadFactoryWrapper(@NotNull ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + @Override + public Thread newThread(@NotNull Runnable r) { + final Object backup = clear(); + try { + return threadFactory.newThread(r); + } finally { + restore(backup); + } + } + + @NotNull + @Override + public ThreadFactory unwrap() { + return threadFactory; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DisableInheritableThreadFactoryWrapper that = (DisableInheritableThreadFactoryWrapper) o; + + return threadFactory.equals(that.threadFactory); + } + + @Override + public int hashCode() { + return threadFactory.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + threadFactory.toString(); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ExecutorServiceTtlWrapper.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ExecutorServiceTtlWrapper.java new file mode 100644 index 000000000..ebb4ea482 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ExecutorServiceTtlWrapper.java @@ -0,0 +1,101 @@ +package com.fr.third.alibaba.ttl.threadpool; + +import com.fr.third.alibaba.ttl.TransmittableThreadLocal; +import com.fr.third.alibaba.ttl.TtlCallable; +import com.fr.third.alibaba.ttl.TtlRunnable; +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.*; + +/** + * {@link TransmittableThreadLocal} Wrapper of {@link ExecutorService}, + * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link Callable} + * to the execution time of {@link Runnable} or {@link Callable}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 0.9.0 + */ +class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService, TtlEnhanced { + private final ExecutorService executorService; + + ExecutorServiceTtlWrapper(@NotNull ExecutorService executorService) { + super(executorService); + this.executorService = executorService; + } + + @Override + public void shutdown() { + executorService.shutdown(); + } + + @NotNull + @Override + public List shutdownNow() { + return executorService.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return executorService.isShutdown(); + } + + @Override + public boolean isTerminated() { + return executorService.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException { + return executorService.awaitTermination(timeout, unit); + } + + @NotNull + @Override + public Future submit(@NotNull Callable task) { + return executorService.submit(TtlCallable.get(task)); + } + + @NotNull + @Override + public Future submit(@NotNull Runnable task, T result) { + return executorService.submit(TtlRunnable.get(task), result); + } + + @NotNull + @Override + public Future submit(@NotNull Runnable task) { + return executorService.submit(TtlRunnable.get(task)); + } + + @NotNull + @Override + public List> invokeAll(@NotNull Collection> tasks) throws InterruptedException { + return executorService.invokeAll(TtlCallable.gets(tasks)); + } + + @NotNull + @Override + public List> invokeAll(@NotNull Collection> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException { + return executorService.invokeAll(TtlCallable.gets(tasks), timeout, unit); + } + + @NotNull + @Override + public T invokeAny(@NotNull Collection> tasks) throws InterruptedException, ExecutionException { + return executorService.invokeAny(TtlCallable.gets(tasks)); + } + + @Override + public T invokeAny(@NotNull Collection> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return executorService.invokeAny(TtlCallable.gets(tasks), timeout, unit); + } + + @NotNull + @Override + public ExecutorService unwrap() { + return executorService; + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ExecutorTtlWrapper.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ExecutorTtlWrapper.java new file mode 100644 index 000000000..87aabac2d --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ExecutorTtlWrapper.java @@ -0,0 +1,56 @@ +package com.fr.third.alibaba.ttl.threadpool; + +import com.fr.third.alibaba.ttl.TransmittableThreadLocal; +import com.fr.third.alibaba.ttl.TtlRunnable; +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.spi.TtlWrapper; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Executor; + +/** + * {@link TransmittableThreadLocal} Wrapper of {@link Executor}, + * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} + * to the execution time of {@link Runnable}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 0.9.0 + */ +class ExecutorTtlWrapper implements Executor, TtlWrapper, TtlEnhanced { + private final Executor executor; + + ExecutorTtlWrapper(@NotNull Executor executor) { + this.executor = executor; + } + + @Override + public void execute(@NotNull Runnable command) { + executor.execute(TtlRunnable.get(command)); + } + + @Override + @NotNull + public Executor unwrap() { + return executor; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ExecutorTtlWrapper that = (ExecutorTtlWrapper) o; + + return executor.equals(that.executor); + } + + @Override + public int hashCode() { + return executor.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + executor.toString(); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ScheduledExecutorServiceTtlWrapper.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ScheduledExecutorServiceTtlWrapper.java new file mode 100644 index 000000000..dd1ea268e --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/ScheduledExecutorServiceTtlWrapper.java @@ -0,0 +1,61 @@ +package com.fr.third.alibaba.ttl.threadpool; + +import com.fr.third.alibaba.ttl.TransmittableThreadLocal; +import com.fr.third.alibaba.ttl.TtlCallable; +import com.fr.third.alibaba.ttl.TtlRunnable; +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import org.jetbrains.annotations.NotNull; + + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * {@link TransmittableThreadLocal} Wrapper of {@link ScheduledExecutorService}, + * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link Callable} + * to the execution time of {@link Runnable} or {@link Callable}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 0.9.0 + */ + +class ScheduledExecutorServiceTtlWrapper extends ExecutorServiceTtlWrapper implements ScheduledExecutorService, TtlEnhanced { + final ScheduledExecutorService scheduledExecutorService; + + public ScheduledExecutorServiceTtlWrapper(@NotNull ScheduledExecutorService scheduledExecutorService) { + super(scheduledExecutorService); + this.scheduledExecutorService = scheduledExecutorService; + } + + @NotNull + @Override + public ScheduledFuture schedule(@NotNull Runnable command, long delay, @NotNull TimeUnit unit) { + return scheduledExecutorService.schedule(TtlRunnable.get(command), delay, unit); + } + + @NotNull + @Override + public ScheduledFuture schedule(@NotNull Callable callable, long delay, @NotNull TimeUnit unit) { + return scheduledExecutorService.schedule(TtlCallable.get(callable), delay, unit); + } + + @NotNull + @Override + public ScheduledFuture scheduleAtFixedRate(@NotNull Runnable command, long initialDelay, long period, @NotNull TimeUnit unit) { + return scheduledExecutorService.scheduleAtFixedRate(TtlRunnable.get(command), initialDelay, period, unit); + } + + @NotNull + @Override + public ScheduledFuture scheduleWithFixedDelay(@NotNull Runnable command, long initialDelay, long delay, @NotNull TimeUnit unit) { + return scheduledExecutorService.scheduleWithFixedDelay(TtlRunnable.get(command), initialDelay, delay, unit); + } + + @Override + @NotNull + public ScheduledExecutorService unwrap() { + return scheduledExecutorService; + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/TtlExecutors.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/TtlExecutors.java new file mode 100644 index 000000000..39a273155 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/TtlExecutors.java @@ -0,0 +1,171 @@ +package com.fr.third.alibaba.ttl.threadpool; + +import com.fr.third.alibaba.ttl.TransmittableThreadLocal; +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.threadpool.agent.TtlAgent; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.*; + +/** + * Util methods for TTL wrapper of jdk executors. + * + *

    + *
  1. Factory methods to get TTL wrapper from jdk executors.
  2. + *
  3. unwrap/check methods for TTL wrapper of jdk executors.
  4. + *
  5. wrap/unwrap/check methods to disable Inheritable for {@link ThreadFactory}.
  6. + *
+ *

+ * Note: + *

    + *
  • all method is {@code null}-safe, when input {@code executor} parameter is {@code null}, return {@code null}.
  • + *
  • skip wrap/decoration thread pool/{@code executor}(aka. just return input {@code executor}) + * when ttl agent is loaded, Or when input {@code executor} is already wrapped/decorated.
  • + *
+ * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see java.util.concurrent.Executor + * @see java.util.concurrent.ExecutorService + * @see java.util.concurrent.ThreadPoolExecutor + * @see java.util.concurrent.ScheduledThreadPoolExecutor + * @see java.util.concurrent.Executors + * @see java.util.concurrent.CompletionService + * @see java.util.concurrent.ExecutorCompletionService + * @see ThreadFactory + * @see Executors#defaultThreadFactory() + * @since 0.9.0 + */ +public final class TtlExecutors { + /** + * {@link TransmittableThreadLocal} Wrapper of {@link Executor}, + * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} + * to the execution time of {@link Runnable}. + */ + @Nullable + public static Executor getTtlExecutor(@Nullable Executor executor) { + if (TtlAgent.isTtlAgentLoaded() || null == executor || executor instanceof TtlEnhanced) { + return executor; + } + return new ExecutorTtlWrapper(executor); + } + + /** + * {@link TransmittableThreadLocal} Wrapper of {@link ExecutorService}, + * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link java.util.concurrent.Callable} + * to the execution time of {@link Runnable} or {@link java.util.concurrent.Callable}. + */ + @Nullable + public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) { + if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) { + return executorService; + } + return new ExecutorServiceTtlWrapper(executorService); + } + + /** + * {@link TransmittableThreadLocal} Wrapper of {@link ScheduledExecutorService}, + * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link java.util.concurrent.Callable} + * to the execution time of {@link Runnable} or {@link java.util.concurrent.Callable}. + */ + @Nullable + public static ScheduledExecutorService getTtlScheduledExecutorService(@Nullable ScheduledExecutorService scheduledExecutorService) { + if (TtlAgent.isTtlAgentLoaded() || scheduledExecutorService == null || scheduledExecutorService instanceof TtlEnhanced) { + return scheduledExecutorService; + } + return new ScheduledExecutorServiceTtlWrapper(scheduledExecutorService); + } + + /** + * check the executor is TTL wrapper executor or not. + *

+ * if the parameter executor is TTL wrapper, return {@code true}, otherwise {@code false}. + *

+ * NOTE: if input executor is {@code null}, return {@code false}. + * + * @param executor input executor + * @param Executor type + * @see #getTtlExecutor(Executor) + * @see #getTtlExecutorService(ExecutorService) + * @see #getTtlScheduledExecutorService(ScheduledExecutorService) + * @see #unwrap(Executor) + * @since 2.8.0 + */ + public static boolean isTtlWrapper(@Nullable T executor) { + return executor instanceof TtlEnhanced; + } + + /** + * Unwrap TTL wrapper executor to the original/underneath one. + *

+ * if the parameter executor is TTL wrapper, return the original/underneath executor; + * otherwise, just return the input parameter executor. + *

+ * NOTE: if input executor is {@code null}, return {@code null}. + * + * @param executor input executor + * @param Executor type + * @see #getTtlExecutor(Executor) + * @see #getTtlExecutorService(ExecutorService) + * @see #getTtlScheduledExecutorService(ScheduledExecutorService) + * @see #isTtlWrapper(Executor) + * @since 2.8.0 + */ + @Nullable + @SuppressWarnings("unchecked") + public static T unwrap(@Nullable T executor) { + if (!isTtlWrapper(executor)) return executor; + + return (T) ((ExecutorTtlWrapper) executor).unwrap(); + } + + /** + * Wrapper of {@link ThreadFactory}, disable inheritable. + * + * @param threadFactory input thread factory + * @see DisableInheritableThreadFactory + * @since 2.10.0 + */ + @Nullable + public static ThreadFactory getDisableInheritableThreadFactory(@Nullable ThreadFactory threadFactory) { + if (threadFactory == null || isDisableInheritableThreadFactory(threadFactory)) return threadFactory; + + return new DisableInheritableThreadFactoryWrapper(threadFactory); + } + + /** + * Wrapper of {@link Executors#defaultThreadFactory()}, disable inheritable. + * + * @see #getDisableInheritableThreadFactory(ThreadFactory) + * @since 2.10.0 + */ + @Nullable + public static ThreadFactory getDefaultDisableInheritableThreadFactory() { + return getDisableInheritableThreadFactory(Executors.defaultThreadFactory()); + } + + /** + * check the {@link ThreadFactory} is {@link DisableInheritableThreadFactory} or not. + * + * @see DisableInheritableThreadFactory + * @since 2.10.0 + */ + public static boolean isDisableInheritableThreadFactory(@Nullable ThreadFactory threadFactory) { + return threadFactory instanceof DisableInheritableThreadFactory; + } + + /** + * Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. + * + * @see DisableInheritableThreadFactory + * @since 2.10.0 + */ + @Nullable + public static ThreadFactory unwrap(@Nullable ThreadFactory threadFactory) { + if (!isDisableInheritableThreadFactory(threadFactory)) return threadFactory; + + return ((DisableInheritableThreadFactory) threadFactory).unwrap(); + } + + private TtlExecutors() { + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/TtlForkJoinPoolHelper.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/TtlForkJoinPoolHelper.java new file mode 100644 index 000000000..10b862d1f --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/TtlForkJoinPoolHelper.java @@ -0,0 +1,73 @@ +package com.fr.third.alibaba.ttl.threadpool; + +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; + +/** + * Util methods to wrap/unwrap/check methods to disable Inheritable for {@link ForkJoinWorkerThreadFactory}. + *

+ * Note: + *

+ * all method is {@code null}-safe, when input parameter(eg: {@link ForkJoinWorkerThreadFactory}) is {@code null}, return {@code null}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see ForkJoinPool + * @see ForkJoinWorkerThreadFactory + * @see ForkJoinPool#defaultForkJoinWorkerThreadFactory + * @since 2.10.1 + */ +public class TtlForkJoinPoolHelper { + /** + * Wrapper of {@link ForkJoinWorkerThreadFactory}, disable inheritable. + * + * @param threadFactory input thread factory + * @see DisableInheritableForkJoinWorkerThreadFactory + * @since 2.10.1 + */ + @Nullable + public static ForkJoinWorkerThreadFactory getDisableInheritableForkJoinWorkerThreadFactory(@Nullable ForkJoinWorkerThreadFactory threadFactory) { + if (threadFactory == null || isDisableInheritableForkJoinWorkerThreadFactory(threadFactory)) + return threadFactory; + + return new DisableInheritableForkJoinWorkerThreadFactoryWrapper(threadFactory); + } + + /** + * Wrapper of {@link ForkJoinPool#defaultForkJoinWorkerThreadFactory}, disable inheritable. + * + * @see #getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinWorkerThreadFactory) + * @since 2.10.1 + */ + @Nullable + public static ForkJoinWorkerThreadFactory getDefaultDisableInheritableForkJoinWorkerThreadFactory() { + return getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinPool.defaultForkJoinWorkerThreadFactory); + } + + /** + * check the {@link ForkJoinWorkerThreadFactory} is {@link DisableInheritableForkJoinWorkerThreadFactory} or not. + * + * @see DisableInheritableForkJoinWorkerThreadFactory + * @since 2.10.1 + */ + public static boolean isDisableInheritableForkJoinWorkerThreadFactory(@Nullable ForkJoinWorkerThreadFactory threadFactory) { + return threadFactory instanceof DisableInheritableForkJoinWorkerThreadFactory; + } + + /** + * Unwrap {@link DisableInheritableForkJoinWorkerThreadFactory} to the original/underneath one. + * + * @see DisableInheritableForkJoinWorkerThreadFactory + * @since 2.10.1 + */ + @Nullable + public static ForkJoinWorkerThreadFactory unwrap(@Nullable ForkJoinWorkerThreadFactory threadFactory) { + if (!isDisableInheritableForkJoinWorkerThreadFactory(threadFactory)) return threadFactory; + + return ((DisableInheritableForkJoinWorkerThreadFactory) threadFactory).unwrap(); + } + + private TtlForkJoinPoolHelper() { + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/TtlAgent.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/TtlAgent.java new file mode 100644 index 000000000..f1e5bb783 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/TtlAgent.java @@ -0,0 +1,245 @@ +package com.fr.third.alibaba.ttl.threadpool.agent; + +import com.fr.third.alibaba.ttl.TransmittableThreadLocal; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.logging.Logger; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.JavassistTransformlet; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.impl.TtlExecutorTransformlet; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.impl.TtlForkJoinTransformlet; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.impl.TtlTimerTaskTransformlet; + +import com.fr.third.alibaba.ttl.threadpool.DisableInheritableForkJoinWorkerThreadFactory; +import com.fr.third.alibaba.ttl.threadpool.DisableInheritableThreadFactory; +import com.fr.third.alibaba.ttl.threadpool.TtlExecutors; +import com.fr.third.alibaba.ttl.threadpool.TtlForkJoinPoolHelper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +/** + * TTL Java Agent. + *

+ * The configuration/arguments for agent see the javadoc of {@link #premain(String, Instrumentation)} + *

+ * NOTE:
+ * Since {@code v2.6.0}, TTL agent jar will auto add self to {@code boot classpath}. + * But you should NOT modify the downloaded TTL jar file name in the maven repo(eg: {@code transmittable-thread-local-2.x.x.jar}).
+ * if you modified the downloaded TTL agent jar file name(eg: {@code ttl-foo-name-changed.jar}), + * you must add TTL agent jar to {@code boot classpath} manually + * by java option {@code -Xbootclasspath/a:path/to/ttl-foo-name-changed.jar}. + *

+ * The implementation of auto adding self agent jar to {@code boot classpath} use + * the {@code Boot-Class-Path} property of manifest file({@code META-INF/MANIFEST.MF}) in the TTL Java Agent Jar: + *

+ *
+ *
Boot-Class-Path
+ *
+ * A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms). + * These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed. + *
+ *
+ *
+ *

+ * More info about {@code Boot-Class-Path} see + * The mechanism for instrumentation. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TransmittableThreadLocal + * @see Instrumentation + * @see The mechanism for instrumentation + * @see JAR File Specification - JAR Manifest + * @see Working with Manifest Files - The Java™ TutorialsHide + * @since 0.9.0 + */ +public final class TtlAgent { + /** + * Entrance method of TTL Java Agent. + * + *

TTL Agent configuration

+ * Configure TTL agent via agent arguments, format is {@code k1:v1,k2:v2}. Below is available configuration keys. + * + *

Disable inheritable for thread pool

+ *

+ * Enable "disable inheritable" for thread pool, config by key {@code ttl.agent.disable.inheritable.for.thread.pool}. + * When no configuration for this key, default does not enabled. Since version {@code 2.10.1}. + * + *

    + *
  • rewrite the {@link java.util.concurrent.ThreadFactory} constructor parameter + * of {@link java.util.concurrent.ThreadPoolExecutor} + * to {@link DisableInheritableThreadFactory} + * by util method {@link TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory)}. + *
  • + *
  • rewrite the {@link java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory} constructor parameter + * of {@link java.util.concurrent.ForkJoinPool} + * to {@link DisableInheritableForkJoinWorkerThreadFactory} + * by util method {@link TtlForkJoinPoolHelper#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory)}. + *
  • + *
+ * More info about "disable inheritable" see {@link TransmittableThreadLocal}. + *

+ * Configuration example:
+ * {@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.disable.inheritable.for.thread.pool:true} + * + *

The log configuration

+ * The log of TTL Java Agent is config by key {@code ttl.agent.logger}. Since version {@code 2.6.0}. + * + *
    + *
  • {@code ttl.agent.logger : STDERR}
    + * only log to {@code stderr} when error. + * This is default, when no/unrecognized configuration for key {@code ttl.agent.logger}.
  • + *
  • {@code ttl.agent.logger : STDOUT}
    + * Log to {@code stdout}, more info than {@code ttl.agent.logger:STDERR}; This is needed when developing.
  • + *
+ *

+ * configuration example: + *

    + *
  • {@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar}
  • + *
  • {@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.logger:STDOUT}
  • + *
+ * + *

Enable/disable TimerTask class decoration

+ * Enable/disable TimerTask class decoration is config by key {@code ttl.agent.enable.timer.task}. + * Since version {@code 2.7.0}. + *

+ * When no configuration for this key, default is enabled.
+ * Note: Since version {@code 2.11.2} the default value is {@code true}(enable TimerTask class decoration); + * Before version {@code 2.11.1} default value is {@code false}. + *

+ * Configuration example: + *

    + *
  • {@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.enable.timer.task:false}
  • + *
  • {@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.enable.timer.task:true}
  • + *
+ * + *

Multi key configuration example

+ * {@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.logger:STDOUT,ttl.agent.disable.inheritable.for.thread.pool:true} + * + * @see java.util.concurrent.ThreadPoolExecutor + * @see java.util.concurrent.ScheduledThreadPoolExecutor + * @see java.util.concurrent.ForkJoinPool + * @see java.util.TimerTask + * @see Logger + * @see Logger#TTL_AGENT_LOGGER_KEY + * @see Logger#STDERR + * @see Logger#STDOUT + */ + public static void premain(String agentArgs, @NotNull Instrumentation inst) { + kvs = splitCommaColonStringToKV(agentArgs); + + Logger.setLoggerImplType(getLogImplTypeFromAgentArgs(kvs)); + final Logger logger = Logger.getLogger(TtlAgent.class); + + try { + logger.info("[TtlAgent.premain] begin, agentArgs: " + agentArgs + ", Instrumentation: " + inst); + final boolean disableInheritableForThreadPool = isDisableInheritableForThreadPool(); + + final List transformletList = new ArrayList(); + transformletList.add(new TtlExecutorTransformlet(disableInheritableForThreadPool)); + transformletList.add(new TtlForkJoinTransformlet(disableInheritableForThreadPool)); + if (isEnableTimerTask()) transformletList.add(new TtlTimerTaskTransformlet()); + + final ClassFileTransformer transformer = new TtlTransformer(transformletList); + inst.addTransformer(transformer, true); + logger.info("[TtlAgent.premain] addTransformer " + transformer.getClass() + " success"); + + logger.info("[TtlAgent.premain] end"); + + ttlAgentLoaded = true; + } catch (Exception e) { + String msg = "Fail to load TtlAgent , cause: " + e.toString(); + logger.log(Level.SEVERE, msg, e); + throw new IllegalStateException(msg, e); + } + } + + private static String getLogImplTypeFromAgentArgs(@NotNull final Map kvs) { + return kvs.get(Logger.TTL_AGENT_LOGGER_KEY); + } + + private static volatile Map kvs; + + private static volatile boolean ttlAgentLoaded = false; + + /** + * Whether TTL agent is loaded. + * + * @since 2.9.0 + */ + public static boolean isTtlAgentLoaded() { + return ttlAgentLoaded; + } + + private static final String TTL_AGENT_ENABLE_TIMER_TASK_KEY = "ttl.agent.enable.timer.task"; + + private static final String TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL = "ttl.agent.disable.inheritable.for.thread.pool"; + + /** + * Whether disable inheritable for thread pool is enhanced by ttl agent, check {@link #isTtlAgentLoaded()} first. + * + * @see TtlExecutors#getDefaultDisableInheritableThreadFactory() + * @see TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) + * @see DisableInheritableThreadFactory + * @see TtlForkJoinPoolHelper#getDefaultDisableInheritableForkJoinWorkerThreadFactory() + * @see TtlForkJoinPoolHelper#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) + * @see DisableInheritableForkJoinWorkerThreadFactory + * @since 2.10.1 + */ + public static boolean isDisableInheritableForThreadPool() { + return isOptionSetOrFalse(kvs, TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL); + } + + /** + * Whether timer task is enhanced by ttl agent, check {@link #isTtlAgentLoaded()} first. + * + * @since 2.10.1 + */ + public static boolean isEnableTimerTask() { + return isOptionSetOrTrue(kvs, TTL_AGENT_ENABLE_TIMER_TASK_KEY); + } + + private static boolean isOptionSetOrFalse(@Nullable final Map kvs, @NotNull String key) { + return isOptionSetOrFalse(kvs, key, false); + } + + private static boolean isOptionSetOrTrue(@Nullable final Map kvs, @NotNull String key) { + return isOptionSetOrFalse(kvs, key, true); + } + + private static boolean isOptionSetOrFalse(@Nullable final Map kvs, @NotNull String key, boolean defaultValue) { + if (null == kvs) return defaultValue; + + final boolean hasEnableKey = kvs.containsKey(key); + if (!hasEnableKey) return defaultValue; + + return !"false".equalsIgnoreCase(kvs.get(key)); + } + + /** + * Split to {@code json} like String({@code "k1:v1,k2:v2"}) to KV map({@code "k1"->"v1", "k2"->"v2"}). + */ + @NotNull + static Map splitCommaColonStringToKV(@Nullable String commaColonString) { + Map ret = new HashMap(); + if (commaColonString == null || commaColonString.trim().length() == 0) return ret; + + final String[] splitKvArray = commaColonString.trim().split("\\s*,\\s*"); + for (String kvString : splitKvArray) { + final String[] kv = kvString.trim().split("\\s*:\\s*"); + if (kv.length == 0) continue; + + if (kv.length == 1) ret.put(kv[0], ""); + else ret.put(kv[0], kv[1]); + } + + return ret; + } + + private TtlAgent() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/TtlTransformer.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/TtlTransformer.java new file mode 100644 index 000000000..4f8d3ea34 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/TtlTransformer.java @@ -0,0 +1,73 @@ +package com.fr.third.alibaba.ttl.threadpool.agent; + +import com.fr.third.alibaba.ttl.threadpool.agent.internal.logging.Logger; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.ClassInfo; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.JavassistTransformlet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +import java.lang.instrument.ClassFileTransformer; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +/** + * TTL {@link ClassFileTransformer} of Java Agent + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see ClassFileTransformer + * @see The mechanism for instrumentation + * @since 0.9.0 + */ +public class TtlTransformer implements ClassFileTransformer { + private static final Logger logger = Logger.getLogger(TtlTransformer.class); + + /** + * "null if no transform is performed", + * see {@code @return} of {@link ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])} + */ + + // [ERROR] com.alibaba.ttl.threadpool.agent.TtlTransformer.transform(ClassLoader, String, Class, ProtectionDomain, byte[]) + // may expose internal representation by returning TtlTransformer.NO_TRANSFORM + // the value is null, so there is NO "EI_EXPOSE_REP" problem actually. + private static final byte[] NO_TRANSFORM = null; + + private final List transformletList = new ArrayList(); + + TtlTransformer(List transformletList) { + for (JavassistTransformlet transformlet : transformletList) { + this.transformletList.add(transformlet); + logger.info("[TtlTransformer] add Transformlet " + transformlet.getClass() + " success"); + } + } + + @Override + public final byte[] transform(@Nullable final ClassLoader loader, @Nullable final String classFile, final Class classBeingRedefined, + final ProtectionDomain protectionDomain, @NotNull final byte[] classFileBuffer) { + try { + // Lambda has no class file, no need to transform, just return. + if (classFile == null) return NO_TRANSFORM; + + final String className = toClassName(classFile); + + ClassInfo classInfo = new ClassInfo(className, classFileBuffer, loader); + + for (JavassistTransformlet transformlet : transformletList) { + transformlet.doTransform(classInfo); + if (classInfo.isModified()) return classInfo.getCtClass().toBytecode(); + } + } catch (Throwable t) { + String msg = "Fail to transform class " + classFile + ", cause: " + t.toString(); + logger.log(Level.SEVERE, msg, t); + throw new IllegalStateException(msg, t); + } + + return NO_TRANSFORM; + } + + private static String toClassName(@NotNull final String classFile) { + return classFile.replace('/', '.'); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/logging/Logger.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/logging/Logger.java new file mode 100644 index 000000000..74a11957b --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/logging/Logger.java @@ -0,0 +1,80 @@ +package com.fr.third.alibaba.ttl.threadpool.agent.internal.logging; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Level; + +/** + * logger adaptor for ttl java agent, internal use for ttl usage only! + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 2.6.0 + */ +public abstract class Logger { + public static final String TTL_AGENT_LOGGER_KEY = "ttl.agent.logger"; + public static final String STDOUT = "STDOUT"; + public static final String STDERR = "STDERR"; + + private static volatile int loggerImplType = -1; + + public static void setLoggerImplType(String type) { + if (loggerImplType != -1) { + throw new IllegalStateException("TTL logger implementation type is already set! type = " + loggerImplType); + } + + if (STDERR.equalsIgnoreCase(type)) loggerImplType = 0; + else if (STDOUT.equalsIgnoreCase(type)) loggerImplType = 1; + else loggerImplType = 0; + } + + public static Logger getLogger(Class clazz) { + if (loggerImplType == -1) throw new IllegalStateException("TTL logger implementation type is NOT set!"); + + switch (loggerImplType) { + case 1: + return new StdOutLogger(clazz); + default: + return new StdErrorLogger(clazz); + } + } + + final Class logClass; + + private Logger(Class logClass) { + this.logClass = logClass; + } + + public void info(String msg) { + log(Level.INFO, msg, null); + } + + public abstract void log(Level level, String msg, Throwable thrown); + + private static class StdErrorLogger extends Logger { + StdErrorLogger(Class clazz) { + super(clazz); + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + if (level == Level.SEVERE) { + final String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); + System.err.printf("%s %s [%s] %s: %s%n", time, level, Thread.currentThread().getName(), logClass.getSimpleName(), msg); + if (thrown != null) thrown.printStackTrace(); + } + } + } + + private static class StdOutLogger extends Logger { + StdOutLogger(Class clazz) { + super(clazz); + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + final String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); + System.out.printf("%s %s [%s] %s: %s%n", time, level, Thread.currentThread().getName(), logClass.getSimpleName(), msg); + if (thrown != null) thrown.printStackTrace(System.out); + } + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/ClassInfo.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/ClassInfo.java new file mode 100644 index 000000000..8d656e5c6 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/ClassInfo.java @@ -0,0 +1,66 @@ +package com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet; + +import com.fr.third.javassist.CtClass; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import com.fr.third.javassist.ClassPool; +import com.fr.third.javassist.CtClass; +import com.fr.third.javassist.LoaderClassPath; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +/** + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 2.11.0 + */ +public class ClassInfo { + private final String className; + private final byte[] classFileBuffer; + private final ClassLoader loader; + + // SuppressFBWarnings for classFileBuffer parameter: + // [ERROR] new com.alibaba.ttl.threadpool.agent.internal.transformlet.ClassInfo(String, byte[], ClassLoader) + // may expose internal representation by storing an externally mutable object + // into ClassInfo.classFileBuffer + public ClassInfo(@NotNull String className, byte[] classFileBuffer, @Nullable ClassLoader loader) { + this.className = className; + this.classFileBuffer = classFileBuffer; + this.loader = loader; + } + + @NotNull + public String getClassName() { + return className; + } + + private CtClass ctClass; + + @NotNull + public CtClass getCtClass() throws IOException { + if (ctClass != null) return ctClass; + + final ClassPool classPool = new ClassPool(true); + if (loader == null) { + classPool.appendClassPath(new LoaderClassPath(ClassLoader.getSystemClassLoader())); + } else { + classPool.appendClassPath(new LoaderClassPath(loader)); + } + + final CtClass clazz = classPool.makeClass(new ByteArrayInputStream(classFileBuffer), false); + clazz.defrost(); + + this.ctClass = clazz; + return clazz; + } + + private boolean modified = false; + + public boolean isModified() { + return modified; + } + + public void setModified() { + this.modified = true; + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/JavassistTransformlet.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/JavassistTransformlet.java new file mode 100644 index 000000000..b88102889 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/JavassistTransformlet.java @@ -0,0 +1,17 @@ +package com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet; + +import org.jetbrains.annotations.NotNull; +import com.fr.third.javassist.CannotCompileException; +import com.fr.third.javassist.NotFoundException; + +import java.io.IOException; + +/** + * TTL {@code Transformlet} by {@code Javassist}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 2.5.1 + */ +public interface JavassistTransformlet { + void doTransform(@NotNull ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException; +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlExecutorTransformlet.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlExecutorTransformlet.java new file mode 100644 index 000000000..603e4c5dd --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlExecutorTransformlet.java @@ -0,0 +1,164 @@ +package com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.impl; + +import com.fr.third.alibaba.ttl.threadpool.TtlExecutors; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.logging.Logger; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.ClassInfo; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.JavassistTransformlet; +import com.fr.third.alibaba.ttl.TtlCallable; +import com.fr.third.alibaba.ttl.TtlRunnable; +import org.jetbrains.annotations.NotNull; +import com.fr.third.javassist.*; + +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +/** + * TTL {@link JavassistTransformlet} for {@link java.util.concurrent.Executor}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @author wuwen5 (wuwen.55 at aliyun dot com) + * @see java.util.concurrent.Executor + * @see java.util.concurrent.ExecutorService + * @see java.util.concurrent.ThreadPoolExecutor + * @see java.util.concurrent.ScheduledThreadPoolExecutor + * @see java.util.concurrent.Executors + * @since 2.5.1 + */ +public class TtlExecutorTransformlet implements JavassistTransformlet { + private static final Logger logger = Logger.getLogger(TtlExecutorTransformlet.class); + + private static Set EXECUTOR_CLASS_NAMES = new HashSet(); + private static final Map PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS = new HashMap(); + + private static final String THREAD_POOL_EXECUTOR_CLASS_NAME = "java.util.concurrent.ThreadPoolExecutor"; + private static final String RUNNABLE_CLASS_NAME = "java.lang.Runnable"; + + static { + EXECUTOR_CLASS_NAMES.add(THREAD_POOL_EXECUTOR_CLASS_NAME); + EXECUTOR_CLASS_NAMES.add("java.util.concurrent.ScheduledThreadPoolExecutor"); + + PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS.put(RUNNABLE_CLASS_NAME, "com.alibaba.ttl.TtlRunnable"); + PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS.put("java.util.concurrent.Callable", "com.alibaba.ttl.TtlCallable"); + } + + private static final String THREAD_FACTORY_CLASS_NAME = "java.util.concurrent.ThreadFactory"; + + private final boolean disableInheritableForThreadPool; + + public TtlExecutorTransformlet(boolean disableInheritableForThreadPool) { + this.disableInheritableForThreadPool = disableInheritableForThreadPool; + } + + @Override + public void doTransform(@NotNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { + if (EXECUTOR_CLASS_NAMES.contains(classInfo.getClassName())) { + final CtClass clazz = classInfo.getCtClass(); + + for (CtMethod method : clazz.getDeclaredMethods()) { + updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(method); + } + + if (disableInheritableForThreadPool) updateConstructorDisableInheritable(clazz); + + classInfo.setModified(); + } else { + final CtClass clazz = classInfo.getCtClass(); + + if (clazz.isPrimitive() || clazz.isArray() || clazz.isInterface() || clazz.isAnnotation()) { + return; + } + if (!clazz.subclassOf(clazz.getClassPool().get(THREAD_POOL_EXECUTOR_CLASS_NAME))) return; + + logger.info("Transforming class " + classInfo.getClassName()); + + final boolean modified = updateBeforeAndAfterExecuteMethodOfExecutorSubclass(clazz); + if (modified) classInfo.setModified(); + } + } + + /** + * @see TtlRunnable#get(Runnable, boolean, boolean) + * @see TtlCallable#get(Callable, boolean, boolean) + * @see Utils#setAutoWrapperAttachment(Object) + */ + // [ERROR] Format string should use %n rather than \n + private void updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(@NotNull final CtMethod method) throws NotFoundException, CannotCompileException { + final int modifiers = method.getModifiers(); + if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) return; + + CtClass[] parameterTypes = method.getParameterTypes(); + StringBuilder insertCode = new StringBuilder(); + for (int i = 0; i < parameterTypes.length; i++) { + final String paramTypeName = parameterTypes[i].getName(); + if (PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS.containsKey(paramTypeName)) { + String code = String.format( + // decorate to TTL wrapper, + // and then set AutoWrapper attachment/Tag + "$%d = %s.get($%d, false, true);" + + "\ncom.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.setAutoWrapperAttachment($% 0) method.insertBefore(insertCode.toString()); + } + + /** + * @see TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) + */ + private void updateConstructorDisableInheritable(@NotNull final CtClass clazz) throws NotFoundException, CannotCompileException { + for (CtConstructor constructor : clazz.getDeclaredConstructors()) { + final CtClass[] parameterTypes = constructor.getParameterTypes(); + final StringBuilder insertCode = new StringBuilder(); + for (int i = 0; i < parameterTypes.length; i++) { + final String paramTypeName = parameterTypes[i].getName(); + if (THREAD_FACTORY_CLASS_NAME.equals(paramTypeName)) { + String code = String.format("$%d = com.alibaba.ttl.threadpool.TtlExecutors.getDisableInheritableThreadFactory($% 0) constructor.insertBefore(insertCode.toString()); + } + } + + /** + * @see Utils#unwrapIfIsAutoWrapper(Runnable) + */ + private boolean updateBeforeAndAfterExecuteMethodOfExecutorSubclass(@NotNull final CtClass clazz) throws NotFoundException, CannotCompileException { + final CtClass runnableClass = clazz.getClassPool().get(RUNNABLE_CLASS_NAME); + final CtClass threadClass = clazz.getClassPool().get("java.lang.Thread"); + final CtClass throwableClass = clazz.getClassPool().get("java.lang.Throwable"); + boolean modified = false; + + try { + final CtMethod beforeExecute = clazz.getDeclaredMethod("beforeExecute", new CtClass[]{threadClass, runnableClass}); + // unwrap runnable if IsAutoWrapper + String code = "$2 = com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.unwrapIfIsAutoWrapper($2);"; + logger.info("insert code before method " + Utils.signatureOfMethod(beforeExecute) + " of class " + beforeExecute.getDeclaringClass().getName() + ": " + code); + beforeExecute.insertBefore(code); + modified = true; + } catch (NotFoundException e) { + // clazz does not override beforeExecute method, do nothing. + } + + try { + final CtMethod afterExecute = clazz.getDeclaredMethod("afterExecute", new CtClass[]{runnableClass, throwableClass}); + // unwrap runnable if IsAutoWrapper + String code = "$1 = com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.unwrapIfIsAutoWrapper($1);"; + logger.info("insert code before method " + Utils.signatureOfMethod(afterExecute) + " of class " + afterExecute.getDeclaringClass().getName() + ": " + code); + afterExecute.insertBefore(code); + modified = true; + } catch (NotFoundException e) { + // clazz does not override afterExecute method, do nothing. + } + + return modified; + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlForkJoinTransformlet.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlForkJoinTransformlet.java new file mode 100644 index 000000000..69e4b70a0 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlForkJoinTransformlet.java @@ -0,0 +1,85 @@ +package com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.impl; + +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.logging.Logger; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.ClassInfo; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.JavassistTransformlet; +import org.jetbrains.annotations.NotNull; +import com.fr.third.javassist.*; + +import java.io.IOException; + +/** + * TTL {@link JavassistTransformlet} for {@link java.util.concurrent.ForkJoinTask}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @author wuwen5 (wuwen.55 at aliyun dot com) + * @see java.util.concurrent.ForkJoinPool + * @see java.util.concurrent.ForkJoinTask + * @since 2.5.1 + */ +public class TtlForkJoinTransformlet implements JavassistTransformlet { + private static final Logger logger = Logger.getLogger(TtlForkJoinTransformlet.class); + + private static final String FORK_JOIN_TASK_CLASS_NAME = "java.util.concurrent.ForkJoinTask"; + private static final String FORK_JOIN_POOL_CLASS_NAME = "java.util.concurrent.ForkJoinPool"; + private static final String FORK_JOIN_WORKER_THREAD_FACTORY_CLASS_NAME = "java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory"; + + private final boolean disableInheritableForThreadPool; + + public TtlForkJoinTransformlet(boolean disableInheritableForThreadPool) { + this.disableInheritableForThreadPool = disableInheritableForThreadPool; + } + + @Override + public void doTransform(@NotNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { + if (FORK_JOIN_TASK_CLASS_NAME.equals(classInfo.getClassName())) { + updateForkJoinTaskClass(classInfo.getCtClass()); + classInfo.setModified(); + } else if (disableInheritableForThreadPool && FORK_JOIN_POOL_CLASS_NAME.equals(classInfo.getClassName())) { + updateConstructorDisableInheritable(classInfo.getCtClass()); + classInfo.setModified(); + } + } + + /** + * @see Utils#doCaptureWhenNotTtlEnhanced(java.lang.Object) + */ + private void updateForkJoinTaskClass(@NotNull final CtClass clazz) throws CannotCompileException, NotFoundException { + final String className = clazz.getName(); + + // add new field + final String capturedFieldName = "captured$field$added$by$ttl"; + final CtField capturedField = CtField.make("private final Object " + capturedFieldName + ";", clazz); + clazz.addField(capturedField, "com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.doCaptureWhenNotTtlEnhanced(this);"); + logger.info("add new field " + capturedFieldName + " to class " + className); + + final CtMethod doExecMethod = clazz.getDeclaredMethod("doExec", new CtClass[0]); + final String doExec_renamed_method_name = Utils.renamedMethodNameByTtl(doExecMethod); + + final String beforeCode = "if (this instanceof " + TtlEnhanced.class.getName() + ") {\n" + // if the class is already TTL enhanced(eg: com.alibaba.ttl.TtlRecursiveTask) + " return " + doExec_renamed_method_name + "($$);\n" + // return directly/do nothing + "}\n" + + "Object backup = com.alibaba.ttl.TransmittableThreadLocal.Transmitter.replay(" + capturedFieldName + ");"; + + final String finallyCode = "com.alibaba.ttl.TransmittableThreadLocal.Transmitter.restore(backup);"; + + Utils.doTryFinallyForMethod(doExecMethod, doExec_renamed_method_name, beforeCode, finallyCode); + } + + private void updateConstructorDisableInheritable(@NotNull final CtClass clazz) throws NotFoundException, CannotCompileException { + for (CtConstructor constructor : clazz.getDeclaredConstructors()) { + final CtClass[] parameterTypes = constructor.getParameterTypes(); + final StringBuilder insertCode = new StringBuilder(); + for (int i = 0; i < parameterTypes.length; i++) { + final String paramTypeName = parameterTypes[i].getName(); + if (FORK_JOIN_WORKER_THREAD_FACTORY_CLASS_NAME.equals(paramTypeName)) { + String code = String.format("$%d = com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper.getDisableInheritableForkJoinWorkerThreadFactory($% 0) constructor.insertBefore(insertCode.toString()); + } + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlTimerTaskTransformlet.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlTimerTaskTransformlet.java new file mode 100644 index 000000000..0505d2434 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlTimerTaskTransformlet.java @@ -0,0 +1,71 @@ +package com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.impl; + +import com.fr.third.alibaba.ttl.threadpool.agent.internal.logging.Logger; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.ClassInfo; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.JavassistTransformlet; +import org.jetbrains.annotations.NotNull; +import com.fr.third.javassist.*; + +import java.io.IOException; + +import static com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.doTryFinallyForMethod; + +/** + * TTL {@link JavassistTransformlet} for {@link java.util.TimerTask}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @author wuwen5 (wuwen.55 at aliyun dot com) + * @see java.util.TimerTask + * @see java.util.Timer + * @since 2.7.0 + */ +public class TtlTimerTaskTransformlet implements JavassistTransformlet { + private static final Logger logger = Logger.getLogger(TtlTimerTaskTransformlet.class); + + private static final String TIMER_TASK_CLASS_NAME = "java.util.TimerTask"; + private static final String RUN_METHOD_NAME = "run"; + + @Override + public void doTransform(@NotNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { + if (TIMER_TASK_CLASS_NAME.equals(classInfo.getClassName())) return; // No need transform TimerTask class + + final CtClass clazz = classInfo.getCtClass(); + + if (clazz.isPrimitive() || clazz.isArray() || clazz.isInterface() || clazz.isAnnotation()) { + return; + } + // class contains method `void run()` ? + try { + final CtMethod runMethod = clazz.getDeclaredMethod(RUN_METHOD_NAME, new CtClass[0]); + if (!CtClass.voidType.equals(runMethod.getReturnType())) return; + } catch (NotFoundException e) { + return; + } + if (!clazz.subclassOf(clazz.getClassPool().get(TIMER_TASK_CLASS_NAME))) return; + + logger.info("Transforming class " + classInfo.getClassName()); + + updateTimerTaskClass(clazz); + classInfo.setModified(); + } + + /** + * @see Utils#doCaptureWhenNotTtlEnhanced(java.lang.Object) + */ + private void updateTimerTaskClass(@NotNull final CtClass clazz) throws CannotCompileException, NotFoundException { + final String className = clazz.getName(); + + // add new field + final String capturedFieldName = "captured$field$added$by$ttl"; + final CtField capturedField = CtField.make("private final Object " + capturedFieldName + ";", clazz); + clazz.addField(capturedField, "com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.doCaptureWhenNotTtlEnhanced(this);"); + logger.info("add new field " + capturedFieldName + " to class " + className); + + final CtMethod runMethod = clazz.getDeclaredMethod(RUN_METHOD_NAME, new CtClass[0]); + + final String beforeCode = "Object backup = com.alibaba.ttl.TransmittableThreadLocal.Transmitter.replay(" + capturedFieldName + ");"; + final String finallyCode = "com.alibaba.ttl.TransmittableThreadLocal.Transmitter.restore(backup);"; + + Utils.doTryFinallyForMethod(runMethod, beforeCode, finallyCode); + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/Utils.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/Utils.java new file mode 100644 index 000000000..39340a96c --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/impl/Utils.java @@ -0,0 +1,119 @@ +package com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.impl; + +import com.fr.third.alibaba.ttl.TtlRunnable; +import com.fr.third.alibaba.ttl.spi.TtlAttachments; +import com.fr.third.alibaba.ttl.spi.TtlEnhanced; +import com.fr.third.alibaba.ttl.threadpool.agent.internal.logging.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import com.fr.third.javassist.*; + +import java.lang.reflect.Modifier; + +import static com.fr.third.alibaba.ttl.TransmittableThreadLocal.Transmitter.capture; + +/** + * @author Jerry Lee (oldratlee at gmail dot com) + * @since 2.6.0 + */ +public class Utils { + private static final Logger logger = Logger.getLogger(Utils.class); + + /** + * String like {@code public ScheduledFuture scheduleAtFixedRate(Runnable, long, long, TimeUnit)} + * for {@link java.util.concurrent.ScheduledThreadPoolExecutor#scheduleAtFixedRate}. + * + * @param method method object + * @return method signature string + */ + @NotNull + static String signatureOfMethod(@NotNull final CtBehavior method) throws NotFoundException { + final StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.append(Modifier.toString(method.getModifiers())); + if (method instanceof CtMethod) { + final String returnType = ((CtMethod) method).getReturnType().getSimpleName(); + stringBuilder.append(" ").append(returnType); + } + stringBuilder.append(" ").append(method.getName()).append("("); + + final CtClass[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + CtClass parameterType = parameterTypes[i]; + if (i != 0) stringBuilder.append(", "); + stringBuilder.append(parameterType.getSimpleName()); + } + + stringBuilder.append(")"); + return stringBuilder.toString(); + } + + @NotNull + static String renamedMethodNameByTtl(@NotNull CtMethod method) { + return "original$" + method.getName() + "$method$renamed$by$ttl"; + } + + static void doTryFinallyForMethod(@NotNull CtMethod method, @NotNull String beforeCode, @NotNull String finallyCode) throws CannotCompileException, NotFoundException { + doTryFinallyForMethod(method, renamedMethodNameByTtl(method), beforeCode, finallyCode); + } + + static void doTryFinallyForMethod(@NotNull CtMethod method, @NotNull String renamedMethodName, @NotNull String beforeCode, @NotNull String finallyCode) throws CannotCompileException, NotFoundException { + final CtClass clazz = method.getDeclaringClass(); + final CtMethod newMethod = CtNewMethod.copy(method, clazz, null); + + // rename original method, and set to private method(avoid reflect out renamed method unexpectedly) + method.setName(renamedMethodName); + method.setModifiers(method.getModifiers() + & ~Modifier.PUBLIC /* remove public */ + & ~Modifier.PROTECTED /* remove protected */ + | Modifier.PRIVATE /* add private */); + + final String returnOp; + if (method.getReturnType() == CtClass.voidType) { + returnOp = ""; + } else { + returnOp = "return "; + } + // set new method implementation + final String code = "{\n" + + beforeCode + "\n" + + "try {\n" + + " " + returnOp + renamedMethodName + "($$);\n" + + "} finally {\n" + + " " + finallyCode + "\n" + + "} }"; + newMethod.setBody(code); + clazz.addMethod(newMethod); + logger.info("insert code around method " + signatureOfMethod(newMethod) + " of class " + clazz.getName() + ": " + code); + } + + @Nullable + public static Object doCaptureWhenNotTtlEnhanced(@Nullable Object obj) { + if (obj instanceof TtlEnhanced) return null; + else return capture(); + } + + public static void setAutoWrapperAttachment(@Nullable Object ttlAttachment) { + if (notTtlAttachments(ttlAttachment)) return; + ((TtlAttachments) ttlAttachment).setTtlAttachment(TtlAttachments.KEY_IS_AUTO_WRAPPER, true); + } + + @Nullable + public static Runnable unwrapIfIsAutoWrapper(@Nullable Runnable runnable) { + if (isAutoWrapper(runnable)) return TtlRunnable.unwrap(runnable); + else return runnable; + } + + private static boolean notTtlAttachments(@Nullable Object ttlAttachment) { + return !(ttlAttachment instanceof TtlAttachments); + } + + private static boolean isAutoWrapper(@Nullable Runnable ttlAttachments) { + if (notTtlAttachments(ttlAttachments)) return false; + + final Boolean value = ((TtlAttachments) ttlAttachments).getTtlAttachment(TtlAttachments.KEY_IS_AUTO_WRAPPER); + if (value == null) return false; + + return value; + } +} diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/package-info.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/package-info.java new file mode 100644 index 000000000..0705dc7db --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/internal/transformlet/package-info.java @@ -0,0 +1,7 @@ +/** + * TTL {@code Transformlet} implementations by {@code Javassist}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet.JavassistTransformlet + */ +package com.fr.third.alibaba.ttl.threadpool.agent.internal.transformlet; diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/package-info.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/package-info.java new file mode 100644 index 000000000..b0a944813 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/agent/package-info.java @@ -0,0 +1,8 @@ +/** + * TTL Agent. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.fr.third.alibaba.ttl.threadpool.agent.TtlAgent + * @see The mechanism for instrumentation + */ +package com.fr.third.alibaba.ttl.threadpool.agent; diff --git a/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/package-info.java b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/package-info.java new file mode 100644 index 000000000..ee37492d5 --- /dev/null +++ b/fine-transmittable-thread-local/src/main/java/com/fr/third/alibaba/ttl/threadpool/package-info.java @@ -0,0 +1,6 @@ +/** + * Thread pool wrap/decoration utils. + * + * @author Jerry Lee (oldratlee at gmail dot com) + */ +package com.fr.third.alibaba.ttl.threadpool; From 8cad028623d000a2f393fe03995c903d2947f74d Mon Sep 17 00:00:00 2001 From: Elijah Date: Fri, 4 Sep 2020 16:59:41 +0800 Subject: [PATCH 2/2] =?UTF-8?q?DEC-14578=20=E6=89=93=E5=8C=85=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.third_step8-jdk11.gradle | 4 +++- build.third_step8.gradle | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build.third_step8-jdk11.gradle b/build.third_step8-jdk11.gradle index f320db709..3825698d4 100644 --- a/build.third_step8-jdk11.gradle +++ b/build.third_step8-jdk11.gradle @@ -26,6 +26,7 @@ sourceSets{ java{ srcDirs=[ "${srcDir}/fine-ehcache/src/main/java" + "${srcDir}/fine-transmittable-thread-local/src/main/java" ] } } @@ -33,7 +34,8 @@ sourceSets{ def resourceDirs = [ "${srcDir}/fine-ehcache/src/main/java", - "${srcDir}/fine-ehcache/src/main/recources" + "${srcDir}/fine-ehcache/src/main/recources", + "${srcDir}/fine-transmittable-thread-local/src/main/java" ] sourceSets.main.java.outputDir = file('build/classes/8') diff --git a/build.third_step8.gradle b/build.third_step8.gradle index f2112233b..8410bac6b 100644 --- a/build.third_step8.gradle +++ b/build.third_step8.gradle @@ -25,7 +25,8 @@ sourceSets{ main{ java{ srcDirs=[ - "${srcDir}/fine-ehcache/src/main/java" + "${srcDir}/fine-ehcache/src/main/java", + "${srcDir}/fine-transmittable-thread-local/src/main/java" ] } } @@ -70,6 +71,7 @@ task copyFiles(type:Copy,dependsOn:'compileJava'){ println "------------------------------------------------copyfiles" with dataContent.call("${srcDir}/fine-ehcache/src/main/java") with dataContent.call("${srcDir}/fine-ehcache/src/main/recources") + with dataContent.call("${srcDir}/fine-transmittable-thread-local/src/main/java") into "${classesDir}" } }