Elijah
4 years ago
38 changed files with 3797 additions and 0 deletions
@ -0,0 +1,41 @@ |
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
|
||||||
|
<parent> |
||||||
|
<groupId>com.fr.third</groupId> |
||||||
|
<artifactId>step8</artifactId> |
||||||
|
<version>10.0-FEATURE-SNAPSHOT</version> |
||||||
|
<relativePath>../base-third-project/base-third-step8</relativePath> |
||||||
|
</parent> |
||||||
|
|
||||||
|
<artifactId>fine-transmittable-thread-local</artifactId> |
||||||
|
<version>${revision}</version> |
||||||
|
|
||||||
|
<dependencies> |
||||||
|
<!-- |
||||||
|
javassist v3.23 is the last version support Java 6 |
||||||
|
DO NOT upgrade javassist version! |
||||||
|
--> |
||||||
|
<!--<dependency> |
||||||
|
<groupId>org.javassist</groupId> |
||||||
|
<artifactId>javassist</artifactId> |
||||||
|
<version>3.23.2-GA</version> |
||||||
|
<optional>true</optional> |
||||||
|
</dependency>--> |
||||||
|
<dependency> |
||||||
|
<groupId>com.fr.third</groupId> |
||||||
|
<artifactId>fine-javassist</artifactId> |
||||||
|
<version>${revision}</version> |
||||||
|
</dependency> |
||||||
|
|
||||||
|
<dependency> |
||||||
|
<groupId>com.fr.third</groupId> |
||||||
|
<artifactId>fine-jetbrains</artifactId> |
||||||
|
<version>${revision}</version> |
||||||
|
</dependency> |
||||||
|
<!-- Testing frameworks and related dependencies --> |
||||||
|
</dependencies> |
||||||
|
|
||||||
|
</project> |
@ -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. |
||||||
|
* <p> |
||||||
|
* <b>Note</b>:<br> |
||||||
|
* {@link TransmittableThreadLocal} extends {@link InheritableThreadLocal}, |
||||||
|
* so {@link TransmittableThreadLocal} first is a {@link InheritableThreadLocal}.<br> |
||||||
|
* If the <b>inheritable</b> ability from {@link InheritableThreadLocal} has <b>potential leaking problem</b>, |
||||||
|
* you can disable the <b>inheritable</b> ability: |
||||||
|
* <p> |
||||||
|
* ❶ 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 <b>never</b> happen, |
||||||
|
* because threads in thread pooling components is pre-created and pooled, these threads is neutral for biz logic/data. |
||||||
|
* <br> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* ❷ or by overriding method {@link #childValue(Object)}. |
||||||
|
* Whether the value should be inheritable or not can be controlled by the data owner, |
||||||
|
* disable it <b>carefully</b> when data owner have a clear idea. |
||||||
|
* <pre> {@code TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<String>() { |
||||||
|
* protected String childValue(String parentValue) { |
||||||
|
* return initialValue(); |
||||||
|
* } |
||||||
|
* }}</pre> |
||||||
|
* <p> |
||||||
|
* More discussion about "disable the <b>inheritable</b> ability" |
||||||
|
* see <a href="https://github.com/alibaba/transmittable-thread-local/issues/100"> |
||||||
|
* issue #100: disable Inheritable when it's not necessary and buggy</a>. |
||||||
|
* |
||||||
|
* @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<T> extends InheritableThreadLocal<T> implements TtlCopier<T> { |
||||||
|
private static final Logger logger = Logger.getLogger(TransmittableThreadLocal.class.getName()); |
||||||
|
|
||||||
|
private final boolean disableIgnoreNullValueSemantics; |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor. |
||||||
|
* <p> |
||||||
|
* Create a {@link TransmittableThreadLocal} instance with "Ignore-Null-Value Semantics". |
||||||
|
* About "Ignore-Null-Value Semantics": |
||||||
|
* <p> |
||||||
|
* <ol> |
||||||
|
* <li>If value is {@code null}(check by {@link #get()} method), do NOT transmit this {@code ThreadLocal}.</li> |
||||||
|
* <li>If set {@code null} value, also remove value(invoke {@link #remove()} method).</li> |
||||||
|
* </ol> |
||||||
|
* <p> |
||||||
|
* This is a pragmatic design decision: |
||||||
|
* <ol> |
||||||
|
* <li>use explicit value type rather than {@code null} to biz intent.</li> |
||||||
|
* <li>more safe(avoid {@code NPE}) and gc friendly.</li> |
||||||
|
* </ol> |
||||||
|
* <p> |
||||||
|
* So it's not recommended to use {@code null} value. |
||||||
|
* <p> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* More info see <a href="https://github.com/alibaba/transmittable-thread-local/issues/157">Issue #157</a>. |
||||||
|
* |
||||||
|
* @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. |
||||||
|
* <p> |
||||||
|
* This method is called from {@link TtlRunnable} or |
||||||
|
* {@link TtlCallable} when it create, before the task is started. |
||||||
|
* <p> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* Default behavior is to do nothing, and should be overridden |
||||||
|
* if a different behavior is desired. |
||||||
|
* <p> |
||||||
|
* Do not throw any exception, just ignored. |
||||||
|
* |
||||||
|
* @since 1.2.0 |
||||||
|
*/ |
||||||
|
protected void beforeExecute() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Callback method after task object({@link TtlRunnable}/{@link TtlCallable}) execute. |
||||||
|
* <p> |
||||||
|
* Default behavior is to do nothing, and should be overridden |
||||||
|
* if a different behavior is desired. |
||||||
|
* <p> |
||||||
|
* 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<TransmittableThreadLocal<Object>, ?>.
|
||||||
|
// 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<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder = |
||||||
|
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() { |
||||||
|
@Override |
||||||
|
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() { |
||||||
|
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) { |
||||||
|
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
private void addThisToHolder() { |
||||||
|
if (!holder.get().containsKey(this)) { |
||||||
|
holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void removeThisFromHolder() { |
||||||
|
holder.get().remove(this); |
||||||
|
} |
||||||
|
|
||||||
|
private static void doExecuteCallback(boolean isBefore) { |
||||||
|
for (TransmittableThreadLocal<Object> 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<Object> 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). |
||||||
|
* <p> |
||||||
|
* {@link Transmitter} is <b><i>internal</i></b> manipulation api for <b><i>framework/middleware integration</i></b>; |
||||||
|
* In general, you will <b><i>never</i></b> use it in the <i>biz/application code</i>! |
||||||
|
* |
||||||
|
* <h2>Framework/Middleware integration to TTL transmittance</h2> |
||||||
|
* Below is the example code: |
||||||
|
* |
||||||
|
* <pre><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) |
||||||
|
* } |
||||||
|
* </code></pre> |
||||||
|
* <p> |
||||||
|
* see the implementation code of {@link TtlRunnable} and {@link TtlCallable} for more actual code sample. |
||||||
|
* <p> |
||||||
|
* 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}. |
||||||
|
* <p> |
||||||
|
* Below is the example code: |
||||||
|
* |
||||||
|
* <pre><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)
|
||||||
|
* </code></pre> |
||||||
|
* <p> |
||||||
|
* The reason of providing 2 util methods is the different {@code throws Exception} type |
||||||
|
* so as to satisfy your biz logic({@code lambda}): |
||||||
|
* <ol> |
||||||
|
* <li>{@link #runCallableWithCaptured(Object, Callable)}: {@code throws Exception}</li> |
||||||
|
* <li>{@link #runSupplierWithCaptured(Object, Supplier)}: No {@code throws}</li> |
||||||
|
* </ol> |
||||||
|
* <p> |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* <h2>ThreadLocal Integration</h2> |
||||||
|
* 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 <b>Transmittable</b> ability for the existed {@link ThreadLocal} instances. |
||||||
|
* <p> |
||||||
|
* Below is the example code: |
||||||
|
* |
||||||
|
* <pre><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); |
||||||
|
* </code></pre> |
||||||
|
* |
||||||
|
* <B><I>Caution:</I></B><br> |
||||||
|
* If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, |
||||||
|
* the instance can NOT <B><I>{@code inherit}</I></B> value from parent thread(aka. the <b>inheritable</b> 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<TransmittableThreadLocal<Object>, Object> captureTtlValues() { |
||||||
|
HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>(); |
||||||
|
for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) { |
||||||
|
ttl2Value.put(threadLocal, threadLocal.copyValue()); |
||||||
|
} |
||||||
|
return ttl2Value; |
||||||
|
} |
||||||
|
|
||||||
|
private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() { |
||||||
|
final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>(); |
||||||
|
for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) { |
||||||
|
final ThreadLocal<Object> threadLocal = entry.getKey(); |
||||||
|
final TtlCopier<Object> 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<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NotNull HashMap<TransmittableThreadLocal<Object>, Object> captured) { |
||||||
|
HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<TransmittableThreadLocal<Object>, Object>(); |
||||||
|
|
||||||
|
for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { |
||||||
|
TransmittableThreadLocal<Object> 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<ThreadLocal<Object>, Object> replayThreadLocalValues(@NotNull HashMap<ThreadLocal<Object>, Object> captured) { |
||||||
|
final HashMap<ThreadLocal<Object>, Object> backup = new HashMap<ThreadLocal<Object>, Object>(); |
||||||
|
|
||||||
|
for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) { |
||||||
|
final ThreadLocal<Object> 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<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>(); |
||||||
|
|
||||||
|
final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>(); |
||||||
|
for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) { |
||||||
|
final ThreadLocal<Object> 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<TransmittableThreadLocal<Object>, Object> backup) { |
||||||
|
// call afterExecute callback
|
||||||
|
doExecuteCallback(false); |
||||||
|
|
||||||
|
for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { |
||||||
|
TransmittableThreadLocal<Object> 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<TransmittableThreadLocal<Object>, Object> ttlValues) { |
||||||
|
for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : ttlValues.entrySet()) { |
||||||
|
TransmittableThreadLocal<Object> threadLocal = entry.getKey(); |
||||||
|
threadLocal.set(entry.getValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static void restoreThreadLocalValues(@NotNull HashMap<ThreadLocal<Object>, Object> backup) { |
||||||
|
for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) { |
||||||
|
final ThreadLocal<Object> threadLocal = entry.getKey(); |
||||||
|
threadLocal.set(entry.getValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class Snapshot { |
||||||
|
final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value; |
||||||
|
final HashMap<ThreadLocal<Object>, Object> threadLocal2Value; |
||||||
|
|
||||||
|
private Snapshot(HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value, HashMap<ThreadLocal<Object>, 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 <R> 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> R runSupplierWithCaptured(@NotNull Object captured, @NotNull Supplier<R> 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 <R> 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> R runSupplierWithClear(@NotNull Supplier<R> 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 <R> 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> R runCallableWithCaptured(@NotNull Object captured, @NotNull Callable<R> 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 <R> 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> R runCallableWithClear(@NotNull Callable<R> bizLogic) throws Exception { |
||||||
|
Object backup = clear(); |
||||||
|
try { |
||||||
|
return bizLogic.call(); |
||||||
|
} finally { |
||||||
|
restore(backup); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static volatile WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>> threadLocalHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>(); |
||||||
|
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 <b>Transmittable</b> ability for the existed {@link ThreadLocal} instances. |
||||||
|
* <p> |
||||||
|
* 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 <b>Transmittable</b> 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 <T> boolean registerThreadLocal(@NotNull ThreadLocal<T> threadLocal, @NotNull TtlCopier<T> copier) { |
||||||
|
return registerThreadLocal(threadLocal, copier, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances |
||||||
|
* to enhance the <b>Transmittable</b> ability for the existed {@link ThreadLocal} instances. |
||||||
|
* <p> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* 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 <b>Transmittable</b> 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 <T> boolean registerThreadLocalWithShadowCopier(@NotNull ThreadLocal<T> threadLocal) { |
||||||
|
return registerThreadLocal(threadLocal, (TtlCopier<T>) shadowCopier, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances |
||||||
|
* to enhance the <b>Transmittable</b> ability for the existed {@link ThreadLocal} instances. |
||||||
|
* <p> |
||||||
|
* 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 <b>Transmittable</b> 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 <T> boolean registerThreadLocal(@NotNull ThreadLocal<T> threadLocal, @NotNull TtlCopier<T> 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<ThreadLocal<Object>, TtlCopier<Object>> newHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>(threadLocalHolder); |
||||||
|
newHolder.put((ThreadLocal<Object>) threadLocal, (TtlCopier<Object>) copier); |
||||||
|
threadLocalHolder = newHolder; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances |
||||||
|
* to enhance the <b>Transmittable</b> ability for the existed {@link ThreadLocal} instances. |
||||||
|
* <p> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* 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 <b>Transmittable</b> 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 <T> boolean registerThreadLocalWithShadowCopier(@NotNull ThreadLocal<T> threadLocal, boolean force) { |
||||||
|
return registerThreadLocal(threadLocal, (TtlCopier<T>) shadowCopier, force); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unregister the {@link ThreadLocal} instances |
||||||
|
* to remove the <b>Transmittable</b> ability for the {@link ThreadLocal} instances. |
||||||
|
* <p> |
||||||
|
* 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 <T> boolean unregisterThreadLocal(@NotNull ThreadLocal<T> 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<ThreadLocal<Object>, TtlCopier<Object>> newHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>(threadLocalHolder); |
||||||
|
newHolder.remove(threadLocal); |
||||||
|
threadLocalHolder = newHolder; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static final TtlCopier<Object> shadowCopier = new TtlCopier<Object>() { |
||||||
|
@Override |
||||||
|
public Object copy(Object parentValue) { |
||||||
|
return parentValue; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
private Transmitter() { |
||||||
|
throw new InstantiationError("Must not instantiate this class"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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. |
||||||
|
* <p> |
||||||
|
* Use factory method {@link #get(Callable)} to get decorated instance. |
||||||
|
* <p> |
||||||
|
* 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<V> implements Callable<V>, TtlWrapper<Callable<V>>, TtlEnhanced, TtlAttachments { |
||||||
|
private final AtomicReference<Object> capturedRef; |
||||||
|
private final Callable<V> callable; |
||||||
|
private final boolean releaseTtlValueReferenceAfterCall; |
||||||
|
|
||||||
|
private TtlCallable(@NotNull Callable<V> callable, boolean releaseTtlValueReferenceAfterCall) { |
||||||
|
this.capturedRef = new AtomicReference<Object>(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<V> getCallable() { |
||||||
|
return unwrap(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* unwrap to the original/underneath {@link Callable}. |
||||||
|
* |
||||||
|
* @see TtlUnwrap#unwrap(Object) |
||||||
|
* @since 2.11.4 |
||||||
|
*/ |
||||||
|
@NotNull |
||||||
|
@Override |
||||||
|
public Callable<V> 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}. |
||||||
|
* <p> |
||||||
|
* This method is idempotent. |
||||||
|
* |
||||||
|
* @param callable input {@link Callable} |
||||||
|
* @return Wrapped {@link Callable} |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
public static <T> TtlCallable<T> get(@Nullable Callable<T> callable) { |
||||||
|
return get(callable, false); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Factory method, wrap input {@link Callable} to {@link TtlCallable}. |
||||||
|
* <p> |
||||||
|
* 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 <T> TtlCallable<T> get(@Nullable Callable<T> callable, boolean releaseTtlValueReferenceAfterCall) { |
||||||
|
return get(callable, releaseTtlValueReferenceAfterCall, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Factory method, wrap input {@link Callable} to {@link TtlCallable}. |
||||||
|
* <p> |
||||||
|
* 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! <b>DO NOT</b> set, only when you know why. |
||||||
|
* @return Wrapped {@link Callable} |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
public static <T> TtlCallable<T> get(@Nullable Callable<T> callable, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) { |
||||||
|
if (null == callable) return null; |
||||||
|
|
||||||
|
if (callable instanceof TtlEnhanced) { |
||||||
|
// avoid redundant decoration, and ensure idempotency
|
||||||
|
if (idempotent) return (TtlCallable<T>) callable; |
||||||
|
else throw new IllegalStateException("Already TtlCallable!"); |
||||||
|
} |
||||||
|
return new TtlCallable<T>(callable, releaseTtlValueReferenceAfterCall); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* wrap input {@link Callable} Collection to {@link TtlCallable} Collection. |
||||||
|
* |
||||||
|
* @param tasks task to be wrapped |
||||||
|
* @return Wrapped {@link Callable} |
||||||
|
*/ |
||||||
|
@NotNull |
||||||
|
public static <T> List<TtlCallable<T>> gets(@Nullable Collection<? extends Callable<T>> 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 <T> List<TtlCallable<T>> gets(@Nullable Collection<? extends Callable<T>> 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! <b>DO NOT</b> set, only when you know why. |
||||||
|
* @return Wrapped {@link Callable} |
||||||
|
*/ |
||||||
|
@NotNull |
||||||
|
public static <T> List<TtlCallable<T>> gets(@Nullable Collection<? extends Callable<T>> tasks, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) { |
||||||
|
if (null == tasks) return Collections.emptyList(); |
||||||
|
|
||||||
|
List<TtlCallable<T>> copy = new ArrayList<TtlCallable<T>>(); |
||||||
|
for (Callable<T> task : tasks) { |
||||||
|
copy.add(TtlCallable.get(task, releaseTtlValueReferenceAfterCall, idempotent)); |
||||||
|
} |
||||||
|
return copy; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unwrap {@link TtlCallable} to the original/underneath one. |
||||||
|
* <p> |
||||||
|
* 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}. |
||||||
|
* <p> |
||||||
|
* 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 <T> Callable<T> unwrap(@Nullable Callable<T> callable) { |
||||||
|
if (!(callable instanceof TtlCallable)) return callable; |
||||||
|
else return ((TtlCallable<T>) callable).getCallable(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unwrap {@link TtlCallable} to the original/underneath one. |
||||||
|
* <p> |
||||||
|
* Invoke {@link #unwrap(Callable)} for each element in input collection. |
||||||
|
* <p> |
||||||
|
* 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 <T> List<Callable<T>> unwraps(@Nullable Collection<? extends Callable<T>> tasks) { |
||||||
|
if (null == tasks) return Collections.emptyList(); |
||||||
|
|
||||||
|
List<Callable<T>> copy = new ArrayList<Callable<T>>(); |
||||||
|
for (Callable<T> task : tasks) { |
||||||
|
if (!(task instanceof TtlCallable)) copy.add(task); |
||||||
|
else copy.add(((TtlCallable<T>) 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> T getTtlAttachment(@NotNull String key) { |
||||||
|
return ttlAttachment.getTtlAttachment(key); |
||||||
|
} |
||||||
|
} |
@ -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<T> { |
||||||
|
/** |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* 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); |
||||||
|
} |
@ -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 { |
||||||
|
} |
@ -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}. |
||||||
|
* <p> |
||||||
|
* 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<Void> 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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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}. |
||||||
|
* <p> |
||||||
|
* 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<V> extends ForkJoinTask<V> 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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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. |
||||||
|
* <p> |
||||||
|
* Use factory methods {@link #get} / {@link #gets} to create instance. |
||||||
|
* <p> |
||||||
|
* 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<Runnable>, TtlEnhanced, TtlAttachments { |
||||||
|
private final AtomicReference<Object> capturedRef; |
||||||
|
private final Runnable runnable; |
||||||
|
private final boolean releaseTtlValueReferenceAfterRun; |
||||||
|
|
||||||
|
private TtlRunnable(@NotNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { |
||||||
|
this.capturedRef = new AtomicReference<Object>(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}. |
||||||
|
* <B><I>Caution</I></B>: {@code true} will cover up bugs! <b>DO NOT</b> 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<TtlRunnable> gets(@Nullable Collection<? extends Runnable> 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<TtlRunnable> gets(@Nullable Collection<? extends Runnable> 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}. |
||||||
|
* <B><I>Caution</I></B>: {@code true} will cover up bugs! <b>DO NOT</b> set, only when you know why. |
||||||
|
* @return wrapped tasks |
||||||
|
* @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. |
||||||
|
*/ |
||||||
|
@NotNull |
||||||
|
public static List<TtlRunnable> gets(@Nullable Collection<? extends Runnable> tasks, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { |
||||||
|
if (null == tasks) return Collections.emptyList(); |
||||||
|
|
||||||
|
List<TtlRunnable> copy = new ArrayList<TtlRunnable>(); |
||||||
|
for (Runnable task : tasks) { |
||||||
|
copy.add(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent)); |
||||||
|
} |
||||||
|
return copy; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unwrap {@link TtlRunnable} to the original/underneath one. |
||||||
|
* <p> |
||||||
|
* 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}. |
||||||
|
* <p> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* Invoke {@link #unwrap(Runnable)} for each element in input collection. |
||||||
|
* <p> |
||||||
|
* 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<Runnable> unwraps(@Nullable Collection<? extends Runnable> tasks) { |
||||||
|
if (null == tasks) return Collections.emptyList(); |
||||||
|
|
||||||
|
List<Runnable> copy = new ArrayList<Runnable>(); |
||||||
|
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> T getTtlAttachment(@NotNull String key) { |
||||||
|
return ttlAttachment.getTtlAttachment(key); |
||||||
|
} |
||||||
|
} |
@ -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}. |
||||||
|
* <p> |
||||||
|
* Use factory method {@link #get(TimerTask)} to create instance. |
||||||
|
* <p> |
||||||
|
* <b>NOTE:</b> |
||||||
|
* 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 <a href="https://alibaba.github.io/Alibaba-Java-Coding-Guidelines/#concurrency">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.</a> |
||||||
|
* @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<TimerTask>, com.fr.third.alibaba.ttl.spi.TtlEnhanced { |
||||||
|
private final AtomicReference<Object> capturedRef; |
||||||
|
private final TimerTask timerTask; |
||||||
|
private final boolean releaseTtlValueReferenceAfterRun; |
||||||
|
|
||||||
|
private TtlTimerTask(@NotNull TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun) { |
||||||
|
this.capturedRef = new AtomicReference<Object>(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}. |
||||||
|
* <p> |
||||||
|
* 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}. |
||||||
|
* <p> |
||||||
|
* 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}. |
||||||
|
* <p> |
||||||
|
* 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! <b>DO NOT</b> 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. |
||||||
|
* <p> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* Invoke {@link #unwrap(TimerTask)} for each element in input collection. |
||||||
|
* <p> |
||||||
|
* 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<TimerTask> unwraps(@Nullable Collection<? extends TimerTask> tasks) { |
||||||
|
if (null == tasks) return Collections.emptyList(); |
||||||
|
|
||||||
|
List<TimerTask> copy = new ArrayList<TimerTask>(); |
||||||
|
for (TimerTask task : tasks) { |
||||||
|
if (!(task instanceof TtlTimerTask)) copy.add(task); |
||||||
|
else copy.add(((TtlTimerTask) task).getTimerTask()); |
||||||
|
} |
||||||
|
return copy; |
||||||
|
} |
||||||
|
} |
@ -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. |
||||||
|
* <p> |
||||||
|
* <b><i>Note:</i></b> |
||||||
|
* all methods is {@code null}-safe, when input parameter is {@code null}, return {@code null}. |
||||||
|
* <p> |
||||||
|
* <b><i>Implementation Note:</i></b> |
||||||
|
* The util methods in this class should have been inside {@link TtlWrappers}.<br> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* 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> T unwrap(@Nullable T obj) { |
||||||
|
if (!isWrapper(obj)) return obj; |
||||||
|
else return ((TtlWrapper<T>) obj).unwrap(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* check the input object is a {@code TtlWrapper} or not. |
||||||
|
* |
||||||
|
* @since 2.11.4 |
||||||
|
*/ |
||||||
|
public static <T> boolean isWrapper(@Nullable T obj) { |
||||||
|
return obj instanceof TtlWrapper; |
||||||
|
} |
||||||
|
|
||||||
|
private TtlUnwrap() { |
||||||
|
throw new InstantiationError("Must not instantiate this class"); |
||||||
|
} |
||||||
|
} |
@ -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}. |
||||||
|
* <p> |
||||||
|
* <b><i>Note:</i></b> |
||||||
|
* <ul> |
||||||
|
* <li>all methods is {@code null}-safe, when input parameter is {@code null}, return {@code null}.</li> |
||||||
|
* <li>all wrap method skip wrap (aka. just return input parameter), when input parameter is already wrapped.</li> |
||||||
|
* </ul> |
||||||
|
* |
||||||
|
* @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 <T> Supplier<T> wrap(@Nullable Supplier<T> supplier) { |
||||||
|
if (supplier == null) return null; |
||||||
|
else if (supplier instanceof TtlEnhanced) return supplier; |
||||||
|
else return new TtlSupplier<T>(supplier); |
||||||
|
} |
||||||
|
|
||||||
|
private static class TtlSupplier<T> implements Supplier<T>, TtlWrapper<Supplier<T>>, TtlEnhanced { |
||||||
|
final Supplier<T> supplier; |
||||||
|
final Object captured; |
||||||
|
|
||||||
|
TtlSupplier(@NotNull Supplier<T> 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<T> 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 <T> Consumer<T> wrap(@Nullable Consumer<T> consumer) { |
||||||
|
if (consumer == null) return null; |
||||||
|
else if (consumer instanceof TtlEnhanced) return consumer; |
||||||
|
else return new TtlConsumer<T>(consumer); |
||||||
|
} |
||||||
|
|
||||||
|
private static class TtlConsumer<T> implements Consumer<T>, TtlWrapper<Consumer<T>>, TtlEnhanced { |
||||||
|
final Consumer<T> consumer; |
||||||
|
final Object captured; |
||||||
|
|
||||||
|
TtlConsumer(@NotNull Consumer<T> 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<T> 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 <T, U> BiConsumer<T, U> wrap(@Nullable BiConsumer<T, U> consumer) { |
||||||
|
if (consumer == null) return null; |
||||||
|
else if (consumer instanceof TtlEnhanced) return consumer; |
||||||
|
else return new TtlBiConsumer<T, U>(consumer); |
||||||
|
} |
||||||
|
|
||||||
|
private static class TtlBiConsumer<T, U> implements BiConsumer<T, U>, TtlWrapper<BiConsumer<T, U>>, TtlEnhanced { |
||||||
|
final BiConsumer<T, U> consumer; |
||||||
|
final Object captured; |
||||||
|
|
||||||
|
TtlBiConsumer(@NotNull BiConsumer<T, U> 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<T, U> 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 <T, R> Function<T, R> wrap(@Nullable Function<T, R> fn) { |
||||||
|
if (fn == null) return null; |
||||||
|
else if (fn instanceof TtlEnhanced) return fn; |
||||||
|
else return new TtlFunction<T, R>(fn); |
||||||
|
} |
||||||
|
|
||||||
|
private static class TtlFunction<T, R> implements Function<T, R>, TtlWrapper<Function<T, R>>, TtlEnhanced { |
||||||
|
final Function<T, R> fn; |
||||||
|
final Object captured; |
||||||
|
|
||||||
|
TtlFunction(@NotNull Function<T, R> 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<T, R> 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 <T, U, R> BiFunction<T, U, R> wrap(@Nullable BiFunction<T, U, R> fn) { |
||||||
|
if (fn == null) return null; |
||||||
|
else if (fn instanceof TtlEnhanced) return fn; |
||||||
|
else return new TtlBiFunction<T, U, R>(fn); |
||||||
|
} |
||||||
|
|
||||||
|
private static class TtlBiFunction<T, U, R> implements BiFunction<T, U, R>, TtlWrapper<BiFunction<T, U, R>>, TtlEnhanced { |
||||||
|
final BiFunction<T, U, R> fn; |
||||||
|
final Object captured; |
||||||
|
|
||||||
|
TtlBiFunction(@NotNull BiFunction<T, U, R> 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<T, U, R> 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"); |
||||||
|
} |
||||||
|
} |
@ -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; |
@ -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> T getTtlAttachment(@NotNull String key); |
||||||
|
|
||||||
|
/** |
||||||
|
* The attachment key of TTL task, weather this task is a auto wrapper task. |
||||||
|
* <p> |
||||||
|
* so the value of this attachment is a {@code boolean}. |
||||||
|
* |
||||||
|
* @since 2.11.0 |
||||||
|
*/ |
||||||
|
String KEY_IS_AUTO_WRAPPER = "ttl.is.auto.wrapper"; |
||||||
|
} |
@ -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<String, Object> attachments = new ConcurrentHashMap<String, Object>(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setTtlAttachment(@NotNull String key, Object value) { |
||||||
|
attachments.put(key, value); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public <T> T getTtlAttachment(@NotNull String key) { |
||||||
|
return (T) attachments.get(key); |
||||||
|
} |
||||||
|
} |
@ -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 { |
||||||
|
} |
@ -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<T> extends TtlEnhanced { |
||||||
|
/** |
||||||
|
* unwrap {@link TtlWrapper} to the original/underneath one. |
||||||
|
* <p> |
||||||
|
* 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. |
||||||
|
* <p> |
||||||
|
* so {@code unwrap} will always return the same input object. |
||||||
|
* |
||||||
|
* @see TtlUnwrap#unwrap(Object) |
||||||
|
*/ |
||||||
|
@NotNull |
||||||
|
T unwrap(); |
||||||
|
} |
@ -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; |
@ -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<ForkJoinWorkerThreadFactory> { |
||||||
|
/** |
||||||
|
* Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
@NotNull |
||||||
|
ForkJoinWorkerThreadFactory unwrap(); |
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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<ThreadFactory> { |
||||||
|
/** |
||||||
|
* Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
@NotNull |
||||||
|
ThreadFactory unwrap(); |
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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<Runnable> 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 <T> Future<T> submit(@NotNull Callable<T> task) { |
||||||
|
return executorService.submit(TtlCallable.get(task)); |
||||||
|
} |
||||||
|
|
||||||
|
@NotNull |
||||||
|
@Override |
||||||
|
public <T> Future<T> 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 <T> List<Future<T>> invokeAll(@NotNull Collection<? extends Callable<T>> tasks) throws InterruptedException { |
||||||
|
return executorService.invokeAll(TtlCallable.gets(tasks)); |
||||||
|
} |
||||||
|
|
||||||
|
@NotNull |
||||||
|
@Override |
||||||
|
public <T> List<Future<T>> invokeAll(@NotNull Collection<? extends Callable<T>> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException { |
||||||
|
return executorService.invokeAll(TtlCallable.gets(tasks), timeout, unit); |
||||||
|
} |
||||||
|
|
||||||
|
@NotNull |
||||||
|
@Override |
||||||
|
public <T> T invokeAny(@NotNull Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { |
||||||
|
return executorService.invokeAny(TtlCallable.gets(tasks)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> T invokeAny(@NotNull Collection<? extends Callable<T>> 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; |
||||||
|
} |
||||||
|
} |
@ -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<Executor>, 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(); |
||||||
|
} |
||||||
|
} |
@ -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 <V> ScheduledFuture<V> schedule(@NotNull Callable<V> 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; |
||||||
|
} |
||||||
|
} |
@ -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. |
||||||
|
* |
||||||
|
* <ol> |
||||||
|
* <li>Factory methods to get TTL wrapper from jdk executors.</li> |
||||||
|
* <li>unwrap/check methods for TTL wrapper of jdk executors.</li> |
||||||
|
* <li>wrap/unwrap/check methods to disable Inheritable for {@link ThreadFactory}.</li> |
||||||
|
* </ol> |
||||||
|
* <p> |
||||||
|
* <b><i>Note:</i></b> |
||||||
|
* <ul> |
||||||
|
* <li>all method is {@code null}-safe, when input {@code executor} parameter is {@code null}, return {@code null}.</li> |
||||||
|
* <li>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.</li> |
||||||
|
* </ul> |
||||||
|
* |
||||||
|
* @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. |
||||||
|
* <p> |
||||||
|
* if the parameter executor is TTL wrapper, return {@code true}, otherwise {@code false}. |
||||||
|
* <p> |
||||||
|
* NOTE: if input executor is {@code null}, return {@code false}. |
||||||
|
* |
||||||
|
* @param executor input executor |
||||||
|
* @param <T> Executor type |
||||||
|
* @see #getTtlExecutor(Executor) |
||||||
|
* @see #getTtlExecutorService(ExecutorService) |
||||||
|
* @see #getTtlScheduledExecutorService(ScheduledExecutorService) |
||||||
|
* @see #unwrap(Executor) |
||||||
|
* @since 2.8.0 |
||||||
|
*/ |
||||||
|
public static <T extends Executor> boolean isTtlWrapper(@Nullable T executor) { |
||||||
|
return executor instanceof TtlEnhanced; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unwrap TTL wrapper executor to the original/underneath one. |
||||||
|
* <p> |
||||||
|
* if the parameter executor is TTL wrapper, return the original/underneath executor; |
||||||
|
* otherwise, just return the input parameter executor. |
||||||
|
* <p> |
||||||
|
* NOTE: if input executor is {@code null}, return {@code null}. |
||||||
|
* |
||||||
|
* @param executor input executor |
||||||
|
* @param <T> Executor type |
||||||
|
* @see #getTtlExecutor(Executor) |
||||||
|
* @see #getTtlExecutorService(ExecutorService) |
||||||
|
* @see #getTtlScheduledExecutorService(ScheduledExecutorService) |
||||||
|
* @see #isTtlWrapper(Executor) |
||||||
|
* @since 2.8.0 |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public static <T extends Executor> 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() { |
||||||
|
} |
||||||
|
} |
@ -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}. |
||||||
|
* <p> |
||||||
|
* <b><i>Note:</i></b> |
||||||
|
* <p> |
||||||
|
* 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() { |
||||||
|
} |
||||||
|
} |
@ -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. |
||||||
|
* <p> |
||||||
|
* The configuration/arguments for agent see the javadoc of {@link #premain(String, Instrumentation)} |
||||||
|
* <p> |
||||||
|
* <b><i>NOTE:</i></b><br> |
||||||
|
* Since {@code v2.6.0}, TTL agent jar will auto add self to {@code boot classpath}. |
||||||
|
* But you <b>should <i>NOT</i></b> modify the downloaded TTL jar file name in the maven repo(eg: {@code transmittable-thread-local-2.x.x.jar}).<br> |
||||||
|
* 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}. |
||||||
|
* <p> |
||||||
|
* 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: |
||||||
|
* <blockquote> |
||||||
|
* <dl> |
||||||
|
* <dt>Boot-Class-Path</dt> |
||||||
|
* <dd> |
||||||
|
* 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. |
||||||
|
* </dd> |
||||||
|
* </dl> |
||||||
|
* </blockquote> |
||||||
|
* <p> |
||||||
|
* More info about {@code Boot-Class-Path} see |
||||||
|
* <a href="https://docs.oracle.com/javase/10/docs/api/java/lang/instrument/package-summary.html">The mechanism for instrumentation</a>. |
||||||
|
* |
||||||
|
* @author Jerry Lee (oldratlee at gmail dot com) |
||||||
|
* @see TransmittableThreadLocal |
||||||
|
* @see Instrumentation |
||||||
|
* @see <a href="https://docs.oracle.com/javase/10/docs/api/java/lang/instrument/package-summary.html">The mechanism for instrumentation</a> |
||||||
|
* @see <a href="https://docs.oracle.com/javase/10/docs/specs/jar/jar.html#jar-manifest">JAR File Specification - JAR Manifest</a> |
||||||
|
* @see <a href="https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html">Working with Manifest Files - The Java™ TutorialsHide</a> |
||||||
|
* @since 0.9.0 |
||||||
|
*/ |
||||||
|
public final class TtlAgent { |
||||||
|
/** |
||||||
|
* Entrance method of TTL Java Agent. |
||||||
|
* |
||||||
|
* <h3>TTL Agent configuration</h3> |
||||||
|
* Configure TTL agent via agent arguments, format is {@code k1:v1,k2:v2}. Below is available configuration keys. |
||||||
|
* |
||||||
|
* <h3>Disable inheritable for thread pool</h3> |
||||||
|
* <p> |
||||||
|
* 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 <b>not</b> enabled. Since version {@code 2.10.1}. |
||||||
|
* |
||||||
|
* <ul> |
||||||
|
* <li>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)}. |
||||||
|
* </li> |
||||||
|
* <li>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)}. |
||||||
|
* </li> |
||||||
|
* </ul> |
||||||
|
* More info about "disable inheritable" see {@link TransmittableThreadLocal}. |
||||||
|
* <p> |
||||||
|
* Configuration example:<br> |
||||||
|
* {@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.disable.inheritable.for.thread.pool:true} |
||||||
|
* |
||||||
|
* <h3>The log configuration</h3> |
||||||
|
* The log of TTL Java Agent is config by key {@code ttl.agent.logger}. Since version {@code 2.6.0}. |
||||||
|
* |
||||||
|
* <ul> |
||||||
|
* <li>{@code ttl.agent.logger : STDERR}<br> |
||||||
|
* only log to {@code stderr} when error. |
||||||
|
* This is <b>default</b>, when no/unrecognized configuration for key {@code ttl.agent.logger}.</li> |
||||||
|
* <li>{@code ttl.agent.logger : STDOUT}<br> |
||||||
|
* Log to {@code stdout}, more info than {@code ttl.agent.logger:STDERR}; This is needed when developing.</li> |
||||||
|
* </ul> |
||||||
|
* <p> |
||||||
|
* configuration example: |
||||||
|
* <ul> |
||||||
|
* <li>{@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar}</li> |
||||||
|
* <li>{@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.logger:STDOUT}</li> |
||||||
|
* </ul> |
||||||
|
* |
||||||
|
* <h3>Enable/disable TimerTask class decoration</h3> |
||||||
|
* Enable/disable TimerTask class decoration is config by key {@code ttl.agent.enable.timer.task}. |
||||||
|
* Since version {@code 2.7.0}. |
||||||
|
* <p> |
||||||
|
* When no configuration for this key, default is <b>enabled</b>.<br> |
||||||
|
* <b><i>Note</i></b>: 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}. |
||||||
|
* <p> |
||||||
|
* Configuration example: |
||||||
|
* <ul> |
||||||
|
* <li>{@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.enable.timer.task:false}</li> |
||||||
|
* <li>{@code -javaagent:/path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.enable.timer.task:true}</li> |
||||||
|
* </ul> |
||||||
|
* |
||||||
|
* <h3>Multi key configuration example</h3> |
||||||
|
* {@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<JavassistTransformlet> transformletList = new ArrayList<JavassistTransformlet>(); |
||||||
|
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<String, String> kvs) { |
||||||
|
return kvs.get(Logger.TTL_AGENT_LOGGER_KEY); |
||||||
|
} |
||||||
|
|
||||||
|
private static volatile Map<String, String> 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<String, String> kvs, @NotNull String key) { |
||||||
|
return isOptionSetOrFalse(kvs, key, false); |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isOptionSetOrTrue(@Nullable final Map<String, String> kvs, @NotNull String key) { |
||||||
|
return isOptionSetOrFalse(kvs, key, true); |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isOptionSetOrFalse(@Nullable final Map<String, String> 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<String, String> splitCommaColonStringToKV(@Nullable String commaColonString) { |
||||||
|
Map<String, String> ret = new HashMap<String, String>(); |
||||||
|
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"); |
||||||
|
} |
||||||
|
} |
@ -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 <a href="https://docs.oracle.com/javase/10/docs/api/java/lang/instrument/package-summary.html">The mechanism for instrumentation</a> |
||||||
|
* @since 0.9.0 |
||||||
|
*/ |
||||||
|
public class TtlTransformer implements ClassFileTransformer { |
||||||
|
private static final Logger logger = Logger.getLogger(TtlTransformer.class); |
||||||
|
|
||||||
|
/** |
||||||
|
* "<code>null</code> 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<JavassistTransformlet> transformletList = new ArrayList<JavassistTransformlet>(); |
||||||
|
|
||||||
|
TtlTransformer(List<? extends JavassistTransformlet> 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('/', '.'); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
@ -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<String> EXECUTOR_CLASS_NAMES = new HashSet<String>(); |
||||||
|
private static final Map<String, String> PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS = new HashMap<String, String>(); |
||||||
|
|
||||||
|
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($%<d);", |
||||||
|
i + 1, PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS.get(paramTypeName), i + 1); |
||||||
|
logger.info("insert code before method " + Utils.signatureOfMethod(method) + " of class " + method.getDeclaringClass().getName() + ": " + code); |
||||||
|
insertCode.append(code); |
||||||
|
} |
||||||
|
} |
||||||
|
if (insertCode.length() > 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($%<d);", i + 1); |
||||||
|
logger.info("insert code before method " + Utils.signatureOfMethod(constructor) + " of class " + constructor.getDeclaringClass().getName() + ": " + code); |
||||||
|
insertCode.append(code); |
||||||
|
} |
||||||
|
} |
||||||
|
if (insertCode.length() > 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; |
||||||
|
} |
||||||
|
} |
@ -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($%<d);", i + 1); |
||||||
|
logger.info("insert code before method " + Utils.signatureOfMethod(constructor) + " of class " + constructor.getDeclaringClass().getName() + ": " + code); |
||||||
|
insertCode.append(code); |
||||||
|
} |
||||||
|
} |
||||||
|
if (insertCode.length() > 0) constructor.insertBefore(insertCode.toString()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
@ -0,0 +1,8 @@ |
|||||||
|
/** |
||||||
|
* TTL Agent. |
||||||
|
* |
||||||
|
* @author Jerry Lee (oldratlee at gmail dot com) |
||||||
|
* @see com.fr.third.alibaba.ttl.threadpool.agent.TtlAgent |
||||||
|
* @see <a href="https://docs.oracle.com/javase/10/docs/api/java/lang/instrument/package-summary.html">The mechanism for instrumentation</a> |
||||||
|
*/ |
||||||
|
package com.fr.third.alibaba.ttl.threadpool.agent; |
Loading…
Reference in new issue