Browse Source
Merge in CORE/base-third from ~ELIJAH/base-third:qufenxi to qufenxi * commit '8cad028623d000a2f393fe03995c903d2947f74d': DEC-14578 打包脚本 DEC-14578 引入com.alibaba:transmittable-thread-localqufenxi
Elijah
4 years ago
40 changed files with 3803 additions and 2 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